26

我试图找到一种使用 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。遗憾。

4

8 回答 8

71

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

重申一下,问题在于您只想从用户选择中捕获 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 来使用户可以将许多不同的元素放在一起看起来不错。我希望最终这能帮助互联网上其他一些疲惫的灵魂!

于 2012-10-10T15:59:07.950 回答
11

好吧,您可以使用 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

于 2008-11-25T13:53:30.620 回答
9

这是我第一次在这里发帖,但是看看你的答案,这样的东西不会有用吗?我在这里有一个样本: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);
}
于 2016-07-31T08:35:42.520 回答
8

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

<!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/

于 2017-08-11T07:29:15.053 回答
6

    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>

于 2008-11-20T23:02:24.490 回答
3

我刚刚发布了一个包,它是 texthighlighter(一个已弃用的库)的 typescript 端口。只是将其转换为 typescript 就发现了一些错误,并使其在未来更容易工作。结帐https://www.npmjs.com/package/@funktechno/texthighlighter。这没有依赖关系,并允许突出显示用户选择、合并突出显示、删除突出显示、序列化和反序列化(从数据应用)突出显示。

请注意,您将需要使用 javascript mouseup 事件来正确触发它。

import { doHighlight, deserializeHighlights, serializeHighlights, removeHighlights, optionsImpl } from "@/../node_modules/@funktechno/texthighlighter/lib/index";
const domEle = document.getElementById("sandbox");
const options: optionsImpl = {};
if (this.color) options.color = this.color;
if (domEle) doHighlight(domEle, true, options);

这就是我在 vue ts 项目中触发它的方式

<div
     id="sandbox"
     @mouseup="runHighlight($event)"
>text to highlight</div>
于 2020-07-12T03:01:26.130 回答
2

我今天遇到了同样的问题,突出显示了多个标签上的选定标签。解决方案:

  1. 找到一种方法来提取所选部分以及 HTML 标记
  2. 用 span 元素包装提取的部分并将其放回 DOM 中

请参阅下面的代码,以获得进一步的说明。

function getRangeObject(selectionObject){
    try{ 
        if(selectionObject.getRangeAt)
            return selectionObject.getRangeAt(0);
    }
    catch(ex){
        console.log(ex);
    }
}
document.onmousedown = function(e){
    var text;
    if (window.getSelection) {
        /* get the Selection object */
        userSelection = window.getSelection()

        /* get the innerText (without the tags) */ 
        text = userSelection.toString();

        /* Creating Range object based on the userSelection object */
        var rangeObject = getRangeObject(userSelection);

        /* 
           This extracts the contents from the DOM literally, inclusive of the tags. 
           The content extracted also disappears from the DOM 
        */
        contents = rangeObject.extractContents(); 

        var span = document.createElement("span");
        span.className = "highlight";
        span.appendChild(contents);

        /* Insert your new span element in the same position from where the selected text was extracted */
        rangeObject.insertNode(span);

    } else if (document.selection && document.selection.type != "Control") {
            text = document.selection.createRange().text;
    }
};
于 2015-12-31T09:39:04.833 回答
0

由于 HTML 使用<mark>元素作为突出显示的文本,也许使用这个节点很容易,而不是使用你自己的 css,更干净的代码:

function highlightRange(range) {
   var newNode = document.createElement('mark');
   range.surroundContents(newNode);
}
// original select range function
function highlight() {
    var userSelection = window.getSelection();
    for(var i = 0; i < userSelection.rangeCount; i++) {
        highlightRange(userSelection.getRangeAt(i));
    }
}

于 2021-09-06T06:36:40.757 回答