用 contenteditable 替代 input,监听 input 事件(配合 compositionstart/end 处理中文),逐字符比对并动态更新 class;用 performance.now() 计时,documentfragment 批量渲染,仅更新变化节点;恢复进度时重置 starttime、currentpos 等状态。

怎么用原生 HTML + JavaScript 实现一个可运行的打字游戏
直接能跑,不依赖框架,核心逻辑就几十行 JS。关键不是“画界面”,而是「实时比对字符」「统计准确率」「控制输入焦点不丢失」这三件事。
常见错误现象:input 失焦导致按键盘没反应、空格或退格键被忽略、中文输入法下乱码、计时不准、重复触发 keydown 造成多计。
- 用
<div contenteditable="true"> 替代 <code><input>或<textarea></textarea>,避免失焦;禁用默认换行(white-space: nowrap; overflow: hidden) - 监听
input事件而非keydown,绕过输入法干扰;但需额外处理Backspace(input事件不区分按键类型,得靠前后文本长度差判断) - 逐字符比对:把目标文本和用户已输内容按索引对比,用
span给每个字加 class(如correct/error/current)动态着色 - 计时从第一个有效输入开始(非页面加载),用
performance.now()比Date.now()更准 - 监听
compositionstart和compositionend,期间暂停校验和计时 - 只在
compositionend后或纯英文输入的input中更新状态 - 标点符号要原样保留:目标文本里的
“”、‘’、。!?必须严格匹配,不能用 ASCII 引号替代 - 空格也参与比对——中文句末常有全角空格,需统一用
\u3000或检测charCodeAt(0) === 12288 - 用
document.createDocumentFragment()批量插入,避免反复 layout - 只更新「变化的部分」:比如当前输入位从第 5 字移到第 6 字,只需改两个
span的 class,不重建整个结构 - 光标用绝对定位的
<span class="cursor">|</span>覆盖在对应字右侧,CSS 设pointer-events: none防止遮挡输入 - 禁用
user-select: none在目标文本区,否则双击选词会失效 - 恢复时必须重置:
startTime = null、currentPos = 0、errors = 0,再手动调用一次渲染函数 - 不要存「已输文本」,存「已通过字符数」——避免因目标文本变更导致恢复错位
- 用
localStorage.setItem('typing-state', JSON.stringify({...})),但读取后需JSON.parse()并做字段存在性检查(防止旧版本字段缺失) - 测试时清空 localStorage 再刷新,别信缓存的旧数据
如何正确处理中英文混排与标点符号
中文输入法下,用户可能先敲拼音再上屏,input 事件会触发多次(compositionstart → input → compositionend),直接取 event.target.textContent 会拿到中间态(如 “nihao” 而非 “你好”)。
使用场景:用户边打字边切换中英文,或输入带顿号、书名号、引号的句子。
怎么让打字过程有反馈又不卡顿
高频 input 事件(尤其长文本)直接 DOM 操作会掉帧。重点不是“炫酷动画”,而是「光标定位」「错误高亮」「当前字标记」三者同步且不重排。
性能影响:每输一个字就重绘全部 span,100 字就要建 100 个节点,Chrome 下明显卡顿。
为什么 localStorage 保存进度后再次加载总出错
问题不在存储本身,而在于「恢复时未重置内部状态变量」:比如上次打了 30 秒,存了 { wpm: 42, accuracy: 95 },但 JS 里 startTime 还是旧值,新计时会叠加。
兼容性注意:Safari 对 contenteditable 的 selection API 支持弱,getSelection().anchorOffset 可能不准。
最麻烦的其实是光标位置同步——DOM 渲染完成后再用 setTimeout(() => { /* focus + setSelectionRange */ }, 0),否则 Safari 里光标会飘到开头。











