04-DOM- 事件高级

59 阅读4分钟

DOM- 事件高级

事件对象

1.初识事件对象

事件对象定义

  • 也是个对象,这个对象里有事件触发时的相关信息
  • 例如:鼠标点击事件中,事件对象就存了鼠标点在哪个位置等信息

获取事件对象

  • 在事件绑定的回调函数的第一个参数就是事件对象

  • 一般命名为event、ev、e

    1649504156570

    代码

    <body>
        <button>点击</button>
        <script>
            let btn = document.querySelector('button')
            btn.addEventListener('click', function (e) {
                console.log(e);
            })
        </script>
    </body>
    

2.事件对象常用属性

部分常用属性

  • type

    获取当前的事件类型

  • clientX/clientY

    获取光标相对于浏览器可见窗口左上角的位置

  • offsetX/offsetY

    获取光标相对于当前DOM元素左上角的位置

  • key

    用户按下的键盘键的值

    现在不提倡使用keyCode

案例

图片跟随鼠标移动

    <style>
        img {
            position: absolute;
            width: 100px;
        }
    </style>
</head>

<body>
    <img src="../images/3.png" alt="">
    <script>
        // let body = document.querySelector('body')
        let pic = document.querySelector('img')

        document.addEventListener('mousemove', function (e) {
            let x = e.clientX
            let y = e.clientY
            pic.style.top = y + 'px'
            pic.style.left = x + 'px'

        })
    </script>
</body>

事件流

1.事件流与两个阶段说明

事件流

事件流指的是事件完整执行过程中的流动路径

1649504372322

1649724977481

  • 说明:假设页面里有个div,当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段

  • 简单来说:捕获阶段是 从父到子 冒泡阶段是从子到父

2.事件捕获和事件冒泡

事件冒泡

当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡

  • 简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的同名事件

  • 默认是事件冒泡

    1649504463347

事件捕获

从DOM的根元素开始去执行对应的事件 (从外到里)

  • 事件捕获需要写对应代码才能看到效果

  • 代码

    1649504543370

  • 说明

    • addEventListener第三个参数传入true代表是捕获阶段触发(很少使用)
    • 若传入false代表冒泡阶段触发,默认就是false
    • 若是用 L0 事件监听,则只有冒泡阶段,没有捕获

3.修改事件流动方向

1649725527693

代码展示

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content=", initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        div {
            width: 200px;
            height: 200px;
            padding: 20px;
            overflow: hidden;
        }

        .a {
            background-color: red;
        }

        .b {
            background-color: blue;
        }

        .c {
            background-color: green;
        }
    </style>
</head>

<body>
    <div class="a"><div class="b"><div class="c"></div>
        </div>
    </div>
    <script>
        let a = document.querySelector('.a')
        let b = document.querySelector('.b')
        let c = document.querySelector('.c')
        a.addEventListener('click', function () {
            console.log('a');
        }, true) //true 将事件流动改为捕获阶段
        b.addEventListener('click', function () {
            console.log('b');
        })
        c.addEventListener('click', function () {  //默认是事件冒泡
            console.log('c');
        }, false) //默认为false,冒泡

    </script>
</body>

</html>

4.阻止事件流动

原因

  • 因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素
  • 若想把事件就限制在当前元素内,就需要阻止事件流动
  • 阻止事件流动需要拿到事件对象

语法

1649504651304

此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效

鼠标经过事件:

  • mouseover 和 mouseout 会有冒泡效果
  • mouseenter 和 mouseleave 没有冒泡效果(推荐)

案例

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content=", initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        div {
            width: 200px;
            height: 200px;
            padding: 20px;
            overflow: hidden;
        }

        .a {
            background-color: red;
        }

        .b {
            background-color: blue;
        }

        .c {
            background-color: green;
        }
    </style>
</head>

<body>
    <div class="a"><div class="b"><div class="c"></div>
        </div>
    </div>
    <script>
        let num = 1
        let a = document.querySelector('.a')
        let b = document.querySelector('.b')
        let c = document.querySelector('.c')
        a.addEventListener('click', function (e) {
            console.log('a');
            e.stopPropagation()
            console.log(num += 1);

        })
        b.addEventListener('click', function (e) {
            console.log('b');
            console.log(num += 10);

            e.stopPropagation()

        })
        c.addEventListener('click', function (e) {  //默认是事件冒泡
            console.log('c');
            e.stopPropagation()
            console.log(num += 100);
        })

    </script>
</body>

</html>

5.阻止默认行为

阻止默认行为,比如链接点击不跳转,表单域的不提交

语法

1649504850926

   e.preventDefault();

1649504856527

案例

form表单 button按钮点击刷新行为--解决方案

1649729298613

案例
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <a href="http://www.baidu.com">百度</a>
    <form action="">
        <!-- <button type="button">点击刷新</button>  //方法三:给button添加type="button" -->
        <button>点击刷新</button>
    </form>
    <script>
        // 1.点击链接不跳转
        let a = document.querySelector('a')
        a.addEventListener('click', function (e) {
            e.preventDefault();
            console.log('点击链接不跳转');
        })


        // 2.点击form表单中的按钮不刷新
        // 方法一
        let form = document.querySelector('form')
        let btn = document.querySelector('button')
        // form.addEventListener('submit', function (e) {
        //     console.log('点击不刷新');
        //     e.preventDefault();

        // })


        // 方法二
        // btn.addEventListener('click', function (e) {
        //     e.preventDefault();
        //     console.log('不刷新');

        // })
    </script>
</body>

</html>
自定义右键菜单
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>

    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            height: 100vh;
        }

        .menu {
            list-style: none;
            padding: 10px;
            border-radius: 5px;
            border: 1px solid #ccc;
            width: 150px;
            position: fixed;

            display: none;
        }

        li {
            height: 40px;
            display: flex;
            align-items: center;
            padding-left: 10px;
            border-bottom: 1px solid #ccc;
        }

        li:hover {
            background-color: skyblue;
            color: #fff;
            cursor: pointer;
        }

        li:last-child {
            border-bottom: none;
        }
    </style>
</head>

<body>
    <ul class="menu">
        <li>添加图标</li>
        <li>切换壁纸</li>
        <li>下载壁纸</li>
        <li>设置</li>
    </ul>

    <script>
        let menu = document.querySelector('.menu')

        // 阻止body右键
        document.body.addEventListener('contextmenu', function (e) {  //contextmenu 右键菜单
            e.preventDefault()
            let left = e.clientX
            let top = e.clientY
            menu.style.left = left
            menu.style.top = top
            menu.style.display = 'block';
            menu.style.left = left + 'px';
            menu.style.top = top + 'px';
        })

        document.body.addEventListener('click', function (e) {
            menu.style.display = 'none';

        })
    </script>
</body>

</html>

事件委托

定义

事件委托是利用事件流的特征解决一些开发需求的知识技巧

总结

  • 优点:给父级元素加事件(可以提高性能)
  • 原理:事件委托其实是利用事件冒泡的特点, 给父元素添加事件,子元素可以触发
  • 实现:事件对象.target 可以获得真正触发事件的元素

1649505302820

代码展示

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        ul {
            background-color: aqua;
            width: 600px;
            padding: 50px;
            list-style: none;
        }
    </style>
</head>

<body>
    <ul>
        <li><a href="#">1</a></li>
        <li><a href="#">2</a></li>
        <li><a href="#">3</a></li>
        <li><a href="#">4</a></li>
        <li><a href="#">5</a></li>
    </ul>
    <script>
        let ul = document.querySelector('ul')
        ul.addEventListener('click', function (e) {
            e.target.style.backgroundColor = 'red'
        })

    </script>
</body>

</html>

在上方的事件委托案例中,我们可以看出一个缺陷:只要在范围内的标签都可以点击并且发生相应的变化,不方便筛选出指定的标签做变化。

需要使用nodeName标签来帮助我们筛选出正确的标签,比如e.target.nodeName === 'LI'代表只有点击li标签才会发生变化,nodeName后的标签要大写

代码展示

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        ul {
            background-color: aqua;
            width: 600px;
            padding: 50px;
            list-style: none;
        }
    </style>
</head>

<body>
    <ul>
        <li><a href="#">1</a></li>
        <li><a href="#">2</a></li>
        <li><a href="#">3</a></li>
        <li><a href="#">4</a></li>
        <li><a href="#">5</a></li>
    </ul>
    <script>
        let ul = document.querySelector('ul')
        ul.addEventListener('click', function (e) {
            // e.target.style.backgroundColor = 'red'
            console.log(e.target);
            // if (e.targe.nodeName === 'LI') {
            if (e.target.nodeName === 'LI') {

                console.log('大写改标签');
                e.target.style.backgroundColor = 'pink'
            }
            if (e.target.nodeName === 'A') {

                console.log('点击链接');
                e.target.style.backgroundColor = 'blank'
                e.target.style.Color = 'blank'
            }
        })

    </script>
</body>

</html>

两种注册事件的区别

  1. 传统on注册(L0)
    • 同一个对象,后面注册的事件会覆盖前面注册(同一个事件)
    • 直接使用null覆盖偶就可以实现事件的解绑
    • 都是冒泡阶段执行的
  2. 事件监听注册(L2)
    • 语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
    • 后面注册的事件不会覆盖前面注册的事件(同一个事件)
    • 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
    • 必须使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)
    • 匿名函数无法被解绑

const 跟 let 的区别

能使用 const ,就不要使用let

1649672251971

1649672259596

const 声明的变量不能被修改