一次按键,触发哪些事件
在输入框里敲一个字母,浏览器会按固定顺序派发一串事件:keydown →(keypress,已废弃)→ beforeinput → input → keyup。而切到中文输入法 (IME) 后行为完全不同:按键先交给输入法「组字」,期间 keydown 的
keyCode 统一是占位值 229、isComposing 为 true,真正上屏靠 compositionstart / update / end 三个事件。这页用一条实时时间线把两种模式都抓下来。
1 · 实时事件时间线
在输入框里打字——先用英文敲几个字母,再切中文输入法打「你好」选字上屏,对比两串时间线。虚线 = 间隔超过 400ms 的分组:
每个事件各管一段:keydown 在字符进入文本之前(可 preventDefault 拦截);beforeinput 带 inputType (insertText / deleteContentBackward / insertCompositionText……) 与将要插入的
data,是取消编辑的最后机会;input 在文本已经改完之后,只适合「读取结果」;keyup 收尾。keypress 只对字符键发、各家行为不一,已被 beforeinput 取代。注意 Backspace / 粘贴 / 语音输入不触发 keypress,但都有 beforeinput。
2 · IME 在事件流里做了什么
中文输入法下,「按键」与「上屏」彻底解耦——上面的时间线里你会看到:
| 阶段 | 事件 | 关键特征 |
|---|---|---|
| 开始组字 | compositionstart |
输入法接管,候选窗弹出 |
| 组字中 | compositionupdate + 一串 keydown |
keydown 的 keyCode = 229、isComposing = true;input 的 inputType = insertCompositionText |
| 选字上屏 | compositionend |
data = 最终上屏的整串文字 |
常见缺陷:输入法选字的 Enter 把表单提交了。用拼音打字时,按 Enter 常常只是「确认候选词」——但很多代码直接 keydown 里判 key === 'Enter' 就提交 / 发送消息,于是中文用户每选一次字就误发一条。修法一行:组字期间的 Enter 不算数。
组字期间拦截 Enter 的 keydown handler:
input.addEventListener('keydown', (e) => {
if (e.isComposing || e.keyCode === 229) return;
if (e.key === 'Enter') submit();
});
顺序细节:compositionend 与最后一个 input 的先后各浏览器并不一致(Chrome 先 input 后 end,Safari 相反的时期都存在过),所以「组字结束后处理一次」最稳的写法是同时监听两者、用
isComposing 做开关,而不是依赖固定的事件顺序。上面的时间线在当前浏览器里抓到的顺序,就是需要适配的顺序。