键盘与滚动事件
什么时候该用键盘事件, 什么时候不该用?
keydown: 按下按键时触发, 按住不放会持续重复;keyup: 释放按键时触发;- 输入不可靠: 语音识别、粘贴、移动端输入法都不产生键盘事件, 想跟踪
<input>的内容变化要用input事件 (任何方式改动后都触发); - 使用场景: 处理键盘动作本身, 例如方向键、上下键、热键组合 (虚拟键盘也算);
- 原则: 只有真正想响应键盘时才用键盘事件;
event.key 与 event.code 该选哪个?
| 按键 | event.key | event.code |
|---|---|---|
Z | z | KeyZ |
Shift+Z | Z | KeyZ |
F1 | F1 | F1 |
Shift | Shift | ShiftLeft 或 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; - 高频: 滚动期间触发极频繁, 处理程序里避免读写会触发重排的属性, 需要限频就用节流, 见 节流与防抖;
- 惯性: 移动端触摸滑动结束后还有惯性滚动, 事件会继续触发一段时间, 限频同样靠节流;
- 用途: 按滚动位置显示或隐藏控件、滚动到底部时加载更多数据;