如何用元素包装/环绕突出显示的文本

cou*_*011 40 javascript dom selection

我想在带有span的div容器中包装选定的文本,是否可能?

用户将选择文本并单击按钮,在按钮单击事件上我想用span元素包装所选文本.我可以使用所选文本,window.getSelection()但如何知道它在DOM结构中的确切位置?

Tim*_*own 60

如果选择完全包含在单个文本节点中,则可以使用surroundContents()从选择中获得的范围的方法执行此操作.但是,这非常脆弱:如果选择不能在逻辑上包围在单个元素中(通常,如果范围跨越节点边界,虽然这不是精确的定义),它不起作用.要在一般情况下执行此操作,您需要一种更复杂的方法.

此外,DOM Rangewindow.getSelection()没有在IE <9支持你再次需要另一种方法为这些浏览器.您可以使用我自己的Rangy等库来规范浏览器行为,您可能会发现类应用程序模块对此问题很有用.

简单的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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 如果所选文本跨越节点边界,我们如何用跨度包裹所选文本?任何解决方案(无论多么复杂)都将不胜感激。 (3认同)

ama*_*mal 20

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);
}
Run Code Online (Sandbox Code Playgroud)
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" />
Run Code Online (Sandbox Code Playgroud)

JS小提琴.

  • 只要选择不跨越多个块级元素,并且只要您不介意可能获得嵌套跨度,这就没问题.此外,如果链接页面不起作用,只包含jsFiddle链接的答案将毫无用处,因此最好至少在答案中描述方法. (14认同)

vor*_*ity 6

这是对允许跨越元素边界的通用解决方案的尝试。可能与图像等或非从左到右的文本混合得不好,但对于简单的情况应该没问题。

首先,这是一个非常通用的函数,用于从左到右遍历由 Range 对象定义的子树中的文本节点。这将为我们提供所需的所有文本:

function walkRange(range) {
    let ranges = [];
    
    let el = range.startContainer;
    let elsToVisit = true;
    while (elsToVisit) {
        let startOffset = el == range.startContainer ? range.startOffset : 0;
        let endOffset = el == range.endContainer ? range.endOffset : el.textContent.length;
        let r = document.createRange();
        r.setStart(el, startOffset);
        r.setEnd(el, endOffset);
        ranges.push(r);
        
        
        /// Move to the next text container in the tree order
        elsToVisit = false;
        while (!elsToVisit && el != range.endContainer) {
            let nextEl = getFirstTextNode(el.nextSibling);
            if (nextEl) {
                el = nextEl;
                elsToVisit = true;
            }
            else {
                if (el.nextSibling)      el = el.nextSibling;
                else if (el.parentNode)  el = el.parentNode;
                else                     break;
            }
        }
    }
    
    return ranges;
}
Run Code Online (Sandbox Code Playgroud)

这利用了这个实用函数来获取子树中的第一个(最左边)文本节点:

function getFirstTextNode(el) {
    /// Degenerate cases: either el is null, or el is already a text node
    if (!el)               return null;
    if (el.nodeType == 3)  return el;
    
    for (let child of el.childNodes) {
        if (child.nodeType == 3) {
            return child;
        }
        else {
            let textNode = getFirstTextNode(child);
            if (textNode !== null) return textNode;
        }
    }
    
    return null;
}
Run Code Online (Sandbox Code Playgroud)

一旦您调用了walkRanges,您就可以使用surroundContents它返回的内容来实际进行突出显示/标记。这是在一个函数中:

function highlight(range, className) {
    range = range.getRangeAt ? range.getRangeAt(0) : range;
    for (let r of walkRange(range)) {
        let mark = document.createElement('mark');
        mark.className = className;
        r.surroundContents(mark);
    }
}
Run Code Online (Sandbox Code Playgroud)

并取消突出显示(假设您使用了唯一的类名来突出显示):

function unhighlight(sel) {
    document.querySelectorAll(sel).forEach(el => el.replaceWith(...el.childNodes));
}
Run Code Online (Sandbox Code Playgroud)

用法示例:

highlight(document.getSelection(), 'mySelectionClassName');
unhighlight('.mySelectionClassName')
Run Code Online (Sandbox Code Playgroud)