前端实现页面文本搜索高亮

299 阅读2分钟

说明

在输入框输入搜索内容后,对页面相应内容进行高亮显示。本文主要针对由pdfjs、mergely等工具生成的html实现搜索高亮

方案

将生成的html与搜索内容进行比对,将匹配到的内容replace为带有高亮样式标签的内容

问题

  1. 获取html直接进行替换:会替换到原本的标签内容,如输入a,会替换到原本的span标签中的a,导致内容和样式错误
  2. 递归获取文本节点,替换文本节点匹配的内容:替换后的节点依旧是文本节点,会按照文本进行解析,无法渲染出新增的带高亮样式的标签
  3. 页面原本渲染的字符被各个标签拆分,没法进行跨标签内容匹配

最终实现

以mergely代码比对结果为例

// 清除上次高亮结果
this.cancleTextHighLight();
if(this.filterText && this.filterText.length) {
// mergely代码比对工具结果分为左右两块,需要分别进行高亮,避免内容一和内容二头尾相接的部分进行错误匹配

// 获取所有包含需要高亮内容的标签,用于获取所有文本内容
// pdfjs需手动创建文本层,后续处理方法类似
    let leftTextLayer = document.querySelectorAll('#compare-lhs+.cm-s-default .CodeMirror-line>span');
    let rightTextLayer = document.querySelectorAll('#compare-rhs+.cm-s-default .CodeMirror-line>span');
// 高亮展示 param1:包含需要高亮内容的标签;param2:高亮关键词;param3:
    this.highLightWord(leftTextLayer, this.filterText, $('.cm-s-default').eq(0));
    this.highLightWord(rightTextLayer, this.filterText, $('.cm-s-default').eq(1));
}

cancleTextHighLight() {
    if($('.text-normal').length) {
        $('.text-normal').removeClass('text-highlight');
    }
}
// 高亮关键词
highLightWord() {
    let _divList = divList;
    const _aim = aim;
    if (!_divList.length) {
        return false;
    }
    // 将所有文本合并为一个字符串
    let allStr = '';
    // 处理所有包含文本的内容的节点,将所有文本合并为一个字符串,并且将每一个字符包裹一层标签进行控制(用于解决跨标签无法匹配高亮问题)
    // 这里可以根据需要加判断是否需要对dom进行再次处理
    for (let i = 0; i < _divList.length; i++) {
        const item = _divList[i];
        // 如果是不是文本,则跳出
        if (item.innerText === undefined) {
            return;
        }
        // 全部拆成一个字一个单位
        let newHtml = '';
        const str = item.innerText;
        allStr = `${allStr}${str}`;
        for (let j = 0; j < str.length; j++) {
            newHtml = `${newHtml}<span class="text-normal">${str[j]}</span>`;
        }
        item.innerHTML = newHtml;
    }

    // 获取包裹好后所有的单字节点
    const doms = container.find('.text-normal');
    // 通过完整文本字符串,获取一共能匹配到的高亮字符个数
    const total = allStr.split(_aim).length - 1;
    // 设置起始匹配位置
    let start = 0;
    // 如果总体匹配结果大于0,进行高亮
    if (total > 0) {
        for (let t = 0; t<total; t++) {
            // 第一个匹配到的位置
            const index = allStr.indexOf(_aim, start);
            // 高亮个数为filterText的长度
            for (let i = 0; i<_aim.length; i++) {
                doms.eq(index + i).addClass('text-hightlight');
            }
            // 下一个高亮文本开始匹配的位置
            start = index + _aim.length;
        }
    }
}