2011-06-13 19 views
29

Quiero envolver un texto seleccionado en un contenedor div con span, ¿es posible?Envoltura de un nodo de texto seleccionado con span

Un usuario seleccionará un texto y hará clic en un botón, en el botón haga clic en el evento Quiero envolver ese texto seleccionado con el elemento span. Puedo obtener el texto seleccionado usando window.getSelection(), pero ¿cómo saber su posición exacta en la estructura DOM?

Respuesta

42

Si la selección está completamente contenida dentro de un solo nodo de texto, puede hacerlo utilizando el método surroundContents() del rango que obtiene de la selección. Sin embargo, esto es muy frágil: no funciona si la selección no puede rodearse lógicamente en un solo elemento (generalmente, si el rango cruza los límites del nodo, aunque este no es el precise definition). Para hacer esto en el caso general, necesita un enfoque más complicado.

Además, DOM Range y window.getSelection() no son compatibles con IE < 9. Necesitará otro enfoque para esos navegadores. Puede usar una biblioteca como la mía Rangy para normalizar el comportamiento del navegador y puede encontrar el class applier module útil para esta pregunta.

simple surroundContents() ejemplo jsFiddle: http://jsfiddle.net/VRcvn/

Código: sólo

function surroundSelection(element) { 
    if (window.getSelection) { 
     var sel = window.getSelection(); 
     if (sel.rangeCount) { 
      var range = sel.getRangeAt(0).cloneRange(); 
      range.surroundContents(element); 
      sel.removeAllRanges(); 
      sel.addRange(range); 
     } 
    } 
} 
+1

¿Cómo podemos envolver el texto seleccionado con un lapso si el texto seleccionado cruza los límites del nodo? Cualquier solución (no importa cuán complicada) sería muy apreciada. –

+1

@JoshGrinberg: Eso no sería posible porque habría etiquetas no coincidentes (por ejemplo, 'foo bar baz'). Probablemente, primero tendría que manipular las etiquetas ya existentes para que el área que desea envolver solo esté contenida en un nodo. – rvighne

+0

@Tim Down Estoy usando su biblioteca rangy para crear notas en el texto seleccionado.Casi lo he hecho, pero me he quedado atascado en un problema: mostrar un punto antes del texto seleccionado, pero en el caso de elementos múltiples, cuando trato de crear una nota sobre eso, aparece el punto varias veces, debido a la creación de varias etiquetas de extensión debido a elementos múltiples ¿Cómo puedo aplicar una clase en el primer nodo para el texto seleccionado? –

12

function wrapSelectedText() {  
 
    var selection= window.getSelection().getRangeAt(0); 
 
    var selectedText = selection.extractContents(); 
 
    var span= document.createElement("span"); 
 
    span.style.backgroundColor = "yellow"; 
 
    span.appendChild(selectedText); 
 
    selection.insertNode(span); 
 
}
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam rhoncus gravida magna, quis interdum magna mattis quis. Fusce tempor sagittis varius. Nunc at augue at erat suscipit bibendum id nec enim. Sed eu odio quis turpis hendrerit sagittis id sit amet justo. Cras ac urna purus, non rutrum nunc. Aenean nec vulputate ante. Morbi scelerisque sagittis hendrerit. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla tristique ligula fermentum tortor semper at consectetur erat aliquam. Sed gravida consectetur sollicitudin. 
 

 
<input type="button" onclick="wrapSelectedText();" value="Highlight" />

JS Fiddle.

+13

Esto está bien siempre y cuando la selección no abarque múltiples elementos de nivel de bloque, y siempre que no le importe potencialmente conseguir tramos anidados. Además, una respuesta que solo contenga un enlace jsFiddle no servirá de nada si la página vinculada no funciona, por lo que es mejor describir al menos el enfoque en la respuesta. –

3

surroundContents funciona si la selección contiene solo texto, sin HTML. Aquí hay una solución más flexible y entre navegadores. Esto insertará un lapso como este:

<span id="new_selection_span"><!--MARK--></span> 

El span se inserta antes de la selección, delante de la etiqueta HTML de apertura más cercana.

var span = document.createElement("span"); 
span.id = "new_selection_span"; 
span.innerHTML = '<!--MARK-->'; 

if (window.getSelection) { //compliant browsers 
    //obtain the selection 
    sel = window.getSelection(); 
    if (sel.rangeCount) { 
     //clone the Range object 
     var range = sel.getRangeAt(0).cloneRange(); 
     //get the node at the start of the range 
     var node = range.startContainer; 
     //find the first parent that is a real HTML tag and not a text node 
     while (node.nodeType != 1) node = node.parentNode; 
     //place the marker before the node 
     node.parentNode.insertBefore(span, node); 
     //restore the selection 
     sel.removeAllRanges(); 
     sel.addRange(range); 
    } 
} else { //IE8 and lower 
    sel = document.selection.createRange(); 
    //place the marker before the node 
    var node = sel.parentElement(); 
    node.parentNode.insertBefore(span, node); 
    //restore the selection 
    sel.select(); 
} 
+0

no está cerca http://jsfiddle.net/kjntoj1m/ – KeepMove

+0

Si desea rodear una selección que no contiene elementos HTML, la solución aceptada funciona mejor. Esta solución lo ayudará a identificar un nodo ya existente que contiene el texto seleccionado, al colocar un lapso de marcado justo antes del elemento de destino. –

0

El siguiente código será útil para envolver la etiqueta span para todo tipo de etiquetas. Por favor revise el código y use la lógica para su implementación.

getSelectedText(this); 
addAnnotationElement(this, this.parent); 

function getSelectedText(this) { 
    this.range = window.getSelection().getRangeAt(0); 
    this.parent = this.range.commonAncestorContainer; 
    this.frag = this.range.cloneContents(); 
    this.clRange = this.range.cloneRange(); 
    this.start = this.range.startContainer; 
    this.end = this.range.endContainer; 
} 


function addAnnotationElement(this, elem) { 
    var text, textParent, origText, prevText, nextText, childCount, 
     annotationTextRange, 
     span = this.htmlDoc.createElement('span'); 

    if (elem.nodeType === 3) { 
     span.setAttribute('class', this.annotationClass); 
     span.dataset.name = this.annotationName; 
     span.dataset.comment = ''; 
     span.dataset.page = '1'; 
     origText = elem.textContent;    
     annotationTextRange = validateTextRange(this, elem); 
     if (annotationTextRange == 'textBeforeRangeButIntersect') { 
      text = origText.substring(0, this.range.endOffset); 
      nextText = origText.substring(this.range.endOffset); 
     } else if (annotationTextRange == 'textAfterRangeButIntersect') { 
      prevText = origText.substring(0, this.range.startOffset); 
      text = origText.substring(this.range.startOffset); 
     } else if (annotationTextRange == 'textExactlyInRange') { 
      text = origText 
     } else if (annotationTextRange == 'textWithinRange') { 
      prevText = origText.substring(0, this.range.startOffset); 
      text = origText.substring(this.range.startOffset,this.range.endOffset); 
      nextText = origText.substring(this.range.endOffset); 
     } else if (annotationTextRange == 'textNotInRange') { 
      return; 
     } 
     span.textContent = text; 
     textParent = elem.parentElement; 
     textParent.replaceChild(span, elem); 
     if (prevText) { 
      var prevDOM = this.htmlDoc.createTextNode(prevText); 
      textParent.insertBefore(prevDOM, span); 
     } 
     if (nextText) { 
      var nextDOM = this.htmlDoc.createTextNode(nextText); 
      textParent.insertBefore(nextDOM, span.nextSibling); 
     } 
     return; 
    } 
    childCount = elem.childNodes.length; 
    for (var i = 0; i < childCount; i++) { 
     var elemChildNode = elem.childNodes[i]; 
     if(Helper.isUndefined(elemChildNode.tagName) || 
      ! (elemChildNode.tagName.toLowerCase() === 'span' && 
      elemChildNode.classList.contains(this.annotationClass))) { 
      addAnnotationElement(this, elem.childNodes[i]); 
     } 
     childCount = elem.childNodes.length; 
    } 
} 

    function validateTextRange(this, elem) { 
    var textRange = document.createRange(); 

    textRange.selectNodeContents (elem); 
    if (this.range.compareBoundaryPoints (Range.START_TO_END, textRange) <= 0) { 
     return 'textNotInRange'; 
    } 
    else { 
     if (this.range.compareBoundaryPoints (Range.END_TO_START, textRange) >= 0) { 
      return 'textNotInRange'; 
     } 
     else { 
      var startPoints = this.range.compareBoundaryPoints (Range.START_TO_START, textRange), 
       endPoints = this.range.compareBoundaryPoints (Range.END_TO_END, textRange); 

      if (startPoints < 0) { 
       if (endPoints < 0) { 
        return 'textBeforeRangeButIntersect'; 
       } 
       else { 
        return "textExactlyInRange"; 
       } 
      } 
      else { 
       if (endPoints > 0) { 
        return 'textAfterRangeButIntersect'; 
       } 
       else { 
        if (startPoints === 0 && endPoints === 0) { 
         return "textExactlyInRange"; 
        } 
        else { 
         return 'textWithinRange'; 
        } 
       } 
      } 
     } 
    } 
} 
+1

¡Bienvenido a StackOverflow! Una gran respuesta explica qué está haciendo el código para que las personas puedan aprender de él en lugar de ser simplemente un bloque de código enorme. Puede mejorar su respuesta resaltando las partes importantes. – iblamefish

Cuestiones relacionadas