js中的事件冒泡与事件捕获

简介: js中的事件冒泡与事件捕获

1. 问题场景


实现了一个同时能够通过鼠标拖拽移动盒子(蓝色部分)以及拖动改变盒子宽高(红色和黄色部分)的效果,但是当想要通过拉动黄色区域部分改变盒子宽度时,发现整个盒子都被拖动,即触发了蓝色区域部分的事件。

附上

问题代码:

大概的意思就是给父盒子box添加了鼠标拖动事件,给两个子元素分别添加了拖拽改变盒子宽度和高度的事件,结果发生了冲突。

为什么会这样呢?出现这个问题的原因其实就是js的事件流执行过程导致的。那么什么是js事件流呢?


2. js事件流


js中事件执行的整个过程称之为事件流,分为三个阶段:事件捕获阶段,处于目标阶段、事件冒泡阶段。


事件捕获:当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件。

处于目标阶段:传播到事件触发处,触发注册的事件。

事件冒泡阶段:与事件捕获阶段相反,由内到外进行事件传播,直到根节点。


2.1 捕获和冒泡


<body>
    <div id="app">div1
        <div id="box">div2
            <div id="title">div3
            </div>
        </div>
    </div>
    <script>
        let app = document.getElementById('app')
        let box = document.getElementById('box')
        let title = document.getElementById('title')
        app.addEventListener('click', () => {
            console.log('div1');
        })
        box.addEventListener('click', () => {
            console.log('div2');
        })
        title.addEventListener('click', () => {
            console.log('div3');
        })
    </script>
</body>

此时页面效果为:

当点击最里面的容器(即黑色容器)代码打印顺序为:div3,div2,div1。

W3C的标准是先捕获再冒泡, addEventListener的第三个参数决定把事件注册在捕获(true)还是冒泡(false)。当点击黑色容器时,开始进行事件捕获,Js事件流从window上往事件触发处传播,遇到注册的捕获事件就会触发;但是捕获阶段默认是不处理的(addEventListener第三个参数默认是false),紧接着传播到事件触发处,触发注册的事件,打印div3,然后进行冒泡阶段从事件触发处 往window上传播,遇到注册的冒泡事件会触发,打印div2,再打印div1。


现在如果给第二个容器的监听点击事件传入第三个参数true,即捕获阶段执行事件。那么打印顺序变为:div2,div3,div1。


2.2 事件流阻止


在一些情况下需要阻止事件流的传播,阻止默认动作的发生。


event.preventDefault():阻止默认事件。比如点击链接后,浏览器默认会跳转到另一个页面,使用这个方法以后,就不会跳转了。但不会阻止事件的传播。

event.stopPropagation():阻止捕获和冒泡阶段中当前事件的进一步传播。但是,它不能防止任何默认行为的发生。

一般来说,如果只希望事件只触发在目标上,这时候可以使用 stopPropagation来阻止事件的进一步传播。通常我们认为 stopPropagation 是用来阻止事件冒泡的,其实该函数也可以阻止捕获事件。

event.stopImmediatePropagation:阻止事件冒泡,还能阻止该事件目标执行别的注册事件。

return false:同时阻止事件冒泡也会阻止默认事件。


2.2.1 stopPropagation()


在上述第三个容器监听事件中加入stopPropagation

title.addEventListener('click', (event) => {
    console.log('div3');
    event.stopPropagation()
})

点击黑色容器,此时打印结果只有div3,因为这个方法阻止了事件流的传播。



2.2.2 stopImmediatePropagation()


给第三个容器监听事件中再添加一个事件,并且使用stopImmediatePropagation。

title.addEventListener('click', (event) => {
    console.log('div3');
    event.stopImmediatePropagation()
})
title.addEventListener('click', () => {
    console.log('_div3');
})

同样点击黑色容器,此时打印结果也只有div3,这个方法终止了默认事件,同时也终止了这个容器的其他事件。

如果是stopPropagation()方法会有两个打印结果,也就是它不会终止这个容器的其他事件,这就是这两个方法的区别。


3. 解决


到这里,其实已经可以发现为什么会产生上述场景中的问题。就是因为点击了盒子的子元素(黄色和红色部分),事件冒泡到了绑定拖拽事件的父元素身上(蓝色部分),导致触发的是拖拽效果。

解决方法:给改变盒子宽度和高度的两个子元素的事件中添加stopPropagation方法阻止事件传播即可。

boxResize() {
      let resizeBox = this.$refs.box; //需要改变宽度的元素
      let resizeWidth = this.$refs.resizeWidth; //操控拖拽改变宽度的侧边滑块
      let resizeHeight = this.$refs.resizeHeight; //操控拖拽改变高度的底部滑块
      //鼠标按下改变盒子宽度
      resizeWidth.onmousedown = (e) => {
        e.stopPropagation();//阻止冒泡
        let initWidth = resizeBox.clientWidth; 
        let startX = e.clientX; 
        const minW = 50; 
        const maxW = 1000; 
        document.onmousemove = function(e) {
          let changeX = e.clientX - startX; //鼠标移动后变化(增大或减小)的距离
          let curWidth = initWidth + changeX; //元素当前宽度 = 初始宽度 + 变化的宽度
          curWidth = curWidth < minW ? minW : curWidth;
          curWidth = curWidth > maxW ? maxW : curWidth;
          resizeBox.style.width = curWidth + "px"; 
        };
        document.onmouseup = function(e) {
          document.onmousemove = null;
          document.onmouseup = null;
        };
      };
      //鼠标按下改变盒子高度
      resizeHeight.onmousedown = (e) => {
        e.stopPropagation();//阻止冒泡
        const initHeight = resizeBox.clientHeight;
        const startY = e.clientY;
        const minH = 50;
        const maxH = 1000;
        document.onmousemove = function(e) {
          let curHeight = initHeight + (e.clientY - startY);
          curHeight = curHeight < minH ? minH : curHeight;
          curHeight = curHeight > maxH ? maxH : curHeight;
          resizeBox.style.height = curHeight + "px";
        };
        document.onmouseup = function(e) {
          document.onmousemove = null;
          document.onmouseup = null;
        };
      };
    },
相关文章
|
22天前
|
JavaScript 前端开发
js事件队列
js事件队列
|
11天前
|
JavaScript 前端开发
JavaScript 事件
JavaScript 事件
23 2
|
1月前
|
JavaScript 前端开发
JavaScript 事件的绑定
JavaScript 事件的绑定
28 0
|
11天前
Nest.js 实战 (十二):优雅地使用事件发布/订阅模块 Event Emitter
这篇文章介绍了在Nest.js构建应用时,如何通过事件/发布-订阅模式使应用程序更健壮、灵活、易于扩展,并简化服务间通信。文章主要围绕@nestjs/event-emitter模块展开,这是一个基于eventemitter2库的社区模块,提供了事件发布/订阅功能,使得实现事件驱动架构变得简单。文章还介绍了如何使用该模块,包括安装依赖、初始化模块、注册EventEmitterModule、使用装饰器简化监听等。最后总结,集成@nestjs/event-emitter模块可以提升应用程序的事件驱动能力,构建出更为松耦合、易扩展且高度灵活的系统架构,是构建现代、响应迅速且具有高度解耦特性的Nest.
|
25天前
|
编解码 JavaScript 前端开发
JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决
JS逆向浏览器脱环境专题:事件学习和编写、DOM和BOM结构、指纹验证排查、代理自吐环境通杀环境检测、脱环境框架、脱环境插件解决
44 1
|
6天前
|
JavaScript 前端开发
|
12天前
|
存储 JavaScript 前端开发
js事件冒泡和事件委托
事件冒泡是指事件从最内层元素开始逐级向上传播至祖先元素的过程,默认情况下,点击子元素时会先触发自身的事件处理程序,再依次向上触发父元素的处理程序。事件委托则是在父元素上设置事件处理程序,利用事件冒泡机制处理子元素的事件,以此减少内存消耗和提高性能,特别适用于处理大量动态子元素的情况。其区别在于事件冒泡是事件传播机制的一部分,而事件委托是编程技巧,通过在父元素上绑定事件处理程序来简化子元素的事件处理。
14 0
|
1月前
|
JavaScript 前端开发
js的回车事件
js的回车事件
35 3
|
1月前
|
JavaScript 前端开发
js常用的几种事件
js常用的几种事件
26 0
|
1月前
|
设计模式 前端开发 JavaScript
javascript 异常问题之Promise的未处理异常如何捕获
javascript 异常问题之Promise的未处理异常如何捕获