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

时间:2011-06-13 09:33:52

标签: javascript dom selection

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

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

6 个答案:

答案 0 :(得分:55)

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

此外,IE中不支持DOM Rangewindow.getSelection()。 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 :(得分:18)

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

答案 2 :(得分:4)

有可能。您需要使用范围API和Range.surroundContents()方法。它将节点内容包装在指定范围的开头。 见https://developer.mozilla.org/en/DOM/range.surroundContents

答案 3 :(得分:3)

仅当您的选择仅包含文本且不包含HTML时,

surroundContents才有效。这是一个更灵活的跨浏览器解决方案。这将插入一个这样的跨度:

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

答案 4 :(得分:2)

这是尝试允许跨元素边界的通用解决方案。可能不能与图像等很好地融合在一起,也不能与非左右文本融合在一起,但在简单的情况下应该可以。

首先,这是一个非常通用的功能,用于从左到右遍历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;
}

利用此实用程序功能获取子树中的第一个(最左侧)文本节点:

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

一旦您致电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);
    }
}

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

function unhighlight(sel) {
    document.querySelectorAll(sel).forEach(el => el.replaceWith(...el.childNodes));
}

用法示例:

highlight(document.getSelection(), 'mySelectionClassName');
unhighlight('.mySelectionClassName')

答案 5 :(得分: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';
                    }
                }
            }
        }
    }
}