直接读 input.value 会拿到拼音中间态或滞后值,这是浏览器对输入法组合过程的标准实现;必须用 compositionstart/compositionend 配合 isComposing 标志位隔离干扰,且因 compositionend 可能晚于 input 触发,需在两者中均处理并兜底 blur/submit 时校验,同时避免依赖 event.isComposing(仅限 keydown/keyup)。

直接读 input.value 会拿到拼音中间态或滞后值,这不是 bug,是浏览器对输入法组合过程的标准实现。必须用 compositionstart/compositionend 配合状态标记来隔离缓冲区干扰。
为什么 compositionend 有时比 input 还晚触发
Chrome 和 Safari 在部分版本中会把 compositionend 排队到 input 之后执行,尤其在快速连打、切换输入法或软键盘弹出时。这意味着仅靠 if (!isComposing) perform() 仍可能漏掉一次真实上屏内容,或重复执行。
- 不要依赖事件触发顺序做逻辑分支,而应统一用标志位 + 值比对来判定是否“已稳定”
-
compositionend的event.data是最终插入文本,但input.value才是 DOM 当前真实值;二者在极端情况下可能不一致(如用户按 ESC 取消) - 安卓 WebView 中
compositionend可能丢失,需兜底监听blur和submit时的value同步
如何安全地同步 value 而不破坏光标位置
在 contenteditable 或富文本编辑器中,直接赋值 el.innerText = newValue 会重置光标、清空选区、丢弃格式。缓冲区冲突的本质是“DOM 状态”和“逻辑状态”不同步,而非单纯取值问题。
- 避免任何直接写
innerHTML或innerText的操作;优先使用编辑器提供的 API(如 CodeMirror 的replaceRange、Slate 的insertText) - 若必须手动同步,请在
compositionend回调中用getSelection()缓存光标位置,再用setSelectionRange恢复(注意 iOS Safari 不支持setSelectionRange在compositionend内立即生效) - 对
input元素可放心用value赋值,但务必配合isComposing标志——否则会覆盖用户正在输入的拼音串
移动端软键盘导致 value 滞后怎么办
iOS Safari 和部分安卓 WebView 在软键盘弹出瞬间,input.value 有概率卡在上一帧,即使 compositionend 已触发。这不是事件没发,是 DOM 渲染管线被阻塞。
立即学习“前端免费学习笔记(深入)”;
- 不用
setTimeout(..., 0),改用Promise.resolve().then(() => console.log(input.value))获取微任务时机的最新值 - 监听
resize事件作为辅助信号:软键盘弹出会缩小 viewport 高度,此时再读value更可靠 - 在
focus或touchstart后立刻读值,90% 情况下拿到的是旧值;必须等至少一个微任务周期
别信 event.isComposing,它只在 KeyboardEvent 里有
很多文章推荐用 event.isComposing 判断输入法状态,但它**只存在于 keydown/keyup 事件中**,且 Firefox 和 Safari 支持不一致。在 input 或 composition 事件里访问该属性永远是 undefined。
- 正确做法是维护一个外部布尔变量
isComposing,在compositionstart设为true,compositionend设为false - 这个变量必须是闭包内局部的,不能挂到元素上(
el.isComposing在 Vue/React 中易被响应式系统劫持) - 若同时监听键盘方向键(如表单跳转),应在
keydown中检查event.isComposing,但仅用于跳过逻辑,不用于状态同步
最常被忽略的一点:composition 事件不是“中文专用”,日文、韩文、emoji 组合、甚至某些西欧语言的重音字符都走同一套流程。只要你的编辑器面向国际化用户,就得默认启用三件套,而不是加个 UA 判断再开关。


















