信息检索类应用的高亮实现

ChatGPT

2 条消息

我打算做一个信息检索类的应用,其中在当前页面,它会调用到一个消息搜索的逻辑,可能需要将搜索得到的关键字,高亮有什么现代的API可以实现他吗?

AI 助手

如果你的场景是信息检索 / 消息搜索结果页(类似微信聊天搜索、Google 搜索结果、IDE 全文搜索),现在比较现代的做法主要有几种,取决于你的需求。

方案 1:CSS Custom Highlight API(现在最现代)

现在浏览器有一个专门干这个事情的 API:

CSS Custom Highlight API

它的特点:

  • 不需要修改 DOM
  • 不需要把文本包成 <span>
  • 性能比 innerHTML.replace() 好很多
  • 很适合搜索结果关键词高亮、代码编辑器、文档阅读器

它的流程:

搜索关键词
    ↓
找到文本位置
    ↓
创建 Range
    ↓
Highlight 注册
    ↓
CSS ::highlight() 渲染

例如:

const textNode = document.querySelector(".message").firstChild;

const range = new Range();

range.setStart(textNode, 5);
range.setEnd(textNode, 10);

const highlight = new Highlight(range);

CSS.highlights.set(
  "search-result",
  highlight
);

CSS:

::highlight(search-result) {
  background: yellow;
  color: black;
}

效果:

今天去了北京旅游
        ^^^
      北京

而原始 DOM 还是:

<div>
今天去了北京旅游
</div>

不会变成:

<div>
今天去了<span>北京</span>旅游
</div>

这对于消息列表非常舒服。(MDN Web Docs)


方案 2:传统 span 包裹(最常见)

很多项目目前仍然这么做:

搜索:

关键词: React

原文本:

今天学习 React 很开心

处理:

今天学习
<span class="highlight">
React
</span>
很开心

CSS:

.highlight {
  background:#ffe58f;
}

优点:

  • 简单
  • 兼容性最好
  • React/Vue 很容易实现

缺点:

如果你的消息:

100万条聊天记录
每条几千字

大量:

<span>

会导致:

  • DOM 膨胀
  • React diff 压力
  • 复制文本处理麻烦

所以大型搜索系统一般不会这么干。


方案 3:后端返回 highlight 片段(搜索引擎方案)

如果你的搜索后端是:

  • Elasticsearch
  • Meilisearch
  • Typesense
  • OpenSearch

通常直接让搜索引擎返回:

{
  "content": "今天去了 <em>北京</em>旅游"
}

例如 Elasticsearch 有:

highlight

功能。

流程:

用户搜索
    |
    v
搜索服务
    |
    v
返回:
{
 text:"今天去了北京旅游",
 highlights:[
   {
    start:4,
    end:6
   }
 ]
}
    |
    v
前端渲染

这种更适合你的场景。


如果你做的是「聊天记录检索」(结合你之前那个消息存储产品)

我会推荐:

数据结构:

搜索服务返回:

{
  "messageId":123,
  "content":"今天去了北京旅游",
  "matches":[
    {
      "keyword":"北京",
      "offset":4,
      "length":2
    }
  ]
}

前端:

Message Component
        |
        |
    Highlight Layer
        |
        |
CSS Custom Highlight API

类似:

┌─────────────────────┐
│ 今天去了 北京 旅游    │
│         ↑           │
│      highlight      │
└─────────────────────┘

优点:

  • 消息内容保持纯文本
  • 搜索结果可以缓存
  • 可以支持多个关键词
  • 可以支持跳转下一个匹配

React 里面怎么封装

大概会做成:

<MessageHighlight
  text="今天去了北京旅游"
  ranges={[
    {
      start:4,
      end:6
    }
  ]}
/>

内部:

useEffect(()=>{
   const range = new Range();

   range.setStart(node,start);
   range.setEnd(node,end);

   CSS.highlights.set(
     "search",
     new Highlight(range)
   );
},[])

兼容性问题

CSS Custom Highlight API 是比较新的标准,目前现代浏览器支持情况已经不错,但老浏览器可能不支持。(MDN Web Docs)

生产项目一般:

if(CSS.highlights){
   useModernHighlight()
}else{
   fallbackToSpan()
}

如果你的目标是做类似 微信聊天搜索 / Notion 全文搜索 / Obsidian 搜索 这种应用,我会倾向:

后端返回 offset → 前端 CSS Custom Highlight API

这是目前架构上最干净的一套。你之前设计的消息存储系统也比较适合这种模型。