背景
我最近参与的项目涉及开发一个公司内部使用的组件库,我在开发一个搜索框组件时遇到了一个麻烦的问题。简单来说是:
- 点击外层容器会聚焦在输入框上。这是别人写的底层组件上的逻辑,不方便改;
- 我希望点击容器中的 icon 时(是个返回箭头),输入框会失焦。
写好 event handler 之后试了一下发现,每次点击容器内部的 icon,都无法实现失焦的效果。显然,内部 icon 的 handler 触发失焦之后,event 又继续传播到了外层容器,并触发相应 handler,结果又聚焦在输入框上了。
当时比较着急,就没处理这个细节直接提交了。昨天回过头来做这个,同事一下点醒了我:阻止事件传播就可以了!
DOM 事件传播模型
之前了解过 DOM 事件传播模型,但从来没有真正遇到过相关的问题,这次终于碰上了一回!
简单来说,当你点击一个层层嵌套的 DOM 元素(目标元素)时,会发生下面这些事情:
- Capture 阶段:浏览器首先会检查最外层元素是否注册了
capture
模式的点击事件 listener,如果有则执行相应 handler;然后检查更靠近目标元素的下一层元素,如此反复,直到处理完目标元素的capture
模式的点击事件 listener; - Bubble 阶段:检查目标元素是否注册了
bubble
模式的点击事件 listener,如果有则执行相应 handler;然后检查其上一层的元素,不断重复此过程,直到没有更上一层的元素;
现代浏览器都同时支持capture
和 bubble
模式,所以这两个阶段都会有。当你注册一个 listener 时:
target.addEventListener(type, listener, useCapture);
第三个参数 useCapture
就用来设置模式,默认为 false
即使用 bubble
模式,如果指定为 true
则使用 useCapture
模式。
分析一下我上面的例子
具体到我上面的例子,只考虑容器和 icon 这两个元素:
- 点击内部 icon,首先进入 capture 阶段。我没有真正写过
addEventListener
,addEventListener
是前端框架替我加上去的,使用的也都是默认的 bubble 模式,所以这个阶段不会触发任何 handler; - 然后进入 bubble 阶段,触发 icon 的点击事件 handler,输入框失焦;
- bubble 阶段,事件传播到外层容器,触发相应 handler,输入框重新聚焦;
使用 event.stopPropagation() 阻止事件传播
解决我上面遇到的问题也很简单,只需要在内部 icon 的点击事件 handler 上加一句:
event.stopPropagation()
就可以了。这样一来,事件就不会继续传播了,无论目前是处在 capture 还是 bubble 阶段。
也就是说,在内部 icon 阻止了事件继续传播到外层容器上。
参考连接
Event.stopPropagation()
What’s the difference between event.stopPropagation and event.preventDefault?