点不到按钮的 7 种真实原因:一套从 DOM 到事件的排查顺序
自动化脚本里最消耗时间的一句话是「元素明明在那里,点了没反应」。这篇把过去几周实际踩到的七种成因摊开,按从外到内的顺序排,前面几种查完能覆盖八成场景。
一、先看它是不是真的在视口里
第一件事永远是拿 getBoundingClientRect() 打印四个数。返回 [0,0,0,0] 就说明这个元素完全没有布局——它可能被 display:none 的祖先包着,也可能在一个尚未渲染的 v-if 分支里,还可能是 opacity:0 的占位影子。
有个很容易看漏的情况:元素本身 rect 正常,但祖先链上某一层是 visibility:hidden。这时元素照样有尺寸、照样能被选择器抓到,elementFromPoint 却永远返回它的父节点。判断方法是沿 parent 往上走,逐层读 getComputedStyle 的 display 和 visibility。
二、滚动容器不是 window
不少后台把整个表单塞进一个 overflow:auto 的 div 里,页面的 window.scrollTo 对它毫无作用。表现就是元素 rect 的 y 值是负数,比如 -284,点哪都点不着。
正确做法是先找出真正的滚动容器:
const scrollers = [...document.querySelectorAll('div')]
.filter(e => e.scrollHeight > e.clientHeight + 50 && e.clientHeight > 200)
.map(e => ({ c: e.className, sh: e.scrollHeight, ch: e.clientHeight, top: e.scrollTop }));
找到后直接设 scrollTop,比 scrollIntoView 更可控——后者会连带滚动所有祖先,把已经摆好的位置又搞乱。
三、元素被其他层盖住了
用 document.elementFromPoint(x, y) 验证你到底点在了谁身上。如果返回的是某个 mask、loading 遮罩或者一个透明蒙层,那点击事件当然不会到目标元素。
一个典型的坑是下拉菜单:输入框里敲了内容之后会弹出一个话题建议浮层,它恰好盖住页面底部的提交按钮。这时候先按一次 Escape 把浮层收掉,再点。
四、坐标点击打在了子节点上
按钮宽 129 像素,左侧是图标、中间是文字、右侧是计数。按几何中心点,很容易落在数字那一小段上,而数字所在的 span 有自己的点击行为——比如弹出通知设置面板。现象就是「点了,出来一个完全无关的弹窗」。
解决办法是把落点往左偏,取宽度的 70% 左右而不是 50%,或者干脆选那个带 status-btn 类名的内层节点直接点。
五、组件库把真实入口封装了
Element Plus 这类库的按钮,el.click() 往往没用。原因是它的点击处理挂在 Symbol(_vei).onClick 这类内部属性上,源码长这样:
n => { m.value || e.loading ? n.stopPropagation() : t("click", n) }
它只是把事件重新 emit 给父组件,真正的业务逻辑在父组件的 listener 里。这种情况下派发合成事件(pointerdown / mousedown / pointerup / mouseup / click 全发一遍)也没用,因为父组件绑定的是它自己的状态判断。
真正能过去的办法有两个:一是找到父组件暴露的方法直接调,比如 window.submitForm(0);二是绕开整个组件,直接打它最终会请求的那个接口。
六、Web Component 的 closed shadow DOM
这是最难缠的一类。有些产品把发布按钮做成自定义元素:
<xhs-publish-btn is-publish="true" submit-text="发布"></xhs-publish-btn>
它的 innerHTML 是空的,children.length 是 0,shadowRoot 返回 null——因为是 closed 模式。你在页面上能看见那个红色的「发布」按钮,但它在 DOM 树里不存在,任何选择器都抓不到它,坐标点击也是打在宿主元素上。
可行的突破口是自定义元素自己挂的回调。检查一下宿主对象上的方法名:
const el = document.querySelector('xhs-publish-btn');
typeof el._onPublish // "function"
el._onPublish(); // 直接触发提交
这类内部方法名通常有规律,_on 前缀、驼峰、动词开头。列一遍 Object.getOwnPropertyNames(Object.getPrototypeOf(el)) 就能看见。
七、事件确实发出去了,但业务路径提前 return
最后这一类最迷惑:点击有 hover 反馈、有埋点上报、有 disabled 态变化,看起来全都正常,但 network 面板里一个业务请求都没有。
判断方法很直接:打开 network,只看 XHR,点一次按钮,看有没有新增。没有就是前端在发请求之前就返回了。这时候把埋点域名过滤掉(uba.xxx.com 这类),剩下的如果只有页面自己的轮询,那就确认是死路。
这种断点通常在产品侧,脚本层面无解。能做的只有两件事:一是把接口层翻出来,确认后端到底存不存在这个能力;二是把证据整理清楚去提工单。
一套可以照着走的顺序
getBoundingClientRect()看有没有尺寸- 沿祖先链查
display/visibility - 找真正的滚动容器并
scrollTop elementFromPoint确认落点是谁el.click()试一次,不行就查自定义元素方法和闭包属性- network 面板确认请求有没有发出去
- 没发出去就往接口层翻,找出真实的能力边界
一个判断原则
前五种都是「我没找到正确的入口」,第六种是「入口被刻意藏起来了」,第七种是「入口根本不存在」。
区分二三两类的价值在于:前六种值得继续投入,第七种继续投入就是纯浪费。而判断第七种的成本极低——打开 network 点一次,三秒钟就有答案。所以每次动手之前,先花三秒做这个判断。
反过来也一样:如果你在做一个需要被自动化的系统,把按钮做成 closed shadow DOM、把提交逻辑藏进父组件的状态判断里,短期确实挡住了脚本,长期也挡住了无障碍工具、挡住了键盘操作、挡住了浏览器自带的表单填充。可点、可聚焦、有语义,比什么都重要。
- 点赞
- 收藏
- 关注作者
评论(0)