使用JavaScript突出显示文本范围

45

我想要对一个指定起始位置和结束位置的文本范围进行高亮(应用CSS),但这比看起来更加困难,因为文本中可能会有其他需要忽略的标签。

例如:

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

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

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

预先感谢!


你能否从临时字符串中去除标签,然后对该字符串进行子字符串操作? - Kevin Ji
你不能忽略标签,否则你最终会得到无效的HTML代码:ab<x>cd<em>ef</x>f</em>。你需要做类似这样的事情:ab<x>cd</x><em><x>ef</x>g</em> - serg
1
当然我不能忽略标签,但如果浏览器能以某种方式解决这些问题,那就太好了。 - Vincent
5个回答

70
下面是一个将选区设置为特定元素内一对字符偏移量的函数。这是一个简单的实现:它不考虑任何可能被隐藏的文本(无论是通过CSS还是在`

2
太棒了,@tim-down的解决方案非常出色!你的代码已经被改编用于合并嵌套的HTML格式文本。http://stackoverflow.com/questions/16226671/consolidate-stacked-dom-formatting-elements-contenteditable-div - Mike Wolfe
当我运行这段代码时,所有被突出显示的文本都会添加一个<span>标签。我能给这个<span>标签添加一个类和一个点击事件吗? - Prateek
@prateek:我猜你是指代码高亮吧?由于浏览器自动添加了这些跨度,因此很难找到它们并进行修改。相反,你可以使用我的Rangy库中的class applier module来实现。 - Tim Down
@prateek:应用背景颜色的跨度是通过浏览器响应简单调用document.execCommand(...)生成的。您可以通过检查文档中每个跨度的计算值background-color来搜索跨度,例如在https://dev59.com/NV3Ua4cB1Zd3GeqP9Bbc#8106283中所示。 - Tim Down
@TimDown 感谢您的建议,它在我的情况下运行良好,但当标签的层次结构更多时,它不会检查这个 fiddle。我在这里使用了您的 JavaScript 代码。http://jsfiddle.net/Bvd9d/47/ - Prateek
显示剩余5条评论

3

0

我知道这个问题与此无关,但这正是我正在寻找的。

如果您需要突出显示所选文本

请使用以下原则:使用{{link1:Selection}} {{link2:Range}}方法进行操作,就像这样


document.getSelection().getRangeAt(0).surroundContents(YOUR_WRAPPER_NODE) // Adds wrapper
document.getSelection().getRangeAt(0).insertNode(NEW_NODE) // Inserts a new node

就是这样,我建议你多学习一下Range方法。

我曾经也为此苦苦挣扎,我的搜索请求也不正确,所以我决定在这里发布它,以防有像我一样的人。

再次对不相关的答案表示抱歉。


1
我认为在你的代码片段中,YOUR_WRAPPER_NODENEW_NODE是同一个实体,因此最好将它们命名为相同的名称。此外,你的解决方案在范围包含部分选定元素时无法正常工作。在这种情况下,将调用surroundContents替换为YOUR_WRAPPER_NODE.appendChild(document.getSelection().getRangeAt(0).extractContents()) - aadcg
我只是展示可以使用的方法,实际上这些代码之间没有任何联系,它们只是方法的示例。是的,它可能不适用于所有情况,因为它只是一个例子。我只是不想让整个过程变得更加无关紧要和复杂。虽然,非常感谢您分享这些知识 ^_^ - FrameMuse

0

以下解决方案在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>

请注意,它是如何将第一个字符包裹在之外的中,然后将其余部分包裹在新的中。如果它只在第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/

编辑 看起来至少我的FF4有一些问题。

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

但同时,它似乎可以在没有它的情况下工作,所以我只是将其更改为

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

编辑 正如Tim所指出的,修改仅适用于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 4.0以来,Firefox仅实现了Selection.modify(),并不支持WebKit支持的所有细粒度设置。具体而言,它不支持“documentboundary”。请参见https://developer.mozilla.org/en/DOM/selection/modify。 - Tim Down
这似乎只在页面上没有其他内容时才有效。例如:http://jsfiddle.net/4NDb9/89/。尽管“Hello world”在div外部,但它被突出显示,而不是div内部的文本。 - Vincent
@Tim Down非常好的观点。我重写了其中很大一部分,现在已经摆脱了modify()方法。 - Niklas
@Vincent,这个重写的代码应该是已经排序好的,请看一下http://jsfiddle.net/niklasvh/4NDb9/93/。现在高亮函数需要第一个变量是要查找的元素。 - Niklas

0

基于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);
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接