需求来自于我厂的 QA 产品。在这个产品中,我需要在浏览器插件里模拟用的各种行为,比如:点击。
click
事件前后发生了什么
最初我觉得点击嘛,能有啥问题,就直接广播 click
事件呗。结果发现并非如此。实际上,一次 click
背后,其实有一大套的逻辑:
- 移动到按钮上时,会依次触发
mouseover
,mouseenter
事件,前者冒泡,后者不冒泡 - 鼠标按下时,广播
mousedown
事件 - 如果此时其它元素有焦点,那么该元素会先失去焦点,并广播
blur
事件 - (下一节补充)
- 按钮获得焦点,广播
focus
事件 - 如果因此影响到 DOM,那么会等待 DOM 变更
- 如果鼠标没有离开按钮,按钮广播
mouseup
事件 - 最后广播
click
事件 - 在移动设备上,可能会有 300ms 延迟
其实(5)并不准确,每一次事件广播都是独立的 event loop ,所以上面每一步都可能产生 DOM 变化和其它次生变化,也可能导致一些操作或功能不符合预期。比如:
- 一个搜索框。输入后自动搜索,结果以 dropdown 形式展示在下面
- 点击 dropdown 里的条目可以跳转
- 搜索框
blur
时 dropdown 移除
此时,dropdown 里的条目可能无法点击。因为点击时,输入框先 blur
,之后 dropdown 隐藏,接下来 mouseup
事件触发在别的元素上,于是不会有 click
事件。
最简单的解决方案,给 blur
事件加延迟,10ms 就够。
(关于上面的事件触发顺序,可以用这个的 codepen 尝试)
click
事件对 change
事件的影响
接下来,回到 QA 产品。
我真正踩的坑,是 change
事件没有按照预期触发。大家知道,Vue 组件通过 $emit('input')
可以更新绑定在 v-model
里的值。触发的时机一般通过侦听 DOM input
或者 change
来决定。如果 change
无法正确触发,那么 QA 产品自然而然就无法正常工作。
实际上,在上一节的 click
逻辑中,第(4)步可以展开为:
如果失去焦点的元素是
<input>
或者<textarea>
,则该元素会广播change
事件。
模拟 click
动作的代码
最后,演示一下最终代码:
const options = {
bubbles: true,
cancelable: true,
view: window,
};
let event = new MouseEvent('mousedown', options);
elem.dispatchEvent(event);
elem.focus();
event = new MouseEvent('mouseup', options);
elem.dispatchEvent(event);
elem.click();
相关链接
- 我在思否上的问答:当一个按钮被点击时,发生了什么
- CodePen
欢迎吐槽,共同进步