← 返回文章列表
所属专题:AI 外骨骼

支付按钮点完页面就"瘫痪"?一行 CSS 冻住全场,这次我靠 AI 少熬了两夜

更新于 2025-12-17年份:2025字数:2,600阅读时长:8 分钟

收银台点完"确认支付"像贴了钢化膜:能看不能摸。复盘里老实说——我让 AI 当"第二双眼睛"快速扫日志、列假设、草拟局部锁定方案,人肉负责点浏览器、看调用栈、拍板要不要动 body。顺手用不大正经的大白话对齐几个原理:模型怎么靠模式联想帮你省时间,啥叫人在回路,以及为啥"暴力给 body 加 pointer-events: none"像用灭火器浇一杯咖啡。

TL;DR · 核心结论

  • 1惨案现场:防重点击给 body 挂了 pointer-events: none,整页像被贴了"请勿触摸"。
  • 2AI 提效点:先列假设、补文档、写一版局部锁伪代码,省掉你从零敲字的机械活。
  • 3人盯着的红线:任何锁都只能打在最必要的那块 DOM 上,别让安全策略变成用户体验的围城。
案发现场

一、案发现场:收银台突然进入"请勿触摸"模式

上周监控飘红,用户说得特别具体:点完"确认支付",加载圈转得挺欢,但想滚一下看看小字、想点返回、甚至想关窗——全都不好使。不是卡死,是那种礼貌的拒绝:像在玻璃栈道外面摸了钢化玻璃,手感冰凉,没用。

我第一反应其实是网络,第二反应是是不是又把遮罩层盖坏了。结果真凶藏得很土:SDK 里一段"怕重复扣费"的小心思,给 body 加了个类名。

// 本意:别让用户手抖连点
function onPaying() {
  document.body.classList.add('noclick');
}

配上这一句,堪称寸草不生:

.noclick {
  pointer-events: none;
}

这段代码写出来的时候大概很自信:锁住世界,保住订单。用户心里想的大概是:我还健在吗?


抽丝剥茧

二、这不是玄学:pointer-events 真的会"株连九族"

`pointer-events` 是个很会继承的属性。你贴在 body 上,等于给整棵 DOM 树发了张集体放假条——子元素默认跟着喝西北风。按钮还在,滚动条也在,但事件流在边缘就被晾着了。你点的不是"关闭",是寂寞。

于是出现一个特别反直觉的现象:动画还能转,因为那是渲染;你想互动,因为事件进不来。排查时如果只听"页面卡",很容易在性能面板里绕远路。我这回学乖了:先看 Elements 面板里 body 头顶糊了啥 class,再看有没有全屏 pointer 拦截。

这里 AI 替我干了件小事:我把"点击支付后除了 loading 啥都不能点"口述给它,它在一分钟内列出了五六条常见路径(body 锁、透明遮罩、错误捕获阶段监听、甚至 focus trap)。不是每条都对,但它把"从最啰嗦到最离谱"排好了序,我省下的是翻三百页论坛帖的心力。

原理上可以粗暴理解成:大模型做的是模式补全——在你的只言片语里找和训练语料最像的故障家谱。它不知道你们产品经理上周改了啥,但在"症状 → 常见写法"这件事上,它比我的短期记忆宽。记住:它只是候选生成器,验货真相比对还得你来。


根治方案

三、救人一命:从"封城"改"封门"

真正的修复不需要什么魔法,需要的是克制。

1. 给按钮发身份证

每个会触发扣款的核心按钮,挂稳定 `id` 或 `data-*`,别再默认全世界都要为你陪葬。

2. 把"嫌疑人"告诉 SDK

别让 SDK 默默假设"我要锁 body"。显式传 `lockButtonId`,谁点的就只摁住谁。

3. 局部锁的朴素写法

function acquireLock(option) {
  const target = getTargetElement(option.lockButtonId);
  if (target && target !== document.body) {
    target.classList.add('btn-disabled');
  } else {
    // 真走到这一步,说明信息丢干净了——再考虑遮罩+可解释的兜底
    applyGlobalOverlay();
  }
}

AI 在这步又摊了一层薄饼:它按我们团队的代码风格吐了一版 diff 形状,顺手提醒"别忘了 focus、aria-disabled、以及失败路径解锁"。我都采纳了吗?没有。但它让我少和 linter 吵两轮,且把"容易忘的边角"提前摆在桌面上——这就是质量提升里最不性感但最值钱的部分:检查清单变长了,背锅半径变短了。

顺便:人和模型的分工,可以很土但很准——模型扩宽度,人保深度。宽度是"还能是什么";深度是"在我们系统里到底是什么"。中间那一道闸,叫人在回路(human-in-the-loop):模型的输出只是建议分布,你的断点才是上线许可。


碎碎念

四、上线之后:世界又能摸了

改完之后,收银台终于回到正常人类的触感:支付中等归等,但至少还能滚条款、还能在网烂时体面退出。业务侧的确定性也在——该锁的按钮照样锁死,只是不再把整个浏览器当嫌疑犯捆起来。

结语

前端写防御代码,心里可以慌,手要稳。"最小影响原则"听起来像 PPT,翻译成人话就是:别用消防栓浇一杯拿铁。AI 能让你少熬两夜、少打两行重复废话,但它替不了你在用户手机里点那一下的良心。

安全要有,围城不必。

本文属于 AI 实用主义流派 的第 6 篇肉身实战。

阅读时长:8 分钟


文档信息

版权声明:自由转载-非商用-非衍生-保持署名(CC BY-NC-ND 3.0)

原文链接:https://yijinlee.com/articles/article-12

作者:李奕锦

商业用途或修改衍生请联系授权。


李奕锦
李奕锦

全栈工程师,业余马拉松选手。

TL;DR

  • 惨案现场:防重点击给 body 挂了 pointer-events: none,整页像被贴了"请勿触摸"。
  • AI 提效点:先列假设、补文档、写一版局部锁伪代码,省掉你从零敲字的机械活。
  • 人盯着的红线:任何锁都只能打在最必要的那块 DOM 上,别让安全策略变成用户体验的围城。
Tags:CSSpointer-eventsJavaScript防重复点击AI 辅助排查

该专题下的阅读路径

AI Coding架构排障

常见问题 FAQ

Q1. 为什么给 body 加 pointer-events: none 会像把整页点了穴?
pointer-events 会从父往子一路"传染"。body 一成绝缘体,下面的按钮、滚动条、关闭叉基本都成了摆设——不是动画坏了,是事件根本到不了你想点的地方。很多人第一反应怪网络,其实有时候只是 CSS 在下狠手。
Q2. AI 在这种 bug 里到底帮了什么忙,又在骗什么忙?
它擅长吞吐:根据症状关键词快速抛出一串候选(样式继承、覆盖层、passive 监听、甚至旧版 Safari 的坑)。但"是不是你们 SDK 干的"得靠你在代码里翻——模型没有你们仓库的愧疚感。把它当会聊天的同事:它提菜单,你点菜;它给草稿,你签字。
Q3. 这跟大模型"猜下一个词"有什么关系?
排查也像在打补丁的自动补全:你给几句现场描述,它在训练里见过的海量相似案子里找高概率解释,先给你一个能跑起来的"下一步"。差别在于你不允许它幻觉上线——每个假设都要用 DevTools 和最小复现来验收。所谓人在回路,就是人握着红笔,模型握着荧光笔。

发表评论

分享你的想法和反馈

支持 Markdown 格式

0/5000