editcontext 是彻底替代 contenteditable 的底层输入机制,需移除 contenteditable 属性后手动初始化,所有输入统一通过 input 事件处理,ime 需配合 updatecomposition,textupdate 仅为通知不可拦截,须自行维护文本快照。

EditContext API 不是用来“替代” contenteditable 的增强版,而是彻底放弃它、从零接管输入生命周期的底层机制。Chrome 123+ 才支持,且必须主动弃用 contenteditable="true",否则 EditContext 不生效。
必须先移除 contenteditable 并手动激活 EditContext
EditContext 和 contenteditable 是互斥的:只要元素有 contenteditable="true"(或空值),浏览器就走默认编辑流程,EditContext 的事件根本不会触发。
- 创建一个普通
<div>,<strong>不加</strong> <code>contenteditable属性 - 手动挂载实例:
element.editContext = new EditContext() - 此时元素才成为可编辑目标,但无光标、无输入反馈、无 DOM 自动更新
- 首次聚焦前必须调用
editContext.setCaretRange(range),否则光标不可见(哪怕你已监听到focus) -
inputType === "insertText":取event.data,用editContext.replaceText(data, event.targetRange)写入 -
inputType === "deleteContentBackward":计算targetRange前一个字符位置,构造新 range 后调用replaceText("", range) -
inputType === "insertFromPaste":通过event.dataTransfer?.getData("text/plain")拿纯文本,清洗后再插入 -
inputType === "insertCompositionText"或监听compositionstart/compositionend:需配合editContext.updateComposition()渲染候选栏 - 触发
compositionstart时,调用editContext.updateComposition({ text: "", range: { start: pos, end: pos } })初始化空候选区 - 触发
compositionupdate时,用新拼音/候选更新text字段,并保持range覆盖整个待替换区域 - 触发
compositionend后,调用replaceText(finalText, range)提交最终结果 -
textupdate是事后通知,文本已经写入内部缓冲区,此时调用preventDefault()无效 - 若需格式校验(比如库存量只能是正整数),必须在
input事件里判断:- 对
"insertText",检查event.data是否合法 - 对
"deleteContentBackward",检查删除后剩余内容是否仍合法
- 对
- 不合法时,跳过
replaceText调用即可,相当于静默丢弃该次输入
常见错误:在已有 contenteditable="true" 的元素上直接赋值 editContext——这不会报错,但所有 input 事件类型(如 "insertText")都不会派发。
所有输入都靠监听 input 事件 + 手动 replaceText
键盘敲字、粘贴、IME 输入、语音转文字……全部统一为 input 事件,类型由 event.inputType 区分,没有 keydown/keyup 的必要(除非你要做快捷键,但和文本输入无关)。
注意:event.targetRange 是只读的,不能直接修改;你要基于它推导出实际要操作的字符区间,再传给 replaceText。
IME 和组合输入必须用 updateComposition 动态渲染
东亚语言用户打字时,会经历「输入拼音 → 弹候选 → 选中上屏」过程。EditContext 把中间态暴露给你:
漏掉 updateComposition,用户会看到输入法弹窗悬浮但编辑器内容不动,或者候选字直接覆盖在光标位置导致错位。
textupdate 事件不是用来拦截非法输入的
很多开发者误以为监听 editContext.addEventListener("textupdate", ...) 就能像表单校验一样 preventDefault()。但这是错的:
真正容易被忽略的是:你得自己维护一份「当前文本快照」,因为 editContext 本身不提供 getText() 方法;每次 replaceText 后,都要同步更新你的内部字符串或 AST。











