跳到主要内容

键盘与滚动事件

什么时候该用键盘事件, 什么时候不该用?​

  • keydown: 按下按键时触发, 按住不放会持续重复;
  • keyup: 释放按键时触发;
  • 输入不可靠: 语音识别、粘贴、移动端输入法都不产生键盘事件, 想跟踪 <input> 的内容变化要用 input 事件 (任何方式改动后都触发);
  • 使用场景: 处理键盘动作本身, 例如方向键、上下键、热键组合 (虚拟键盘也算);
  • 原则: 只有真正想响应键盘时才用键盘事件;

event.key 与 event.code 该选哪个?​

按键event.keyevent.code
ZzKeyZ
Shift+ZZKeyZ
F1F1F1
ShiftShiftShiftLeft 或 ShiftRight
  • key: 按键的"含义"即字符, 换语言或按 Shift 都会变;
  • code: 物理键位, 与语言无关; 字母键为 Key<字母>, 数字键为 Digit<数字>, 特殊键用名字 (Enter、Backspace);
  • 拼写: 是 KeyZ 不是 keyZ, 首字母大写;
  • 非字符键: 如 F1、Shift, key 与 code 大致相同, 但 code 能区分左右 Shift;
  • 布局反例: 德语 QWERTZ 布局下按 Y 的 code 是 KeyZ, 用 code 判断会误判; 只有少数码 (KeyA、KeyQ、KeyZ 等) 有此问题, Shift 之类不受影响;
  • 结论: 切语言后热键仍要生效 -> 用 code; 要跟随具体字符 -> 用 key;
// 撤销热键, 同时照顾 Mac 的 Cmd
document.addEventListener("keydown", (event) => {
if (event.code === "KeyZ" && (event.ctrlKey || event.metaKey)) alert("Undo!");
});
  • 自动重复: 长按时 keydown 反复触发而 keyup 只有一个, 自动重复的事件 event.repeat === true;

如何用 preventDefault 过滤按键?​

  • 键盘的默认行为很多: 显示字符、删除字符、翻页、Ctrl+S 弹保存对话框;
  • 在 keydown 上阻止默认行为能拦掉大部分, 但操作系统级快捷键 (Windows 的 Alt+F4) 拦不住;
  • 例外: 笔记本的 Fn 键在低于操作系统的层面实现, 没有对应的键盘事件;
<input onkeydown="return checkPhoneKey(event.key)" placeholder="Phone, please" type="tel" />
function checkPhoneKey(key) {
return (
(key >= "0" && key <= "9") ||
["+", "(", ")", "-", "ArrowLeft", "ArrowRight", "Delete", "Backspace"].includes(key)
);
}
  • 机制: 处理程序返回 false 即阻止默认行为, 返回 true 没有任何作用;
  • 副作用: 过滤太严会顺手禁掉 Backspace 和方向键, 要显式加回白名单;
  • 不彻底: 右键粘贴、移动端输入法仍能绕过滤镜; 要可靠就用 oninput 在改动后检查 input.value 并纠正或标红, 也可两者并用;

移动端虚拟键盘有什么坑?​

  • 正式名: 虚拟键盘即 IME (Input-Method Editor);
  • 规范要求: 此时 keyCode 应为 229, key 应为 "Unidentified";
  • 现实: 方向键、退格等按键是否给出正确的 key/code/keyCode 没有保证, 键盘逻辑在移动端可能失效;
  • 历史包袱: keypress 事件与 keyCode/charCode/which 属性因浏览器兼容性混乱已被废弃, 浏览器仍支持但不必再用;

scroll 事件该监听在谁身上, 又如何阻止滚动?​

window.addEventListener("scroll", () => {
document.getElementById("showScroll").textContent = window.pageYOffset + "px";
});
  • 作用对象: 页面滚动在 window 与 document 上都能收到 (通常监听 window); 元素滚动则在自己的元素上触发;
  • 不冒泡: 元素滚动的 scroll 不会冒泡到祖先, 所以不能像 click 那样委托, 只能在目标元素上监听, 见 事件委托与自定义事件;
  • 无法阻止: 在 scroll 处理程序里 preventDefault() 无效, 事件在滚动发生之后才触发;
  • 替代做法: 在引发滚动的事件上阻止, 例如 keydown 里对 PageUp/PageDown 调 preventDefault(); 但引发滚动的方式太多, 更可靠的是 CSS 的 overflow;
  • 高频: 滚动期间触发极频繁, 处理程序里避免读写会触发重排的属性, 需要限频就用节流, 见 节流与防抖;
  • 惯性: 移动端触摸滑动结束后还有惯性滚动, 事件会继续触发一段时间, 限频同样靠节流;
  • 用途: 按滚动位置显示或隐藏控件、滚动到底部时加载更多数据;