最近心血来潮,想给自己的博客网站加一个有趣的小彩蛋,想法是用一种类似终端命令行的方式去访问博客中的内容。终端本身对开发者而言非常熟悉,但把它搬到网站中应该可以带来意想不到的惊喜感。(如果你还没有体验,建议现在先暂停一下阅读,去主页寻找这个隐藏的小彩蛋。)但是由于本人不擅长前端,尤其是 CSS,因此在与 ChatGPT 还有 Claude 激烈地争论,折腾了整整 3 晚后,终于成功实现了这个功能,成品效果还不错。
设计思路
功能定位
设计这个“终端”彩蛋时,我希望达到以下几个目的:首先,彩蛋的入口是隐蔽的,不会影响正常用户的浏览体验,也让它更神秘一点,作为一个bonus;同时要提供尽量真实的终端体验,支持常见的 shell 命令如 ls
、cd
、cat
、pwd
等,最重要的,让用户能够通过命令行的方式浏览文章,提供另一种独特的导航体验,最后还要有流畅的交互动画,让进入和退出终端时都有合适的视觉效果。
核心实现
文件系统模拟
为了让终端体验更加真实,我模拟了一个简单的文件系统结构:
/
├── home/
├── posts/
├── archives/
├── tags/
└── me/
通过 VitePress 的主题数据,我可以获取到所有文章的元数据,然后将标题转换为文件名的形式放在 /posts/
目录下。用户可以通过 ls
命令查看所有文章,用 cat
命令阅读文章内容。
命令解析与执行
实现的常用 Shell 命令包括:
- 文件导航类
pwd
:显示当前工作目录ls
:列出当前目录的文件/子目录cd
:切换工作目录,支持相对路径和绝对路径
- 内容查看类
cat
、head
、tail
:查看文件内容
- 辅助工具类
help
:显示帮助信息clear
:清屏exit
:关闭终端
比较有趣的设计是,当用户 cd
到其他页面目录时,终端会自动跳转到对应的网页,这样就实现了通过命令行导航博客的功能。(比如在根目录 \
时,执行 cd archives
这个命令,网页会自动跳转到我的 Archives 目录)。
交互体验优化
为了让终端用户体验更好,我又增加了下列的功能:
- Tab 自动补全:支持命令和文件名的 Tab 补全
- 命令历史:用上下箭头键可以查看命令历史
- 组合键支持:
Ctrl+C
取消当前输入 - 滚动控制:终端打开时禁用背景页面滚动,关闭时恢复
- 中文输入法兼容:处理了中文输入法的 composition 事件
视觉设计
为了提升用户体验和视觉的舒适感,终端的视觉设计借鉴了现代终端模拟器的风格。首先,整个终端使用了毛玻璃背景,同时使用渐变阴影,使用多层阴影营造深度感。在进入和退出终端时有舒缓的动画切换页面,整体缓慢舒适,并且在跳转网页的时候也有一定的动画过渡。
最重要的,提示符号我设为了我最喜欢的 符号,它是金色的,并且他会发光!
一些小细节
输入处理
在实现终端的输入处理逻辑时,一个绕不过去的问题就是中文输入法的兼容性。中文输入不像英文字符是即时输入的,而是经历一个“组合输入”(composition)阶段:用户先输入拼音,系统再给出候选词,用户选择后才会将最终文字写入输入框。如果不加处理,这个过程中按下 Enter 很容易提前触发命令执行,导致未完成的输入被误当作有效指令提交。
为了解决这个问题,我监听了 compositionstart 和 compositionend 事件,用 isComposing 标记当前是否处于输入法的组合状态。当用户处于组合输入时,Enter 会被完全忽略。更进一步,为了防止某些浏览器在输入结束瞬间触发 Enter 的“时差执行”,我引入了 lastCompositionEnd 时间戳,并在 onEnter 中判断用户是否在输入结束 极短时间内(大约 20~30ms 内)触发了回车。如果是,那也视为“误触”并跳过处理。
不同浏览器对输入法行为的实现略有差异,所以我还区分了 Chrome、Safari 等环境,为它们设置了不同的时间阈值。这种方式比单纯用 skipNextEnter 这种布尔值要更鲁棒。
一个微妙但重要的点是:Tab 自动补全不应该被视为“组合输入”行为的一部分。所以在 handleTab 中我手动重置 lastCompositionEnd,确保用户在输入中文后使用 Tab 补全时,接下来的 Enter 能被正确识别并立即执行命令。否则会出现用户刚输入完中文,用 Tab 补全了命令,再按 Enter 结果被忽略的奇怪现象。
彩蛋命令
当然,作为一个彩蛋,少不了一些有趣的隐藏命令:
- 当用户尝试
rm -rf /
时,终端会回复 "You bad guy" sudo
命令会提示 "No no. You are not invited."- 尝试删除或移动文件时会提示权限不足
彩蛋在哪里
最后,揭晓一下彩蛋的位置--点击主页的头像!
彩蛋还会持续更新。。。