云图札记文章越来越多,靠翻页找内容越来越低效。我决定加一个搜索弹窗,支持中英文全文检索,Ctrl+K 快捷键唤起。这篇文章记录实现过程中关键决策和踩坑经验。

方案选型

博客搜索几种主流方案:

方案优点缺点
Algolia专业、快速需要第三方服务,免费额度有限
Pagefind静态友好,自动索引中文支持一般
自建索引完全可控需要自己写分词和索引逻辑

我选自建索引。博客文章数量有限(几十篇),一个 JSON 索引文件就够了,不需要外部服务。

构建时生成索引

在 Astro 构建流程中遍历所有文章 Markdown 内容,生成搜索索引 JSON:

// 构建脚本核心逻辑
const index = blogPosts.map(post => ({
  title: post.data.title,
  description: post.data.description,
  category: post.data.category,
  tags: post.data.tags,
  slug: post.slug,
  // 正文内容去除 Markdown 标记
  content: stripMarkdown(post.body),
}));

索引文件通过 glob loader 自动收集所有 .md 文件,构建时写入 dist/ 目录。

中文分词

中文没有空格分词,简单的 includes() 匹配效果很差。搜”设计”,应该能匹配到”页面设计”和”设计系统”。

我的做法—双模式匹配:

  1. 精确匹配:直接 includes(keyword)
  2. 逐字匹配:对中文关键词逐字拆开,每个字符都出现在目标文本中才算匹配
function matchChinese(text, keyword) {
  // 精确匹配优先
  if (text.includes(keyword)) return true;
  // 逐字匹配:每个字都出现才算匹配
  return [...keyword].every(char => text.includes(char));
}

不完美,但博客搜索场景够用了。jieba 之类的分词库会增加索引体积和加载时间,性价比不高。

搜索弹窗设计

弹窗是搜索核心交互。设计原则:

  • 毛玻璃遮罩:半透明背景 + backdrop-filter,视觉焦点集中在搜索框
  • 实时搜索:输入即搜索,无需按回车,300ms 防抖
  • 结果卡片:标题 + 描述预览 + 分类徽章 + 标签 + 日期
  • 键盘导航:↑↓ 选择结果,Enter 打开,Esc 关闭

FOUC 陷阱

搜索弹窗上线后出现了一个 bug:每次回到首页,弹窗会短暂闪现一下。

根因:<style is:inline> 放在了 <body> 底部。Astro 默认 scoped CSS 注入 <head>,但 is:inline 的样式保留原位。在 body 底部的话,CSS 延迟加载导致弹窗先以默认状态显示(可见),样式加载后才隐藏。

修复:给弹窗容器加内联初始隐藏样式:

<div class="search-overlay" style="opacity:0; visibility:hidden">

JS 控制显隐时同时操作类名和内联样式,CSS 加载时序不再影响初始状态。

搜索高亮

搜索结果中高亮匹配关键词,方便定位:

function highlight(text, keyword) {
  const regex = new RegExp(`(${escapeRegex(keyword)})`, 'gi');
  return text.replace(regex, '<mark class="search-highlight">$1</mark>');
}

高亮颜色用主色调浅色变体,和整体设计统一。

性能考量

8 篇文章索引约 15KB(gzip 后 5KB),懒加载即可。超过 100 篇可能需要分片或换 Pagefind。

搜索函数用 filter() 遍历索引数组,几十条数据绰绰有余。上千条时可以加简单倒排索引。

总结

自建博客搜索,贴合使用场景比算法先进更重要。博客不是搜索引擎,不需要毫秒级百万级检索。

  • 中文友好
  • 即搜即得
  • 视觉统一
  • 键盘可操作

做到这些就够用了。

种下你的想法

在花园里留下一条评论,和这篇文章一起生长。

COMMENTS