javascript用户选择突出显示

IT技术 javascript highlight
2021-01-31 16:01:22

我试图找到一种使用 javascript 的方法来突出显示用户在单击某个奇怪的突出显示按钮时选择的文本(如<span style="background-color:yellow">突出显示的文本</span>)。它只需要与 WebKit 或 Firefox 一起使用,但似乎几乎不可能,因为它必须在以下情况下工作:

<p>this is text</p>
<p>I eat food</p>

当用户在浏览器中从“是文本”到“我吃”中选择时(不能只是在那里放一个跨度)。

这个案例:

<span><span>this is text</span>middle text<span>this is text</span></span>

当用户在浏览器中从“是文本”选择到“这是”时(即使您可以将高亮跨度包裹在选择中的每个元素周围,我还是希望看到您尝试突出显示中间文本)。

这个问题似乎在任何地方都没有解决,坦率地说,我怀疑这是可能的。

如果您可以将您从选择中获得的范围作为一个带有 html 的字符串来获取,该字符串可以被解析然后替换,但据我所知,您无法获得范围的原始 html ..可怜。

6个回答

这个答案对你来说可能晚了几年,但我遇到了类似的问题并想在这里记录它,因为它是谷歌上的第一次命中。

重申一下,问题是您只想从用户选择中捕获 Range 对象并用样式化的 div 将其包围,如下所示:

function highlightSelection() {
    var userSelection = window.getSelection().getRangeAt(0);
    highlightRange(userSelection);

}

function highlightRange(range) {
    var newNode = document.createElement("div");
    newNode.setAttribute(
       "style",
       "background-color: yellow; display: inline;"
    );
    range.surroundContents(newNode);
}

但正如原始父母所说,这是不安全的。如果选择不跨越元素边界,它将起作用,但如果用户选择创建的范围是跨越 HTML 标签边界的不安全范围,它将抛出 DOM 错误。


解决方案是生成一组较小的 Range 对象,其中没有一个单独跨越元素屏障,而是共同覆盖用户选择的 Range。这些安全范围中的每一个都可以如上突出显示。

function getSafeRanges(dangerous) {
    var a = dangerous.commonAncestorContainer;
    // Starts -- Work inward from the start, selecting the largest safe range
    var s = new Array(0), rs = new Array(0);
    if (dangerous.startContainer != a)
        for(var i = dangerous.startContainer; i != a; i = i.parentNode)
            s.push(i)
    ;
    if (0 < s.length) for(var i = 0; i < s.length; i++) {
        var xs = document.createRange();
        if (i) {
            xs.setStartAfter(s[i-1]);
            xs.setEndAfter(s[i].lastChild);
        }
        else {
            xs.setStart(s[i], dangerous.startOffset);
            xs.setEndAfter(
                (s[i].nodeType == Node.TEXT_NODE)
                ? s[i] : s[i].lastChild
            );
        }
        rs.push(xs);
    }

    // Ends -- basically the same code reversed
    var e = new Array(0), re = new Array(0);
    if (dangerous.endContainer != a)
        for(var i = dangerous.endContainer; i != a; i = i.parentNode)
            e.push(i)
    ;
    if (0 < e.length) for(var i = 0; i < e.length; i++) {
        var xe = document.createRange();
        if (i) {
            xe.setStartBefore(e[i].firstChild);
            xe.setEndBefore(e[i-1]);
        }
        else {
            xe.setStartBefore(
                (e[i].nodeType == Node.TEXT_NODE)
                ? e[i] : e[i].firstChild
            );
            xe.setEnd(e[i], dangerous.endOffset);
        }
        re.unshift(xe);
    }

    // Middle -- the uncaptured middle
    if ((0 < s.length) && (0 < e.length)) {
        var xm = document.createRange();
        xm.setStartAfter(s[s.length - 1]);
        xm.setEndBefore(e[e.length - 1]);
    }
    else {
        return [dangerous];
    }

    // Concat
    rs.push(xm);
    response = rs.concat(re);    

    // Send to Console
    return response;
}

然后可以(似乎)使用以下修改后的代码突出显示用户选择:

function highlightSelection() {
    var userSelection = window.getSelection().getRangeAt(0);
    var safeRanges = getSafeRanges(userSelection);
    for (var i = 0; i < safeRanges.length; i++) {
        highlightRange(safeRanges[i]);
    }
}

请注意,您可能需要一些更漂亮的 CSS 来使用户可以将许多不同的元素放在一起看起来很漂亮。我希望这最终能帮助互联网上其他一些疲惫的灵魂!

我希望我能为你在这方面的辛勤工作投两票。
2021-03-17 16:01:22
这在大多数情况下效果很好,但在reddit.com/r/Tinder/comments/gyb7vc/ 上将直接子评论突出显示到父评论时失败了 ......“dm me...”仍未突出显示
2021-03-17 16:01:22
干得好,但是……这个解决方案的局限性是将一个整体分成几个部分,然后将这些部分分开。在按照用户期望(已创建)使用它们时,您还需要一个通用的类或属性查找来重新统一它们。
2021-03-20 16:01:22
非常感谢您为我们所有人解决了这个问题。这很好用。我想强调的一点是该函数返回一些空范围,从而创建了一些不必要的跨度元素。我在我的 highlightRange 函数中添加了一个额外的条件来避免这个问题。此条件跳过任何空范围。谢谢,如果 (range.toString() !== "" && range.toString().match(/\w+/g) !== null) { }
2021-03-25 16:01:22
感谢您的努力,这是一项伟大的工作,但我有一个问题,我想取消突出显示突出显示的文本或其中的一些,可以这样做吗?请回答我,我在等你,谢谢。
2021-04-04 16:01:22

好吧,您可以使用 DOM 操作来做到这一点。这适用于 Firefox:

var selection = window.getSelection();
var range = selection.getRangeAt(0);
var newNode = document.createElement("span");
newNode.setAttribute("style", "background-color: pink;");
range.surroundContents(newNode); 

似乎也适用于当前版本的 Safari。请参阅https://developer.mozilla.org/en/DOM/range.surroundContentshttp://www.w3.org/TR/2000/REC-DOM-Level-2-Traversal-Range-20001113/ranges.html

如果选择跨越元素边界(例如,如果它跨越多个段落),这将不起作用。
2021-03-30 16:01:22

这是我第一次在这里发帖,但是查看您的答案,这样的事情行不通?我在这里有一个示例:http : //henriquedonati.com/projects/Extension/extension.html

function highlightSelection() {
    var userSelection = window.getSelection();
    for(var i = 0; i < userSelection.rangeCount; i++) {
        highlightRange(userSelection.getRangeAt(i));
    }

}

function highlightRange(range) {
    var newNode = document.createElement("span");
    newNode.setAttribute(
       "style",
       "background-color: yellow; display: inline;"
    );
    range.surroundContents(newNode);
}
@henrique 是否有机会将突出显示的内容存储到 Db 中?所以当你回来时它仍然突出显示?
2021-03-24 16:01:22
尝试跨多个元素突出显示时,这不起作用
2021-04-11 16:01:22

这是突出显示和取消突出显示文本的完整代码

<!DOCTYPE html>
    <html>
        <head>
            <style type="text/css">
                .highlight
                {
                    background-color: yellow;
                }
                #test-text::-moz-selection { /* Code for Firefox */

                    background: yellow;
                }

                #test-text::selection {

                    background: yellow;
                }

            </style>
        </head>

        <body>
            <div id="div1" style="border: 1px solid #000;">
                <div id="test-text">
                    <h1> Hello How are you </h1>
                    <p >
                        Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
                    </p>
                </div>
            </div>
            <br />

        </body>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
            <script type="text/javascript">
                mouseXPosition = 0;
                $(document).ready(function () {

                    $("#test-text").mousedown(function (e1) {
                        mouseXPosition = e1.pageX;//register the mouse down position
                    });

                    $("#test-text").mouseup(function (e2) {
                        var highlighted = false;
                        var selection = window.getSelection();
                        var selectedText = selection.toString();
                        var startPoint = window.getSelection().getRangeAt(0).startOffset;
                        var endPoint = window.getSelection().getRangeAt(0).endOffset;
                        var anchorTag = selection.anchorNode.parentNode;
                        var focusTag = selection.focusNode.parentNode;
                        if ((e2.pageX - mouseXPosition) < 0) {
                            focusTag = selection.anchorNode.parentNode;
                            anchorTag = selection.focusNode.parentNode;
                        }
                        if (selectedText.length === (endPoint - startPoint)) {
                            highlighted = true;

                            if (anchorTag.className !== "highlight") {
                                highlightSelection();
                            } else {
                                var afterText = selectedText + "<span class = 'highlight'>" + anchorTag.innerHTML.substr(endPoint) + "</span>";
                                anchorTag.innerHTML = anchorTag.innerHTML.substr(0, startPoint);
                                anchorTag.insertAdjacentHTML('afterend', afterText);
                            }

                        }else{
                            if(anchorTag.className !== "highlight" && focusTag.className !== "highlight"){
                                highlightSelection();  
                                highlighted = true;
                            }

                        }


                        if (anchorTag.className === "highlight" && focusTag.className === 'highlight' && !highlighted) {
                            highlighted = true;

                            var afterHtml = anchorTag.innerHTML.substr(startPoint);
                            var outerHtml = selectedText.substr(afterHtml.length, selectedText.length - endPoint - afterHtml.length);
                            var anchorInnerhtml = anchorTag.innerHTML.substr(0, startPoint);
                            var focusInnerHtml = focusTag.innerHTML.substr(endPoint);
                            var focusBeforeHtml = focusTag.innerHTML.substr(0, endPoint);
                            selection.deleteFromDocument();
                            anchorTag.innerHTML = anchorInnerhtml;
                            focusTag.innerHTml = focusInnerHtml;
                            var anchorafterHtml = afterHtml + outerHtml + focusBeforeHtml;
                            anchorTag.insertAdjacentHTML('afterend', anchorafterHtml);


                        }

                        if (anchorTag.className === "highlight" && !highlighted) {
                            highlighted = true;
                            var Innerhtml = anchorTag.innerHTML.substr(0, startPoint);
                            var afterHtml = anchorTag.innerHTML.substr(startPoint);
                            var outerHtml = selectedText.substr(afterHtml.length, selectedText.length);
                            selection.deleteFromDocument();
                            anchorTag.innerHTML = Innerhtml;
                            anchorTag.insertAdjacentHTML('afterend', afterHtml + outerHtml);
                         }

                        if (focusTag.className === 'highlight' && !highlighted) {
                            highlighted = true;
                            var beforeHtml = focusTag.innerHTML.substr(0, endPoint);
                            var outerHtml = selectedText.substr(0, selectedText.length - beforeHtml.length);
                            selection.deleteFromDocument();
                            focusTag.innerHTml = focusTag.innerHTML.substr(endPoint);
                            outerHtml += beforeHtml;
                            focusTag.insertAdjacentHTML('beforebegin', outerHtml );


                        }
                        if (!highlighted) {
                            highlightSelection();
                        }
                        $('.highlight').each(function(){
                            if($(this).html() == ''){
                                $(this).remove();
                            }
                        });
                        selection.removeAllRanges();
                    });
                });

                function highlightSelection() {
                    var selection;

                    //Get the selected stuff
                    if (window.getSelection)
                        selection = window.getSelection();
                    else if (typeof document.selection != "undefined")
                        selection = document.selection;

                    //Get a the selected content, in a range object
                    var range = selection.getRangeAt(0);

                    //If the range spans some text, and inside a tag, set its css class.
                    if (range && !selection.isCollapsed) {
                        if (selection.anchorNode.parentNode == selection.focusNode.parentNode) {
                            var span = document.createElement('span');
                            span.className = 'highlight';
                            span.textContent = selection.toString();
                            selection.deleteFromDocument();
                            range.insertNode(span);
    //                        range.surroundContents(span);
                        }
                    }
                }

            </script>
    </html>

https://jsfiddle.net/Bilalchk123/1o4j0w2v/

不适用于跨多个段落的文本选择。
2021-03-18 16:01:22

    function load(){
      window.document.designMode = "On";
      //run this in a button, will highlight selected text
      window.document.execCommand("hiliteColor", false, "#768");
    }
   
    <html>
    <head>

    </head>
    <body contentEditable="true" onload="load()">
      this is text
    </body>
    </html>

虽然帖子很旧,但我仍然认为这是我现在能找到的最佳解决方案
2021-03-21 16:01:22
2021-03-22 16:01:22
这很容易成为最好的主意。很好的答案,但designMode之后再次关闭确实是一个想法
2021-04-05 16:01:22