前端高亮功能实现复盘
“高亮”功能,个人觉得没必要再解释什么了。作为一名程序猿,天天都会接触高亮:写代码时的语法高亮;使用搜索引擎时的搜索结果高亮。作为一名前端,如果你做过与搜索相关的功能,那么你很有可能就实现过高亮,本文也主要从前端的角度复盘一下“高亮”功能实现的关键知识点。
高亮实现思路
对用户的输入进行分词得到关键词,根据关键词搜索得到搜索结果。再次使用关键词从搜索结果中找到匹配,对匹配加上高亮样式,即完成高亮。细分这个过程,会有以下细节点:
对用户输入分词得到关键词
根据关键词得到搜索结果
关键词匹配
对匹配使用高亮样式
前两步一般在后台完成,后两步才是我们前端的工作,下面通过具体例子来实际演练一下。
普通文本高亮
比如我们有这样的文本:“我是中国人,我爱中华人民共和国,中华人民共和国万岁!”,这时我们的关键词是“我”,即要高亮文本中所有的我。代码实现完整如下:
普通文本高亮 原文本:我是中国人,我爱中华人民共和国,中华人民共和国万岁! 关键词: 我 结果如下:
上面的demo运行效果如下
上面的例子虽然很简单,但已经完整实现了高亮功能,说明了高亮的实现原理。在实际应用中,我们的关键词多半不会是一个简简单单的“我”,而是一个List,下面我们将关键词改成:我,中华。只需稍加修改,就可以实现同时对“我”,“中华”两个关键词高亮
原文本:我是中国人,我爱中华人民共和国,中华人民共和国万岁! 关键词: 我,中华 结果如下:
运行效果:
上面的代码似乎已经完美了。但如果我们将关键词改成:我,中华,中华人民共和国。再次运行看结果,会发现“中华人民共和国”这个关键词没有被高亮,而“中华”高亮了,但这并不是我们想要的结果。
注意:如果一个关键词包含另一个关键词,要优先高亮长度较长的词才对。要修复这个Bug也很简单,只需要将字数多的关键词放到关键词数组的最前面即可
原文本:我是中国人,我爱中华人民共和国,中华人民共和国万岁! 关键词: 我,中华,中华人民共和国 结果如下:
效果如下:
富文本高亮
说完了普通文本的高亮,我们来说说富文本的高亮。所谓富文本,即待高亮的字符串不再是单独的文本,而是html代码。如果你需要富文本编辑器,可以考虑百度的UEditor,富文本编辑器生成的代码就是可以直接插入到页面的html串。
// 普通文本我是中国人,我爱中华人民共和国,中华人民共和国万岁!// 富文本,即html串我是 中国人,我爱中华人民共和国,中华人民共和国万岁!
富文本高亮未高亮
我是 中国人,我爱中华人民共和国,中华人民共和国万岁!高亮效果
这里的高亮实现直接使用了前面普通文本高亮,似乎也能正常工作。但前提是这个富文本串太简单了。我们现在考虑这样一种情况,如果富文本串中的元素属性具有完全匹配关键词的内容,会发生什么呢?
// 待高亮富文本串我是 中国人,我爱中华人民共和国,中华人民共和国万岁!
对于上面的字符串,直接使用前面的高亮逻辑,得到的字符串是:
中华人民共和国"> 我是 中国人, 我爱 中华人民共和国, 中华人民共和国万岁!
显然,第一个div属性中的“中华人民共和国”不应该被匹配到。如果直接匹配会破坏原来的富文本串,使之不再是一个有效的html串。这是富文本高亮的最大难题,那这个难题怎么解决呢?
当然,第一反应可能就是改正则。但根据个人经验,对于一个模式内包含自己时,正则几乎无能为力。比如下面我们常见的字符串结构:
// 这是常见的less语法。现在如果要求使用正则将最未的选择器名称加上“$”,大家可以试一下,能否做到// 原串@media (max-width: 600px) { .header { .hd { width: 100px; } .bd { background-color: #fff; } }}// 要求结果@media (max-width: 600px) { .header { .hd$ { // 加上$ width: 100px; } .bd$ { background-color: #fff; } }}
对于富文本串,它也可能是模式自包含的字符串类型,比如
// 这是一个标准得不能再标准的html串了hello world!
如果要使用正则去匹配上面字符串的hello内容,这个正则应该怎么写?先提醒一下,真正的富文本串要比这复杂太多太多,真实的用户输入也比这复杂太多太多。我当时做富文本高亮时,遇到这个问题也是一时找不到办法。某天,突然灵光一闪,不要硬碰硬啊,曲线救国嘛(这其实应该早就想到,只是走入正则的死胡同了):如果能够先把富文本串中的html标签去掉,剩下的不就是普通文本了吗?匹配普通文本简直是不要太简单了哦。匹配完成后,再把去掉的html还原,就完成高亮匹配了。不过这里有几个难题:
如何去掉html标签。别笑,这真心难,不信你试下
占位符一定要够特殊,不能在关键词匹配时被破坏
如何还原html串
根据上面的思路,完成了新一版的高亮逻辑,代码如下:
富文本高亮,关键词:['中华人民共和国', '中华', '我'];未高亮
我是 中国人,我爱中华人民共和国,中华人民共和国万岁!高亮效果
上面的hightlightKeyword函数已经能够满足大多数情况下富文本高亮,博主曾经负责的一个项目,使用这个高亮逻辑安全运行1年多,也没出现大问题。但其实,上面的高亮逻辑还是有bug,对于一些十分特殊的富文本串还是存在问题,比如下面的富文本串
hello world!hello world!
经过多次尝试,碰壁,最后决定借用浏览器来将html转成DOM,通过DOM操作来完成高亮。下面是高亮终极版本
富文本高亮,关键词:['中华人民共和国', '中华', '我'];未高亮
hello world!hello world!hello world!我是 中国人 ,我爱中华人民共和国,中华人民共和国万岁!高亮效果
简单的分词实现
文章最开始说了,分词逻辑一般是后台通过专门的库来完成的。但其实,前端也可以自己实现一个简单的分词,只不过会产生许多无意义的词而已,思路大家一看就明白了
// 简单,粗暴分词function splitWord(word) { var len = word.length, splitWordList = word.split(''); // 一字分组 // 分词 for(var i = 2; i <= len; i++) { for (var j = 0; j + i <= len; j++) { splitWordList.push(word.slice(j, j+i)); } } // 必须把长度最长的放到最前面,否则会造成匹配不全的情况 return splitWordList.reverse();}
运行效果
小结
本文主要从前端的角度,介绍了如何实现高亮功能,包括普通文本高亮和富文本高亮。关键的知识点是:
利用new RegExp(
(${keyword})
, 'g')方式动态创建正则利用str.replace(regexp,
<span class="keyword-match">$1</span>
)为匹配加上高亮样式最长的关键词一定要优先匹配,否则会造成匹配不全的情况
富文本匹配,用正则很难做到100%精确。但如果有浏览器环境,可以借助浏览器先将富文本串转换成DOM,通过DOM操作来实现一个更精确的富文本高亮
前端也可以自己实现分词,只不过会产生大量无意义词组而已
原文地址: