zchlab.space 支持纯键盘操作:/ 打开命令面板、j/k 在文章列表移动、[ ] 翻页、Alt+c 评论、Enter 发送。有人问:这玩意儿是怎么实现的?
这篇文章把”网站听懂键盘”这件事拆开讲,不装高深,都是前端基本功。
核心:一个全局按键监听器
所有快捷键都挂在同一个事件上:
document.addEventListener('keydown', handler);
浏览器里每一次按键,都会冒泡到 document,这个监听器统一分发。逻辑大致是:
按键来了
├─ Esc → 关面板 / 关帮助 / 退出输入框
├─ Alt 组合键 → 搜索、导航、评论(输入框里也生效)
├─ 输入框里?→ 直接放行(打字优先)
└─ 其他 → 命令面板、j/k 列表移动、Enter 打开…
最关键的一步是”输入框守卫”:当焦点在 input / textarea 里时,所有单字符快捷键直接放行——不然用户打字时,按个 j 就把列表光标移走了,那还怎么聊天?Esc 和 Alt 组合键例外,它们在任何地方都该生效(这是”随时能退出”的原则)。
命令面板:输入什么都有反应
按 / 弹出命令面板,输入命令回车执行。设计上追求”输入什么都有反应”:
- 中英文命令都认:
home/首页、posts/分类、logout/退出登录…… - 前缀补全:输入
sub能命中submit,取第一个匹配命令执行(类 MC 式补全) - 都不匹配?当搜索:输入的不是命令,就自动跳转站内搜索,绝不会让你白敲
两个特殊命令值得一提:
// 个人主页:按需加载登录逻辑,取当前用户跳转
const { supabase } = await import('../lib/supabase');
const { data: { session } } = await supabase.auth.getSession();
location.href = session?.user?.id ? `/u/?id=${session.user.id}` : '/login';
这里用了动态 import:登录相关的库只在执行命令的那一刻才加载,平时不拖累页面。轻量、按需,是这类”偶尔用到的大逻辑”的标准做法。
列表导航:j/k 走天下
j/k 在文章列表、分类卡片、标签卡片间移动光标,Enter 打开。实现朴素但有个讲究:
const kbItems = () => [...document.querySelectorAll(
'.post-list a[href^="/posts/"], .cat-grid a[href^="/categories/"]'
)].filter((el) => el.getClientRects().length > 0);
- 统一选择器:不同页面(首页、分类页、标签页)的列表结构各不相同,但都暴露同一个类名,一套代码全站通用
- 只认可见项:
getClientRects()过滤掉隐藏 Tab 里的元素——首页切 Tab 时,j/k绝不会把光标挪进”看不见”的列表里
评论流:回车即执行
评论输入框里,Enter 发送、Shift+Enter 换行——跟终端一个逻辑(回车执行,Shift 是逃生门)。实现就是在 textarea 的 keydown 里拦一下:
ta.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault(); // 阻止换行
sendButton.click(); // 触发发送
}
});
Alt+r 进入”回复选择”模式:j/k 在评论间移动(青色高亮跟随),Enter 展开选中的那条的回复框。这样”回复谁”也变成了键盘操作。
翻页:[ ] 与 URL
首页分页用 URL 参数(?page=N)实现,SSR 直接渲染对应页——好处是刷新不丢页码、链接可分享。键盘上 [ / ] 映射到上一页/下一页,前端只需要读当前分页信息然后跳转:
else if (e.key === '[' || e.key === ']') {
const pager = document.querySelector('.pager[data-page][data-total]');
// 有分页才响应,翻到头自动不响应
}
踩过的坑
-
stopPropagation的代价:命令面板输入框里曾有一个无条件的stopPropagation(),把Esc也拦在了输入框里——结果面板”打开就关不掉”。修法:只拦Enter,让Esc冒泡给全局监听器。事件冒泡是分发的管道,别一棍子全打死。 -
快捷键的”可发现性”:按键太多记不住,所以做了
?帮助浮层,随时查看;侧栏公告和《键盘流操作介绍》文章也在持续教。功能做得再好,用户不知道等于没有。
为什么这么做
因为终端有它的精神:能少碰一次鼠标,就少碰一次;能回车就回车,能用 Esc 就退出。把这种精神搬上网页,就是键盘流——它不是炫技,是给”手不想离开键盘”的人的一份体贴。
评论