Encapsulando um nó de texto selecionado com extensão

Eu quero embrulhar um texto selecionado em um contêiner div com span, é possível?

Um usuário selecionará um texto e clicará em um botão; no evento de clique no botão, quero include o texto selecionado com o elemento span. Eu posso obter o texto selecionado usando window.getSelection() mas como saber sua posição exata na estrutura DOM?

Se a seleção estiver completamente contida em um único nó de texto, você poderá fazer isso usando o método surroundContents() do intervalo obtido na seleção. No entanto, isso é muito frágil: não funciona se a seleção não puder ser logicamente envolvida em um único elemento (geralmente, se o intervalo ultrapassar os limites dos nós, embora essa não seja a definição precisa ). Para fazer isso no caso geral, você precisa de uma abordagem mais complicada.

Além disso, DOM Range e window.getSelection() não são suportados no IE <9. Você precisará de outra abordagem novamente para esses navegadores. Você pode usar uma biblioteca como o meu próprio Rangy para normalizar o comportamento do navegador e você pode achar o módulo de aplicador de classe útil para essa pergunta.

Exemplo simples de surroundContents() jsFiddle: http://jsfiddle.net/VRcvn/

Código:

 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); } } } 
 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.  

é possível. Você precisa usar a API de intervalo e o método Range.surroundContents (). Ele coloca o nó no qual o conteúdo é empacotado no início do intervalo especificado. veja https://developer.mozilla.org/en/DOM/range.surroundContents

surroundContents só funciona se a sua seleção contiver apenas texto e sem HTML. Aqui está uma solução mais flexível, bem como entre navegadores. Isto irá inserir um intervalo como este:

  

O intervalo é inserido antes da seleção, na frente da tag HTML de abertura mais próxima.

 var span = document.createElement("span"); span.id = "new_selection_span"; span.innerHTML = ''; 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(); } 

Por favor, encontrar o código abaixo será útil para envolver a tag span para todos os tipos de tags. Por favor, vá até o código e use a lógica para sua implementação.

 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'; } } } } } }