如何使用javascript突出显示文本

IT技术 javascript highlighting
2021-01-17 14:18:14

有人可以帮助我使用可以突出显示网页上的文本的 javascript 函数吗?并且要求是 - 仅突出显示一次,而不是像我们在搜索中那样突出显示所有出现的文本。

6个回答

您可以使用 jquery高亮效果

但是,如果您对原始 javascript 代码感兴趣,请查看我得到的内容 只需将粘贴复制到 HTML 中,打开文件并单击“突出显示” - 这应该突出显示“狐狸”一词。性能方面我认为这适用于小文本和单次重复(如您指定的那样)

function highlight(text) {
  var inputText = document.getElementById("inputText");
  var innerHTML = inputText.innerHTML;
  var index = innerHTML.indexOf(text);
  if (index >= 0) { 
   innerHTML = innerHTML.substring(0,index) + "<span class='highlight'>" + innerHTML.substring(index,index+text.length) + "</span>" + innerHTML.substring(index + text.length);
   inputText.innerHTML = innerHTML;
  }
}
.highlight {
  background-color: yellow;
}
<button onclick="highlight('fox')">Highlight</button>

<div id="inputText">
  The fox went over the fence
</div>

编辑:

使用 replace

我看到这个答案很受欢迎,我想我可以补充一下。您还可以轻松使用替换

"the fox jumped over the fence".replace(/fox/,"<span>fox</span>");

或者对于多次出现(与问题无关,但在评论中被问到),您只需添加global替换正则表达式。

"the fox jumped over the other fox".replace(/fox/g,"<span>fox</span>");

希望这对感兴趣的评论者有所帮助。

将 HTML 替换为整个网页

要替换整个网页的 HTML,您应该参考innerHTML文档正文的 。

document.body.innerHTML

如果我想突出显示整个页面上出现的所有单词怎么办?@guy mograbi
2021-03-11 14:18:14
你可以替换"<span class='highlight'>"使用"<span style='color: " + color + ";'>",颜色应该是这样的var color = "#ff0000";
2021-03-17 14:18:14
非常感谢您的回复,但您能否也告诉我如何在 javascript 中指定颜色?
2021-03-20 14:18:14
这不是一个好主意,因为这将尝试突出显示 HTML 标签/属性/等。例如,在以下情况下会发生什么: <img src="fox.jpg" /> 你会得到无效的 HTML,看起来像:<img src="<span class='highlight'>fox</span>.jpg" /> 不好
2021-03-24 14:18:14
使用简单的“替换”是一个坏主意我在这里描述了原因:stackoverflow.com/a/32758672/3894981
2021-04-01 14:18:14

这里提供的解决方案非常糟糕。

  1. 您不能使用正则表达式,因为那样,您可以在 html 标签中搜索/突出显示。
  2. 您不能使用正则表达式,因为它不能与 UTF*(任何非拉丁文/英文字符)一起正常工作。
  3. 你不能只做一个innerHTML.replace,因为当字符有特殊的HTML符号时这不起作用,例如&amp;&、&lt;<、&gt;>、&auml;ä、&ouml;ö &uuml;、ü &szlig;、ß等。

你需要做什么:

循环遍历 HTML 文档,找到所有文本节点,获取textContent,获取突出显示文本的位置indexOftoLowerCase如果不区分大小写,则为可选),将所有内容附加在indexofas之前textNode,将匹配的文本附加到突出显示范围,并对文本节点的其余部分重复(高亮字符串可能在textContent字符串中出现多次)。

这是代码:

var InstantSearch = {

    "highlight": function (container, highlightText)
    {
        var internalHighlighter = function (options)
        {

            var id = {
                container: "container",
                tokens: "tokens",
                all: "all",
                token: "token",
                className: "className",
                sensitiveSearch: "sensitiveSearch"
            },
            tokens = options[id.tokens],
            allClassName = options[id.all][id.className],
            allSensitiveSearch = options[id.all][id.sensitiveSearch];


            function checkAndReplace(node, tokenArr, classNameAll, sensitiveSearchAll)
            {
                var nodeVal = node.nodeValue, parentNode = node.parentNode,
                    i, j, curToken, myToken, myClassName, mySensitiveSearch,
                    finalClassName, finalSensitiveSearch,
                    foundIndex, begin, matched, end,
                    textNode, span, isFirst;

                for (i = 0, j = tokenArr.length; i < j; i++)
                {
                    curToken = tokenArr[i];
                    myToken = curToken[id.token];
                    myClassName = curToken[id.className];
                    mySensitiveSearch = curToken[id.sensitiveSearch];

                    finalClassName = (classNameAll ? myClassName + " " + classNameAll : myClassName);

                    finalSensitiveSearch = (typeof sensitiveSearchAll !== "undefined" ? sensitiveSearchAll : mySensitiveSearch);

                    isFirst = true;
                    while (true)
                    {
                        if (finalSensitiveSearch)
                            foundIndex = nodeVal.indexOf(myToken);
                        else
                            foundIndex = nodeVal.toLowerCase().indexOf(myToken.toLowerCase());

                        if (foundIndex < 0)
                        {
                            if (isFirst)
                                break;

                            if (nodeVal)
                            {
                                textNode = document.createTextNode(nodeVal);
                                parentNode.insertBefore(textNode, node);
                            } // End if (nodeVal)

                            parentNode.removeChild(node);
                            break;
                        } // End if (foundIndex < 0)

                        isFirst = false;


                        begin = nodeVal.substring(0, foundIndex);
                        matched = nodeVal.substr(foundIndex, myToken.length);

                        if (begin)
                        {
                            textNode = document.createTextNode(begin);
                            parentNode.insertBefore(textNode, node);
                        } // End if (begin)

                        span = document.createElement("span");
                        span.className += finalClassName;
                        span.appendChild(document.createTextNode(matched));
                        parentNode.insertBefore(span, node);

                        nodeVal = nodeVal.substring(foundIndex + myToken.length);
                    } // Whend

                } // Next i 
            }; // End Function checkAndReplace 

            function iterator(p)
            {
                if (p === null) return;

                var children = Array.prototype.slice.call(p.childNodes), i, cur;

                if (children.length)
                {
                    for (i = 0; i < children.length; i++)
                    {
                        cur = children[i];
                        if (cur.nodeType === 3)
                        {
                            checkAndReplace(cur, tokens, allClassName, allSensitiveSearch);
                        }
                        else if (cur.nodeType === 1)
                        {
                            iterator(cur);
                        }
                    }
                }
            }; // End Function iterator

            iterator(options[id.container]);
        } // End Function highlighter
        ;


        internalHighlighter(
            {
                container: container
                , all:
                    {
                        className: "highlighter"
                    }
                , tokens: [
                    {
                        token: highlightText
                        , className: "highlight"
                        , sensitiveSearch: false
                    }
                ]
            }
        ); // End Call internalHighlighter 

    } // End Function highlight

};

然后你可以像这样使用它:

function TestTextHighlighting(highlightText)
{
    var container = document.getElementById("testDocument");
    InstantSearch.highlight(container, highlightText);
}

这是一个示例 HTML 文档

<!DOCTYPE html>
<html>
    <head>
        <title>Example of Text Highlight</title>
        <style type="text/css" media="screen">
            .highlight{ background: #D3E18A;}
            .light{ background-color: yellow;}
        </style>
    </head>
    <body>
        <div id="testDocument">
            This is a test
            <span> This is another test</span>
            äöüÄÖÜäöüÄÖÜ
            <span>Test123&auml;&ouml;&uuml;&Auml;&Ouml;&Uuml;</span>
        </div>
    </body>
</html>

顺便说一句,如果你在数据库中搜索LIKE
例如WHERE textField LIKE CONCAT('%', @query, '%')[你不应该这样做,你应该使用全文搜索或 Lucene],那么你可以用 \ 转义每个字符并添加一个 SQL 转义语句,这样你会发现 LIKE 表达式的特殊字符。

例如

WHERE textField LIKE CONCAT('%', @query, '%') ESCAPE '\'

而@query 的值不是'%completed%'但是'%\c\o\m\p\l\e\t\e\d%'

(经过测试,适用于 SQL-Server 和 PostgreSQL,以及所有其他支持 ESCAPE 的 RDBMS 系统)


修改后的typescript版本:

namespace SearchTools 
{


    export interface IToken
    {
        token: string;
        className: string;
        sensitiveSearch: boolean;
    }


    export class InstantSearch 
    {

        protected m_container: Node;
        protected m_defaultClassName: string;
        protected m_defaultCaseSensitivity: boolean;
        protected m_highlightTokens: IToken[];


        constructor(container: Node, tokens: IToken[], defaultClassName?: string, defaultCaseSensitivity?: boolean)
        {
            this.iterator = this.iterator.bind(this);
            this.checkAndReplace = this.checkAndReplace.bind(this);
            this.highlight = this.highlight.bind(this);
            this.highlightNode = this.highlightNode.bind(this);    

            this.m_container = container;
            this.m_defaultClassName = defaultClassName || "highlight";
            this.m_defaultCaseSensitivity = defaultCaseSensitivity || false;
            this.m_highlightTokens = tokens || [{
                token: "test",
                className: this.m_defaultClassName,
                sensitiveSearch: this.m_defaultCaseSensitivity
            }];
        }


        protected checkAndReplace(node: Node)
        {
            let nodeVal: string = node.nodeValue;
            let parentNode: Node = node.parentNode;
            let textNode: Text = null;

            for (let i = 0, j = this.m_highlightTokens.length; i < j; i++)
            {
                let curToken: IToken = this.m_highlightTokens[i];
                let textToHighlight: string = curToken.token;
                let highlightClassName: string = curToken.className || this.m_defaultClassName;
                let caseSensitive: boolean = curToken.sensitiveSearch || this.m_defaultCaseSensitivity;

                let isFirst: boolean = true;
                while (true)
                {
                    let foundIndex: number = caseSensitive ?
                        nodeVal.indexOf(textToHighlight)
                        : nodeVal.toLowerCase().indexOf(textToHighlight.toLowerCase());

                    if (foundIndex < 0)
                    {
                        if (isFirst)
                            break;

                        if (nodeVal)
                        {
                            textNode = document.createTextNode(nodeVal);
                            parentNode.insertBefore(textNode, node);
                        } // End if (nodeVal)

                        parentNode.removeChild(node);
                        break;
                    } // End if (foundIndex < 0)

                    isFirst = false;


                    let begin: string = nodeVal.substring(0, foundIndex);
                    let matched: string = nodeVal.substr(foundIndex, textToHighlight.length);

                    if (begin)
                    {
                        textNode = document.createTextNode(begin);
                        parentNode.insertBefore(textNode, node);
                    } // End if (begin)

                    let span: HTMLSpanElement = document.createElement("span");

                    if (!span.classList.contains(highlightClassName))
                        span.classList.add(highlightClassName);

                    span.appendChild(document.createTextNode(matched));
                    parentNode.insertBefore(span, node);

                    nodeVal = nodeVal.substring(foundIndex + textToHighlight.length);
                } // Whend

            } // Next i 

        } // End Sub checkAndReplace 


        protected iterator(p: Node)
        {
            if (p == null)
                return;

            let children: Node[] = Array.prototype.slice.call(p.childNodes);

            if (children.length)
            {
                for (let i = 0; i < children.length; i++)
                {
                    let cur: Node = children[i];

                    // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
                    if (cur.nodeType === Node.TEXT_NODE) 
                    {
                        this.checkAndReplace(cur);
                    }
                    else if (cur.nodeType === Node.ELEMENT_NODE) 
                    {
                        this.iterator(cur);
                    }
                } // Next i 

            } // End if (children.length) 

        } // End Sub iterator


        public highlightNode(n:Node)
        {
            this.iterator(n);
        } // End Sub highlight 


        public highlight()
        {
            this.iterator(this.m_container);
        } // End Sub highlight 


    } // End Class InstantSearch 


} // End Namespace SearchTools 

用法:

let searchText = document.getElementById("txtSearchText");
let searchContainer = document.body; // document.getElementById("someTable");
let highlighter = new SearchTools.InstantSearch(searchContainer, [
    {
        token: "this is the text to highlight" // searchText.value,
        className: "highlight", // this is the individual highlight class
        sensitiveSearch: false
    }
]);


// highlighter.highlight(); // this would highlight in the entire table
// foreach tr - for each td2 
highlighter.highlightNode(td2); // this highlights in the second column of table
@StefanSteiger 1. 然后你应该更正你的决定关系,因为它说我们根本不能用 RegExp 搜索,这不是真的 2. 它不使用 jQuery.each。什么让你有那个想法?3. 这不是真的,至少在 Firefox 中是这样。&auml;eg 将被转换为实际字符,即使使用innerHTML.
2021-03-10 14:18:14
很好的答案.. 该方法看起来有点矫枉过正,但简洁!肯定会兴趣做与方法的速度测试在我的情况下,结果是懒加载到DOM(因为CAN有成千上万的结果),好奇,如果这种方法会增加高延迟的延迟加载。
2021-03-15 14:18:14
对不起,但你的论点都不是真的。1. 你绝对可以使用正则表达式,你只是不应该在 HTML 值中搜索,而是在元素的文本值中搜索。2. 你绝对可以使用带有变音符号的 RegExp,就像在mark.js 中实现的那样3. HTML 符号会转换为浏览器 DOM 中的实际字符,所以你也绝对使用它们!
2021-03-26 14:18:14
嗨@StefanSteiger 实际上,我正在使用您的解决方案。这个是完美的。但是有一些问题,例如,如果 II 有一个 P 其中有两个跨度,一个跨度具有像 Diploma MSBTE 这样的数据,第二个跨度具有数据 2012 。现在,如果我要突出显示的字符串是 Diploma MSBTE 2012 ,则整个字符串然后我检查这不起作用,如果要匹配的所有内容都存在于一个 span 中,则它可以工作,但是如果文本内容在 diff 标签中,则这是行不通的。你能谈谈这件事吗?
2021-03-26 14:18:14
@朱尔莫特;To 1:这意味着您需要遍历每个元素,这正是我所做的。除非你不关心丢失格式,在这种情况下你可以在 document.body.innerText 中搜索,这会很慢。3. 不是在 DOM 中,而是在文本元素的 innerText 或 textContent 属性中。这又意味着您需要遍历文本元素;不能用 regEx AFAIK 来完成。2:不知道mark.js,但我会避免使用jQuery.each 的所有内容,因为那太慢了。
2021-04-05 14:18:14

为什么使用自制的高亮功能是个坏主意

从头开始构建自己的突出显示功能可能是一个坏主意的原因是因为您肯定会遇到其他人已经解决的问题。挑战:

  • 您需要删除带有 HTML 元素的文本节点以突出显示您的匹配项,而不会破坏 DOM 事件并一遍又一遍地触发 DOM 重新生成(例如,就是这种情况innerHTML
  • 如果要删除突出显示的元素,则必须删除 HTML 元素及其内容,并且还必须组合拆分的文本节点以进行进一步搜索。这是必要的,因为每个荧光笔插件都会在文本节点内搜索匹配项,并且如果您的关键字将被拆分为多个文本节点,它们将不会被找到。
  • 您还需要构建测试以确保您的插件在您没有考虑过的情况下工作。我说的是跨浏览器测试!

听起来很复杂?如果您想要一些功能,例如从突出显示、变音符号映射、同义词映射、iframe 内搜索、分离词搜索等中忽略某些元素,这将变得越来越复杂。

使用现有插件

使用现有的、实现良好的插件时,您不必担心上述命名的事情。Sitepoint 上的文章10 jQuery 文本荧光笔插件比较了流行的荧光笔插件。

看看mark.js

mark.js就是这样一个用纯 JavaScript 编写的插件,但也可以作为 jQuery 插件使用。它的开发目的是提供比其他插件更多的机会,可以选择:

  • 单独搜索关键字而不是完整的术语
  • 映射变音符号(例如,如果“justo”也应该匹配“justò”)
  • 忽略自定义元素内的匹配项
  • 使用自定义高亮元素
  • 使用自定义突出显示类
  • 映射自定义同义词
  • 也在 iframe 内搜索
  • 收到未找到的条款

演示

或者你可以看到这个 fiddle

用法示例

// Highlight "keyword" in the specified context
$(".context").mark("keyword");

// Highlight the custom regular expression in the specified context
$(".context").markRegExp(/Lorem/gmi);

它是在 GitHub 上免费开发的开源软件(项目参考)。

@Roy 我牢记这一点。好消息,从 v6.0.0 开始,mark.js放弃了对 jQuery 的依赖,现在可以选择将其用作 jQuery 插件。
2021-03-16 14:18:14
您指的是@StefanSteiger 的哪些方面?如果没有这些信息,就不能说第一点。但是,第二条注释是错误的,mark.js 可以使用acrossElements选项查找标签之间的匹配项。以及第三条评论;与它提供的功能相比,mark.js 并不大。不,将来不太可能出现问题,因为 mark.js 已经过测试,例如启动 Chrome 30 和所有具有跨浏览器单元测试的新版本,并且即将推出的版本从来没有任何问题。
2021-03-24 14:18:14
都是真的,除了:第一点是不可能的,因为你不能得到注册的事件处理程序,即使你能,你也不能设置匿名函数......第二:mark.js 也没有在两个标签之间找到文本,例如<span>s</span>ed 找不到 sed... 第三:每当出现您尚未测试的浏览器(包括新版本)时,它可能会崩溃。无论您编写多少测试,这始终是正确的。在 17kb 时,标记对于它的作用来说太大了。
2021-03-28 14:18:14
仅突出显示文本不足以成为我包含 jQuery 的充分理由。
2021-04-03 14:18:14
@dude:第一段之后的三点。啊,好吧,在我查看的演示中缺少该选项。在这种情况下,它可能有一定的意义。但是,我还是觉得它太大了。
2021-04-04 14:18:14
function stylizeHighlightedString() {

    var text = window.getSelection();

    // For diagnostics
    var start = text.anchorOffset;
    var end = text.focusOffset - text.anchorOffset;

    range = window.getSelection().getRangeAt(0);

    var selectionContents = range.extractContents();
    var span = document.createElement("span");

    span.appendChild(selectionContents);

    span.style.backgroundColor = "yellow";
    span.style.color = "black";

    range.insertNode(span);
}
不应该有一种方法可以在不创建另一个节点的情况下选择文本吗?
2021-03-15 14:18:14
@user191433 问题不仅仅是选择文本,还包括应用样式。为此,您需要一个节点。
2021-03-15 14:18:14
PS Mohit 在stackoverflow.com/questions/7991474/...的回答是此代码的更简化变体。(例如,省略此处仅具有诊断功能/非功能性的开始和结束变量。)
2021-03-21 14:18:14
JavaScriptspan.style.backgroundColor = "yellow";转换为 CSS 的提醒/提示style="background-color: yellow;"——驼峰式命名法和虚线符号之间的细微差别一开始让我感到困惑。
2021-03-31 14:18:14
Mohit,欢迎来到 SO。代码的一些描述会很好!
2021-04-07 14:18:14

这是我的正则表达式纯 JavaScript 解决方案:

function highlight(text) {
    document.body.innerHTML = document.body.innerHTML.replace(
        new RegExp(text + '(?!([^<]+)?<)', 'gi'),
        '<b style="background-color:#ff0;font-size:100%">$&</b>'
    );
}
按要求修改。
2021-03-10 14:18:14
如果文本末尾有>字符,则不会替换文本修改正则表达式(?!([^<]+)?<)以使其正常工作。
2021-03-16 14:18:14
完美的!这对我来说是最好的
2021-03-18 14:18:14
当我试图突出显示的文本块包含 HTML 标签时,这对我来说非常有用。
2021-04-04 14:18:14
您还可以调整函数以通过正则表达式管道符号接受多个单词,例如 one|two|three
2021-04-06 14:18:14