2011-06-13 57 views
29

我想将选定的文本包装在具有范围的div容器中,是否有可能?使用范围包装选定的文本节点

用户将选择一个文本,并点击一个按钮,在按钮点击事件我想用span元素来包装选定的文本。我可以使用window.getSelection()得到选定的文本,但是如何知道它在DOM结构中的确切位置?

回答

42

如果所选内容完全包含在单个文本节点中,则可以使用从选择中获得的范围的surroundContents()方法执行此操作。但是,这非常脆弱:如果选择无法逻辑地包含在单个元素中(通常,如果范围跨越节点边界,虽然这不是precise definition),但它不起作用。要在一般情况下这样做,您需要更复杂的方法。

另外,DOM Rangewindow.getSelection()在IE <中不受支持9.您需要为这些浏览器再次使用另一种方法。您可以使用我自己的Rangy等库来标准化浏览器行为,并且您可能会发现class applier module对此问题很有用。

简单surroundContents()例子的jsfiddle:http://jsfiddle.net/VRcvn/

代码:只有

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

如果所选文本跨越节点边界,我们如何用span选择文本进行封装?任何解决方案(无论多么复杂)都将不胜感激。 – 2016-06-15 15:33:44

+1

@JoshGrinberg:这是不可能的,因为您的标签不匹配(例如'foo bar baz')。可能您首先必须操作已有的标签,以便您要包装的区域只包含在一个节点中。 – rvighne 2016-07-20 18:12:02

+0

@Tim Down我正在使用你的rangy库在选定的文本上创建注释。我已经差不多完成了,但遇到了一个问题,我在选定的文本之前显示了一个点,但是在多个元素的情况下,当我尝试创建注释时,它多次显示点,因为创建了多个span标记,原因是多个元素。我如何才能在选定文本的第一个节点上应用任何一个类? – 2017-04-19 09:33:51

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

只要选择不跨越多个块级元素,并且只要您不介意可能获取嵌套跨度,这就好了。此外,如果链接的页面不工作,只包含jsFiddle链接的答案根本就没用,所以最好至少在答案中描述方法。 – 2011-06-13 09:56:40

3

surroundContents如果您选择只包含文本和HTML没有工作。这是一个更灵活的跨浏览器解决方案。这将插入一个这样的跨度:

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

跨度插入选择之前,在最近的开始HTML标记的前面。

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

它不包围http://jsfiddle.net/kjntoj1m/ – KeepMove 2016-05-11 11:24:06

+0

如果您想围绕不包含HTML元素的选择,则接受的解决方案效果最好。此解决方案将通过在目标元素之前放置标记跨度来帮助您识别包含选定文本的现有节点。 – 2016-05-16 13:53:18

0

请找到下面的代码将为所有类型的标签包装span标签是有用的。请仔细阅读代码并使用您的实施逻辑。

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

欢迎来到StackOverflow!一个很好的答案解释了代码在做什么,以便人们可以从中学习,而不仅仅是一个巨大的代码块。您可以通过突出显示重要部分来改进答案。 – iblamefish 2017-01-05 12:33:31