高亮:单关键词、多关键词、多组多关键词,从简单到复杂实现满足多方面需求的页面关键词高亮

本文详述了从简单到复杂的网页关键词高亮方法,包括字符串处理、DOM节点高亮,以及处理多个关键词和分组高亮的策略。文章讨论了各种方案的优缺点,提供了实用的代码示例,并提醒读者根据实际需求选择合适的高亮方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

我的前言都是良心话,还是姑且看一下吧:

别人一看这个标题,心想,“怎么又是一个老到掉牙的需求,网上一搜一大堆解决方案啦!”。没错!这个需求实在老土得不能再老土了,我真不想写这样一种需求的文章,无奈!无奈!

现实的情况是我想这么旧的需求网上资料一大把一大把,虽然我知道网上资料可能有坑,但是我总不信找不到一篇好的全面的资料的。然而现实又是一次啪啪的打脸,我是没找到,而且很多资料都是一个拷贝一个,质量参差不齐,想必很多找资料的人也深有体会

为了让别人不再走我的老路,特此写了此篇文章和大家分享

我不能说我写的文章质量杠杠滴。但是我会在这里,客观地指出我方案的缺点,不忽悠别人。

写该文章的目的只有两个:

  • 让缺乏这方面经验的人能够信手拈来一个较为全面的方案,对自己对公司相对负责,别qa提很多bug啦(我也是这么过来,纯粹想帮助小白)
  • 让更有能力的人,补充完善我的方案,或者借鉴我的经验,造出更强更全面的方案,当然,我也希望能让我学习一下就最好了。

目录

需求

还是说一下这到底是个什么需求吧。想必大家都试过在一个网页上,按下“ctrl + F”,然后输入关键词来找到页面上匹配的。

没错,就是这么一种类似的简单的需求。但是这么一个简单的需求,却暗藏杀机。这种需求(非就是这种形式)用文字明确描述一下:

页面上有一个按钮,或者一个输入框,进行操作时,针对某些关键词(任意字符串都可以,除换行符),在页面上进行高亮显示,注意此页面内容是有任何可能的网页

描述很抽象?那我就干脆定一个明确的需求:

实现一个插件,在任何别人的网页上高亮想要的关键词。

这里不说实现插件的本身,只描述高亮的方案。

接下来我将循序渐进地从一个个简单的需求到复杂的需求,告诉你这里边到底需要考虑什么。

一个最简单的方案

第一反应,想必大家都觉得用字符串来处理了吧,在字符串里找到匹配的文字,然后用一个html元素包围着,加上类名,css高亮!对吧,一切都感觉如此自然顺利~
我先不说这方案的鸡肋之处,光说落实到实际处理的时候,需要做些什么。

超简单处理

// js
var keyword = '关键词1';    // 假设这里的关键词为“关键词1”
var bodyContent = document.body.innerHTMl;  // 获取页面内容
var contentArray = bodyContent.split(keyword);
document.body.innerHTMl = contentArray.join('<span>' + keyword + '</span>');
// css
.highlight {
   
   
    background: yellow;
    color: red;
}

简单处理二

这里相对上面还没那么简单,至于为啥我说这个方案的原因是,在后面讲的复杂方案里,需要用到这些知识。

关键词的处理

上面说需求的时候讲过,是针对任意关键词(除换行符)进行的高亮,如果更简单点,说只针对英文或中文,那么可以直接匹配了,如str.match('keyword');。但是我们是要做一个通用的功能的话,还是要特别针对一些转义字符做处理的,不然如关键词为?keyword',用'?keyword'.match('?keyword');,会报错。

我找了各种特殊字符进行了测试,最终形成了以下方法针对各种特殊字符进行了处理。

// string为原本要进行匹配的关键词
// 结果transformString为进行处理后的要用来进行匹配的关键词
var transformString = string.replace(/[.[*?+^$|()/]|\]|\\/g, '\\$&');

看不懂?想深究,可以看一下这边文章: 这是一篇男女老少入门精通咸宜的正则笔记
反正这里的意思就是把各种转义字符变成普通字符,以便可以匹配出来。

匹配高亮
// js部分
var bodyContent = document.body.innerHTMl;  // 获取页面内容
var pattern = new RegExp(transformString, 'g'); // 生成正则表达式
// 匹配关键词并替换
document.body.innerHTMl = bodyContent.replace(pattern, '<span class="highlight">$&</span>');
// css
.highlight {
   
   
    background: yellow;
    color: red;
}

缺点

把页面的内容当成一个字符串来处理,存在很多预想不到的情况。

  • script标签内有匹配文本,添加高亮html元素后,导致脚本报错。
  • 标签属性(特别是自定义属性,如dats-*)存在匹配文本,添加高亮后,破坏原有功能
  • 刚好匹配文本跟某内联样式文本匹配上,如<div style="width: 300px;"></div>,关键词刚好为width,这时候就尴尬了,替换结果为<div style="<span class="highlight">width</span>: 300px;"><div。这样就破坏了原本的样式了。
  • 还有一种情况,如<div>右</div>,关键词为>右,这时候替换结果为<div<span class="highlight">>右</span></div>,同样破坏了结构。
  • 以及还有很多很多情况,以上仅是我罗列的一些,未知的情况实在太多了

利用DOM节点高亮(基础版)

既然字符串的方法太多弊端了,那只能舍弃掉了,另寻他法。
这节内容就考大家的基础知识扎不扎实了

页面的内容有一个DOM树构成,其中有一种节点叫文本节点,就是我们页面上所能看到的文字(大部分,图片等除外),那么我们只要在这些文本节点里找到是否有我们匹配的关键词,匹配上的就对该文本节点做改造就好了。

封装一个函数做上述处理(注释中一个个解释), ①内容为上述讲过:


// ①
// string为原本要进行匹配的关键词
// 结果transformString为进行处理后的要用来进行匹配的关键词
var transformString = string.replace(/[.[*?+^$|()/]|\]|\\/g, '\\$&');
var pattern = new RegExp(transformString, 'i'); // 这里不区分大小写

/**
 * ② 高亮关键字
 * @param node - 节点
 * @param pattern - 用于匹配的正则表达式,就是把上面的pattern传进来
 */
function highlightKeyword(node, pattern) {
   
   
    // nodeType等于3表示是文本节点
    if (node.nodeType === 3) {
   
   
        // node.data为文本节点的文本内容
        var matchResult = node.data.match(pattern);
        // 有匹配上的话
        if (matchResult) {
   
   
            // 创建一个span节点,用来包裹住匹配到的关键词内容
            var highlightEl = document.createElement('span');
            // 不用类名来控制高亮,用自定义属性data-*来标识,
            // 比用类名更减少概率与原本内容重名,避免样式覆盖
            highlightEl.dataset.highlight = 'yes';
            // splitText相关知识下面再说,可以先去理解了再回来这里看
            // 从匹配到的初始位置开始截断到原本节点末尾,产生新的文本节点
            var matchNode = node.splitText(matchResult.index);
            // 从新的文本节点中再次截断,按照匹配到的关键词的长度开始截断,
            // 此时0-length之间的文本作为matchNode的文本内容
            matchNode.splitText(matchResult[0].length);
            // 对matchNode这个文本节点的内容(即匹配到的关键词内容)创建出一个新的文本节点出来
            var highlightTextNode = document.createTextNode(matchNode.data);
            // 插入到创建的span节点中
            highlightEl.appendChild(highlightTextNode);
            // 把原本matchNode这个节点替换成用于标记高亮的span节点
            matchNode.parentNode.replaceChild(highlightEl, matchNode);
        }
    } 
    // 如果是元素节点 且 不是script、style元素 且 不是已经标记过高亮的元素
    // 至于要区分什么元素里的内容不是你想要高亮的,可自己补充,这里的script和style是最基础的了
    // 不是已经标记过高亮的元素作为条件之一的理由是,避免进入死循环,一直往里套span标签
    else if ((node.nodeType === 1)  && !(/script|style/.test(node.tagName.toLowerCase())) && (node.dataset.highlight !== 'yes')) {
   
   
        // 遍历该节点的所有子孙节点,找出文本节点进行高亮标记
        var childNodes = node.childNodes;
        for (var i = 0; i < childNodes.length; i++) {
   
   
            highlightKeyword(childNodes[i], pattern);
        }
    }
}

注意这里的pattern参数,就是上述关键词处理后的正则表达式

/** css高亮样式设置 **/
[data-highlight=yes] {
   
   
    display: inline-block;
    background: #32a1ff;
}

这里用的是属性选择器

splitText

这个方法针对文本节点使用,IE8+都能使用。它的作用是能把文本节点按照指定位置分离出另一个文本节点,作为其兄弟节点,即它们是同父同母哦~ 看图理解更清楚:

虽然这个div原本是只有一个文本节点,后来变成了两个,但是对实际页面效果,看起来还是一样的。

语法
/**
 * @param offset 指定的偏移量,值为从0开始到字符串长度的整数
 * @returns replacementNode - 截出的新文本节点,不含offset处文本
 */
replacementNode = textnode.splitText(offset)
例子
<body>
  <p 
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值