contenteditable捕获实时打字行为需组合keydown(记录按键时间/键值/光标位置)与input(比对textContent校验正确性),禁用spellcheck,清除零宽字符,并用performance.now()精准计时计算WPM。
contenteditable 捕获实时打字行为原生 input 或 textarea 无法精确监听单个按键(比如按住不放会重复触发),而打字测速必须区分「按键次数」「是否正确」「光标位置」。用 contenteditable + keydown + input 组合才是可靠方案。
关键点:只在 keydown 中记录按键时间、键值和期望字符;在 input 触发后比对实际输入内容与目标文本的对应位置。避免用 keypress(已废弃)或单纯监听 input(无法区分删除/粘贴/方向键)。
contenteditable="true" 的容器要设 spellcheck="false",禁用拼写检查干扰光标定位keydown 时,用 getSelection().anchorOffset 获取当前光标位置,比字符串索引更准确paste)事件必须单独拦截并手动校验,否则会跳过多个字符导致 WPM 计算失真WPM 不是简单用「正确字符数 ÷ 5 ÷ 分钟」。真实场景中,1 个 word = 5 个字符(含空格),但用户删改、回退、误按都得剔除——否则 100 字错 30 个还显示 40 WPM,完全失真。
正确做法:以「目标文本中已被正确覆盖的连续字符段」为单位统计。例如目标是 "the quick",用户输入 "th quik",只计前 2 个字符("th")+ 后 4 个("quik"),中间空格错位导致断开,不合并。
立即学习“前端免费学习笔记(深入)”;
performance.now() 记录开始时间,避免 Date.now() 受系统时间调整影响innerText 和 textContent 在测速中结果不同用 innerText 读取 contenteditable 内容会丢失换行、折叠空白、甚至因 CSS display: none 隐藏内容而漏字;而 textContent 返回原始 DOM 文本节点内容,严格对应目标字符串索引。
例如目标文本含两个空格:"a b",用户输入 "a b",用 innerText 比较会认为「中间空格数不对」,但实际只是渲染折叠了——这会导致误判错误率。
textContent 获取用户当前输入内容/[u200B-u200DuFEFF]/g 清除零宽字符(复制粘贴常带入)iOS Safari 和部分安卓 WebView 中,软键盘弹出后 getSelection().anchorOffset 常返回 0 或错误值,导致字符比对从头开始,全盘错乱。
根本原因:软键盘改变视口,重排布局导致 Selection API 失效。不能依赖事件时机,得用兜底策略。
focusin 和 blur,在聚焦瞬间用 setTimeout(() => {}, 0) 延迟获取光标位置textContent.length 估算光标(仅限单行场景)user-scalable=no,否则 iOS 键盘缩放会进一步扰乱坐标计算真正难的不是算 WPM,而是让每一次空格、每一次退格、每一次粘贴都反映真实输入节奏——浏览器对编辑行为的抽象太粗,必须自己补全语义。