【问题标题】:Highlight text range using JavaScript使用 JavaScript 突出显示文本范围
【发布时间】:2011-09-08 13:38:37
【问题描述】:

我想突出显示(应用 css 到)某个文本范围,由它的开始和结束位置表示。这比看起来更困难,因为文本中可能还有其他标签需要忽略。

例子:

<div>abcd<em>efg</em>hij</div>

highlight(2, 6) 需要在不移除标签的情况下突出显示"cdef"。

我已经尝试过使用 TextRange 对象,但没有成功。

提前致谢!

【问题讨论】:

  • 你能去掉临时字符串中的标签,然后从那个字符串中提取子字符串吗?
  • 你不能忽略标签,否则你会得到无效的 html:ab&lt;x&gt;cd&lt;em&gt;ef&lt;/x&gt;f&lt;/em&gt;。你需要做类似ab&lt;x&gt;cd&lt;/x&gt;&lt;em&gt;&lt;x&gt;ef&lt;/x&gt;g&lt;/em&gt;
  • 我当然不能忽略标签,但如果浏览器能以某种方式为我解决这些问题,那就太好了。

标签: javascript selection textrange


【解决方案1】:

下面是一个函数,用于将选择设置为特定元素内的一对字符偏移量。这是一个幼稚的实现:它没有考虑任何可能变得不可见的文本(例如,通过 CSS 或通过位于 &lt;script&gt;&lt;style&gt; 元素内)并且可能存在浏览器差异(IE 与其他所有内容)带有换行符,并且不考虑折叠的空白(例如 2 个或更多连续的空格字符折叠到页面上的一个可见空间)。但是,它在所有主流浏览器中都适用于您的示例。

对于另一部分,突出显示,我建议使用document.execCommand()。你可以使用我下面的函数来设置选择,然后调用document.execCommand()。您需要使该文档在非 IE 浏览器中临时可编辑,以使该命令起作用。在此处查看我的答案以获取代码:getSelection & surroundContents across multiple tags

这是一个 jsFiddle 示例,展示了整个内容,适用于所有主流浏览器:http://jsfiddle.net/8mdX4/1211/

以及选择设置代码:

function getTextNodesIn(node) {
    var textNodes = [];
    if (node.nodeType == 3) {
        textNodes.push(node);
    } else {
        var children = node.childNodes;
        for (var i = 0, len = children.length; i < len; ++i) {
            textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
        }
    }
    return textNodes;
}

function setSelectionRange(el, start, end) {
    if (document.createRange && window.getSelection) {
        var range = document.createRange();
        range.selectNodeContents(el);
        var textNodes = getTextNodesIn(el);
        var foundStart = false;
        var charCount = 0, endCharCount;

        for (var i = 0, textNode; textNode = textNodes[i++]; ) {
            endCharCount = charCount + textNode.length;
            if (!foundStart && start >= charCount
                    && (start < endCharCount ||
                    (start == endCharCount && i <= textNodes.length))) {
                range.setStart(textNode, start - charCount);
                foundStart = true;
            }
            if (foundStart && end <= endCharCount) {
                range.setEnd(textNode, end - charCount);
                break;
            }
            charCount = endCharCount;
        }

        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    } else if (document.selection && document.body.createTextRange) {
        var textRange = document.body.createTextRange();
        textRange.moveToElementText(el);
        textRange.collapse(true);
        textRange.moveEnd("character", end);
        textRange.moveStart("character", start);
        textRange.select();
    }
}

【讨论】:

  • 出色的解决方案@tim-down!您的代码适用于合并嵌套的 HTML 格式文本。 stackoverflow.com/questions/16226671/…
  • 当我运行这段代码时,一个跨度标签被添加到所有突出显示的文本中,我可以向该跨度标签添加一个类和一个点击事件吗?
  • @prateek:我假设您的意思是突出显示代码?由于跨度是由浏览器自动添加的,因此很难找到并修改它们。您可以改用我的Rangy 库中的class applier module
  • @TimDown 在创建跨度标签时这不可能我可以添加一些属性和事件,就像您添加背景颜色一样
  • @prateek:应用背景颜色的跨度由浏览器生成,以响应document.execCommand(...) 的简单调用。您可以通过检查文档中每个跨度的background-color 的计算值来搜索跨度,如stackoverflow.com/questions/8076341/…
【解决方案2】:

您可以看看这个支持对多个 DOM 元素进行选择的强大 JavaScript 实用程序的工作原理:

MASHA(Mark & Share 的缩写)允许您标记网页内容的有趣部分并分享它

http://mashajs.com/index_eng.html

它也在 GitHub 上 https://github.com/SmartTeleMax/MaSha

甚至可以在 Mobile Safari 和 IE 上使用!

【讨论】:

    【解决方案3】:

    以下解决方案不适用于 IE,您需要为此应用 TextRange 对象等。由于它使用选择来执行此操作,因此在正常情况下不应破坏 HTML,例如:

    <div>abcd<span>efg</span>hij</div>
    

    highlight(3,6);

    输出:

    <div>abc<em>d<span>ef</span></em><span>g</span>hij</div>
    

    请注意它是如何将跨度之外的第一个字符包装成em,然后将span 内的其余字符包装成一个新字符。好像它只是在字符 3 处打开并在字符 6 处结束,它会给出无效的标记,例如:

    <div>abc<em>d<span>ef</em>g</span>hij</div>
    

    代码:

    var r = document.createRange();
    var s = window.getSelection()
    
    r.selectNode($('div')[0]);
    s.removeAllRanges();
    s.addRange(r);
    
    // not quite sure why firefox has problems with this
    if ($.browser.webkit) {
        s.modify("move", "backward", "documentboundary");
    }
    
    function highlight(start,end){
        for(var st=0;st<start;st++){
            s.modify("move", "forward", "character");
        }
    
        for(var st=0;st<(end-start);st++){
            s.modify("extend", "forward", "character");
        }
    }
    
    highlight(2,6);
    
    var ra = s.getRangeAt(0);
    var newNode = document.createElement("em");
    newNode.appendChild(ra.extractContents()); 
    ra.insertNode(newNode);
    

    示例:http://jsfiddle.net/niklasvh/4NDb9/

    edit看起来至少我的FF4有些问题

    s.modify("move", "backward", "documentboundary");
    

    但同时,它似乎没有它也能工作,所以我只是把它改成了

    if ($.browser.webkit) {
            s.modify("move", "backward", "documentboundary");
    }
    

    编辑 正如蒂姆指出的那样,修改仅从 FF4 开始可用,所以我采用了不同的方法来获取选择,不需要修改方法,希望使其更兼容浏览器(IE 仍然需要自己的解决方案)。

    代码:

    var r = document.createRange();
    var s = window.getSelection()
    
    var pos = 0;
    
    function dig(el){
        $(el).contents().each(function(i,e){
            if (e.nodeType==1){
                // not a textnode
             dig(e);   
            }else{
                if (pos<start){
                   if (pos+e.length>=start){
                    range.setStart(e, start-pos);
                   }
                }
    
                if (pos<end){
                   if (pos+e.length>=end){
                    range.setEnd(e, end-pos);
                   }
                }            
    
                pos = pos+e.length;
            }
        });  
    }
    var start,end, range;
    
    function highlight(element,st,en){
        range = document.createRange();
        start = st;
        end = en;
        dig(element);
        s.addRange(range);
    
    }
    highlight($('div'),3,6);
    
    var ra = s.getRangeAt(0);
    
    var newNode = document.createElement("em");
    newNode.appendChild(ra.extractContents()); 
    ra.insertNode(newNode);
    

    示例:http://jsfiddle.net/niklasvh/4NDb9/

    【讨论】:

    • Firefox 自 Firefox 4.0 起仅实现 Selection.modify(),并不支持 WebKit 所做的所有粒度设置。具体来说,它不支持“文档边界”。见developer.mozilla.org/en/DOM/selection/modify
    • 这似乎只有在页面上没有其他内容时才有效。例如:jsfiddle.net/4NDb9/89。虽然 Hello world 在 div 之外,但它被突出显示,而不是 div 内的文本。
    • @Tim Down 非常好。我现在重写了其中的很大一部分以摆脱 modify() 方法。
    • @Vincent 应该在这个重写的代码中排序,看看jsfiddle.net/niklasvh/4NDb9/93。 highlight 函数现在要求第一个变量是要从中查找的元素。
    【解决方案4】:

    基于jQuery.highlight插件的思想。

        private highlightRange(selector: JQuery, start: number, end: number): void {
            let cur = 0;
            let replacements: { node: Text; pos: number; len: number }[] = [];
    
            let dig = function (node: Node): void {
                if (node.nodeType === 3) {
                    let nodeLen = (node as Text).data.length;
                    let next = cur + nodeLen;
                    if (next > start && cur < end) {
                        let pos = cur >= start ? cur : start;
                        let len = (next < end ? next : end) - pos;
                        if (len > 0) {
                            if (!(pos === cur && len === nodeLen && node.parentNode &&
                                node.parentNode.childNodes && node.parentNode.childNodes.length === 1 &&
                                (node.parentNode as Element).tagName === 'SPAN' && (node.parentNode as Element).className === 'highlight1')) {
    
                                replacements.push({
                                    node: node as Text,
                                    pos: pos - cur,
                                    len: len,
                                });
                            }
                        }
                    }
                    cur = next;
                }
                else if (node.nodeType === 1) {
                    let childNodes = node.childNodes;
                    if (childNodes && childNodes.length) {
                        for (let i = 0; i < childNodes.length; i++) {
                            dig(childNodes[i]);
                            if (cur >= end) {
                                break;
                            }
                        }
                    }
                }
            };
    
            selector.each(function (index, element): void {
                dig(element);
            });
    
            for (let i = 0; i < replacements.length; i++) {
                let replacement = replacements[i];
                let highlight = document.createElement('span');
                highlight.className = 'highlight1';
                let wordNode = replacement.node.splitText(replacement.pos);
                wordNode.splitText(replacement.len);
                let wordClone = wordNode.cloneNode(true);
                highlight.appendChild(wordClone);
                wordNode.parentNode.replaceChild(highlight, wordNode);
            }
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-04-07
      • 1970-01-01
      • 2010-12-10
      • 1970-01-01
      • 1970-01-01
      • 2014-05-31
      • 1970-01-01
      • 2012-08-02
      相关资源
      最近更新 更多