浏览器按键失灵踩坑记录一、按键失灵文章目录浏览器按键失灵踩坑记录一、按键失灵1.1 按现象对原因1.2 外部抢键1.3 三步定位1.4 处理二、在被节流的页面里验证动画三、其它3.1 对象方法之间不能按名字互相调用3.2 ES 模块缓存导致新旧代码混跑3.3 .gitignore 否定规则3.4 着色器通道与阈值方向3.5 压缩按下游用途分层3.6 交互参数先量化四、验证信号1.1 按现象对原因现象原因确认方式所有键都无效焦点不在能接收键的元素上看document.activeElement鼠标点一下目标区域再试某个字母键无效同页别的字母键正常外部程序抢键扩展 / 宿主 / 输入法 / 系统工具见 1.3某个组合键无效撞上浏览器或系统保留键换组合Ctrl0/CtrlS会被浏览器拿走等价按钮有效只有键无效与业务逻辑无关问题在键这一层这条本身就是分界线开发者侧正常用户侧无效环境差异扩展、宿主、输入法、键盘布局让用户回传 1.4 的探针结果只在某些时候无效焦点被输入框/弹窗抢走dialog打开时页面收不到键是否刚点过输入框、是否有 modal 开着e.key值异常输入法合成中isComposing/key: Process、非 QWERTY 布局打印e.key与e.isComposing1.2 外部抢键同一份代码有的键灵、有的键不灵按钮好使。keydown 在页面之前就被扩展或宿主消费掉了addEventListener(keydown)不触发处理器里加日志也看不到。来源典型被占的键特征视频倍速类扩展Video Speed Controller / Global SpeedAD减速/加速、R重置倍速部分还有SZX按下后页面角落浮出当前倍率数字如1.6默认一档 0.1Vimium / 键盘导航类扩展几乎全部字母有提示条或光标跳转翻译 / 截图 / 笔记 / 密码管理类扩展常占TSECtrlShift*有浮层或侧栏弹出IDE 内置浏览器、Electron / CEF 外壳宿主自己绑定的全局键禁用扩展仍复现输入法字母键报key: Process或isComposing: true切英文输入法正常截图 / 录屏 / 显卡驱动 / 游戏辅助不固定换浏览器、换机器仍复现辨认数字浮层细步进倍率1.0→1.1→…→1.6是视频倍速扩展的特征浏览器缩放不会出现1.6这种值。该浮层不在页面 DOM 里querySelectorAll(body *)找不到。扩展常用 closed shadow rootquerySelector看不到属于正常现象。确认途径鼠标悬停看 tooltip或「扩展管理 → 该扩展 → 快捷键」查是否绑了这个键。本项目实例用户报按 R 复位无效F 与空格正常复位按钮有效。把同一功能改绑到C后有效说明代码路径无差异r/R的绑定从首个提交起未变过。真因是用户装了视频倍速扩展R是它的“重置倍速”A/D是减速/加速用户按 A/D 时看到浮出1.6。定位过程中的无效改动把复位语义从开自动摆动改成停住、加C别名、再换成D——都没触及原因。期间用截图和像素差异做验证得出过三次互相矛盾的结论原因见第二节。1.3 三步定位找等价按钮。按钮有效 → 业务逻辑没问题问题在键这一层直接排除函数写错了整类原因。换一个键试。功能改绑另一个字母后有效 → 基本断定外部抢键代码路径完全相同仍无效 →回到 1.1 的所有键都无效查焦点。上探针看页面实际收到了什么。写到 DOM 而不是console.log用户不一定开控制台el.addEventListener(keydown,(e){el.dataset.lastkeye.key;el.dataset.lastComposinge.isComposing;// 只在认得的键上 preventDefault});控制台读document.querySelector(...).dataset.lastkey停在上一个成功的键 → 目标键没到页面外部抢键显示目标键 → 键到了页面问题在后续逻辑一直undefined→ 键盘事件没进页面焦点或宿主层面ev.defaultPrevented也可以作为是否被接受的信号一个合成事件就能验证绑定不依赖页面可见性和渲染循环见第四节。1.4 处理治本让用户在「扩展快捷键」里解绑冲突键或本项目改用不冲突的键。页面侧无法绕过。兜底重要操作永远保留一条指针路径——按钮、双击、长按。键盘作为附加入口不作为唯一入口。不把r/R/c/C/d/D全绑上。别名会掩盖真因定位清楚后留一个正式键冲突写进注释。二、在被节流的页面里验证动画自动化 / 无头 / 后台标签页里document.hidden会翻转而渲染循环通常写成functionframe(now){if(!document.hiddenvisible)step(now);elselastTimenow;rafrequestAnimationFrame(frame);}step()被跳过时两类实现的行为不同行为实现方式隐藏时拖拽 / 直接赋值直接写最终状态obj.rotation ...照常生效补间 / 复位 / 翻转只改target靠step()里的缓动收敛完全无变化结果是隐藏时拖拽能动、复位不动被读成复位坏了。用同一个方法测已知能用的翻转也测不出来说明量具本身失效此时任何基于画面的结论都不可信。对策验证前确认document.hidden、visible、rAF 是否在跑。改用不依赖渲染循环的信号见第四节。动画的观感和手感只能真人验证自动化只能验逻辑。三、其它3.1 对象方法之间不能按名字互相调用constapi{flip(v){/* ... */},reset(){flip(false);},// ReferenceError: flip is not defined};flip是对象属性不是作用域里的名字方法内部裸名引用不到。node --check查不出来——语法合法失败发生在运行时名字解析。抽成闭包函数两个方法都调它functionsetFace(v){/* ... */}constapi{flip(v!flipped){setFace(v);},reset(){setFace(false);}};改完没语法错、一调用就炸的先查名字解析打错字、作用域外引用、被同名变量遮蔽。只有实跑能发现。3.2 ES 模块缓存导致新旧代码混跑index.html/main.js是新的被import的子模块还是旧的。症状表现为提示文案写着新键位按下去没反应这类自相矛盾的现象。构建号写进 DOM比看 Network 面板快container.dataset.build2026-09-30.7;// 控制台document.querySelector(#app).dataset.build改了行为就递增号位否则标记无意义。Cache-Control: no-store不保证 ES 模块不缓存需要强刷CtrlShiftR。排错先问这个页面是什么时候加载的。3.3.gitignore否定规则最后匹配生效!important-file.png必须放在*.png之后放前面等于没写。父目录被整体排除时目录内文件无法再否定!dir/file无效。否定规则统一放文件末尾并注释说明顺序不能动。用git check-ignore -q path echo 仍被忽略验证不靠肉眼看。3.4 着色器通道与阈值方向线稿辉光一直不亮。着色器是line (1. - smoothstep(.06, .25, texture2D(tLine, uv).r))只读红色通道且越暗越亮。生成脚本把阈值图写进 alpha、RGB 留全白红通道恒为 1辉光恒为 0。先找采样语句确认读哪个通道、阈值区间、方向smoothstep还是1 - smoothstep。需要过渡就把强度直接当遮罩保留中灰过渡带。p 阈值 ? 0 : 255硬切会扔掉阈值区间内的柔和段。轮廓单独从 alpha 取深色主体贴透明区时 RGB 落差太小边缘检测抓不到。3.5 压缩按下游用途分层用途选择原因平滑渐变 / 照片有损WebP q88~90肉眼无差体积降 5~20 倍参与阈值判断的图mask / 描边 / 深度无损有损会把数值糊过阈值区间产生斑块分辨率可降到一半——按归一化 UV 采样与分辨率无关平色 柔和 alpha无损有损在边缘产生色晕3.6 交互参数先量化“拖拽手感差、旋转不顺手”凭感觉改容易越改越玄。先量出可指认的数字灵敏度0.006 rad/px× 限位±0.65 rad→ 拖 108px 撞墙之后是死区实测拖 250px 只转 44°后半段效率降到 1/6。渲染按时间常数8/s追目标 → 每帧落后0.03~0.07 rad。据此改成软限位 直接写实际值改完用同样两个数字验收。数值合理不等于体验好最终仍需真人确认。四、验证信号按可信度与成本排序手段用来验说明ev.defaultPrevented事件绑定是否生效处理器只对接受的输入调preventDefault()合成事件即可判定不需要页面可见、不需要渲染循环DOM 属性信号状态变更aria-pressed/dataset.*同步可读比截图像素可靠两条路径对照分层定位按钮 vs 键盘、旧键 vs 新键差异本身即证据亮区重心 / 面积姿态、正反面对流光等高频噪声不敏感整幅平均亮度通常只能区分明显不同的两态跨调用分段读被节流的动画状态挂window借两次执行之间的墙钟时间让节流的帧跑完假外部服务网络链路假 SMTP / mock API 可反复跑真实服务会限流真人操作手感 / 视觉不可替代