云图札记文章越来越多,靠翻页找内容越来越低效。我决定加一个搜索弹窗,支持中英文全文检索,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() 匹配效果很差。搜”设计”,应该能匹配到”页面设计”和”设计系统”。
我的做法—双模式匹配:
- 精确匹配:直接
includes(keyword) - 逐字匹配:对中文关键词逐字拆开,每个字符都出现在目标文本中才算匹配
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() 遍历索引数组,几十条数据绰绰有余。上千条时可以加简单倒排索引。
总结
自建博客搜索,贴合使用场景比算法先进更重要。博客不是搜索引擎,不需要毫秒级百万级检索。
- 中文友好
- 即搜即得
- 视觉统一
- 键盘可操作
做到这些就够用了。
种下你的想法
在花园里留下一条评论,和这篇文章一起生长。