基础概念

术语

  • 事件:发生一件事
  • 事件类型:发生什么事情;点击、鼠标按下、鼠标抬起、鼠标移入、鼠标移出、键盘按下、键盘抬起…
  • 事件处理程序:一个函数,当某件事情发生时运行。
  • 事件注册:将一个事件处理程序,挂载到某个事件上。

事件流

事件流:当某个事件发生的时候,哪些元素会监听到该事件发生,这些元素发生该事件的顺序。

当一个元素发生了某个事件时,那该元素的所有祖先元素都发生了该事件

事件冒泡: 先触发最里层的元素,然后再依次触发外层元素

事件捕获: 先触发外层的元素,然后再依次触发里面元素

目前,标准规定,默认情况下,事件是冒泡的方式触发。

事件源(事件目标): 事件目标阶段的元素

image.png

test.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  7. <title>事件冒泡</title>
  8. <style>
  9. div {
  10. width: 100px;
  11. height: 100px;
  12. background: red;
  13. position: relative;
  14. }
  15. button {
  16. position: absolute;
  17. right: -200px;
  18. }
  19. </style>
  20. </head>
  21. <body>
  22. <div>
  23. <button>
  24. 点击我
  25. </button>
  26. </div>
  27. <p></p>
  28. <script>
  29. // 点击button后的触发顺序
  30. document.documentElement.onclick = function () {
  31. console.log("html") // ==> 4
  32. }
  33. document.onclick = function () {
  34. console.log("document") // ==> 5
  35. }
  36. document.querySelector("div").onclick = function () {
  37. console.log("div") // ==> 2
  38. }
  39. document.body.onclick = function () {
  40. console.log("body") // ==> 3
  41. }
  42. document.querySelector("button").onclick = function () {
  43. console.log("button") // ==> 1
  44. }
  45. document.querySelector("p").onclick = function () {
  46. console.log("p") // ==> 不会触发
  47. }
  48. // 注意:
  49. // 1 ==> 只要button被点击 就会向上层冒泡 上层的元素都能监听到 比如 body html 都能监听到点击事件, 这和它们是否注册了点击事件无关;
  50. // 2 ==> 执行顺序看的是结构上的关系 而不是 视觉效果上的关系;
  51. </script>
  52. </body>
  53. </html>

事件注册

也叫事件绑定

dom0

将事件名称(事件类型)前面加上on,作为dom的属性名,给该属性赋值为一个函数,即为事件注册。

事件的移除: 重新给事件属性赋值,通常赋值为nullundefined

dom2

dom对象.addEventListener:注册事件

与dom0的区别

  1. dom2可以为某个元素的同一个事件,添加多个处理程序,按照注册的先后顺序运行
  2. dom2允许开发者控制事件处理的阶段,使用第三个参数,表示是否在捕获阶段触发
    1. 如果元素是目标元素(事件源),第三个参数无效

事件的移除: dom对象.removeEventListener(事件名, 处理函数);

dom2中如果要移除事件,不能使用匿名函数 (因为使用匿名函数就好比重新创建了一个函数(匿名函数无法获得它的引用) 所以需要将函数名传入)

细节

  1. dom2在IE8及以下不兼容,需要使用attachEvent,detachEvent添加和移除事件
  2. 添加和移除事件时,可以将第三个参数写为一个对象,进行相关配置
<!-- 事件注册 ==> dom0 ==> dom.onclick -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>事件注册</title>
</head>

<body>
    <button id="btn1">点击1</button>

    <script>
        // 事件注册 (事件绑定)
        // 将事件名称(事件类型)前面加上on, 作为dom的属性名, 给该属性赋一个函数即为事件注册(事件绑定)
        var btn = document.getElementById("btn1");
        btn.onclick = function () {
            console.log("1");
        }

        // 事件移除 ==> 重新给事件属性赋值 通常赋 undefined 或 null
        // btn.onclick = null; // 啥都不写 直接在控制台输出 btn.onclick ==> null
    </script>
</body>

</html>





<!-- 事件注册 ==> dom0 ==> 在dom元素身上直接写 onclick="js代码" 1 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>事件注册</title>
</head>

<body>
    <button id="btn1" onclick="console.log('1')">点击1</button>

    <script>
        var btn = document.getElementById("btn1");
        // 直接在 对应的元素身上写 事件处理程序也okk ==> but 这种方式并不好 这会将 html 和 js 代码混合在一起 不利于后期的维护工作
        // 不过这种直接写在元素身上的 事件处理程序 也可通过给其重新赋值来移除事件
        // 事件移除 ==> 重新给事件属性赋值 通常赋 undefined 或 null
        btn.onclick = null;
    </script>
</body>

</html>



<!-- 事件注册 ==> dom0 ==> 在dom元素身上直接写 onclick="js代码" 2 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>事件注册</title>
</head>

<body>
    <button id="btn1" onclick="test()">点击1</button>

    <script>
        var btn = document.getElementById("btn1");
        test = function () {
            console.log('1')
        }
        // 事件移除
        // btn.onclick = null;
    </script>
</body>

</html>





<!-- 事件注册 ==> dom2 ==> dom对象.addEventListener:注册事件
1. dom2可以为某个元素的同一个事件,添加多个处理程序,按照注册的先后顺序运行 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>dom2 和 dom0 在事件注册方面的区别1</title>
</head>

<body>
    <button id="btn1">点击1</button>

    <script>
        // dom2可以为某个元素的同一个事件,添加多个处理程序,按照注册的先后顺序运行(注意 这是相对于同一个 元素 的 同一个事件类型 而言的)
        var btn = document.getElementById("btn1");
        btn.addEventListener("click", function () {
            console.log("1");
        })

        btn.addEventListener("click", function () {
            console.log("2");
        })
        // 点击 #btn1 后 ==> 1 换行 2
    </script>
</body>

</html>


<!-- 事件注册 ==> dom2 ==> dom对象.addEventListener:注册事件
2. dom2允许开发者控制事件处理的阶段,使用第三个参数,表示是否在捕获阶段触发
   - 如果元素是目标元素(事件源),第三个参数无效 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>dom2 和 dom0 在事件注册方面的区别2</title>
    <style>
        div {
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <button id="btn1">点击1</button>
    </div>


    <script>
        var btn = document.getElementById("btn1");
        // dom2允许开发者控制事件处理的阶段,使用第三个参数,表示是(true)否(false)在捕获阶段触发 (默认 false)
        btn.addEventListener("click", function () {
            console.log("btn1");
        }, false);

        btn.addEventListener("click", function () {
            console.log("btn2");
        }, true);

        var div = document.querySelector("div")
        div.addEventListener("click", function () {
            console.log("div 冒泡");
        }, false);
        div.addEventListener("click", function () {
            console.log("div 捕获");
        }, true);

        /* 点击 btn 后 输出内容如下 (这一部分的知识点 很容易出面试题)
        div 捕获
        btn1
        btn2
        div 冒泡 */

        // 解释 ==> 实际上 此时 在给btn添加事件时 传入的第三个参数是没啥用的
        // 因为 此时点击的元素 就是 btn 所以 btn 是作为事件目标阶段的元素 一定会触发 无论是事件捕获阶段还是事件冒泡阶段
        // 而且触发btn后 btn1 和 btn2 输出的顺序 是由绑定这两个事件处理函数的顺序来决定的
        // 至于 在给div添加事件时所传入的第三个参数 将会影响其触发阶段 true ==> 事件捕获时就触发(在事件目标阶段之前) false ==> 事件冒泡时才触发(在事件目标阶段之后)
        // 它们触发的顺序将不在由注册的先后顺序来决定
    </script>
</body>

</html>




<!-- 事件注册 ==> dom2 ==> 事件的移除: dom对象.removeEventListener(事件名, 处理函数);
细节1 ==> dom2中如果要移除事件,不能使用匿名函数 (因为使用匿名函数就好比重新创建了一个函数(匿名函数无法获得它的引用) 所以需要将函数名传入) -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>dom对象.removeEventListener(事件名, 处理函数);</title>
    <style>
        div {
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <button id="btn1">点击1</button>
        <button id="btnremove">移除按钮1的事件</button>
    </div>


    <script>
        var btn = document.getElementById("btn1");
        var btnRemove = document.getElementById("btnremove");

        // 正确写法
        function handler1() {
            console.log("btn1");
        }

        function handler2() {
            console.log("btn2");
        }

        btn.addEventListener("click", handler1, {
            once: true // 是否只触发一次
        });

        btn.addEventListener("click", handler2);

        // dom对象.removeEventListener(事件名, 处理函数);
        btnRemove.addEventListener("click", function () {
            btn.removeEventListener("click", handler2);
            btn.removeEventListener("click", handler1);
        })

        // 错误写法
        /* btn.addEventListener("click", function () {
            console.log("btn1")
        })
        btnRemove.addEventListener("click", function () {
            btn.removeEventListener("click", function () { // 这个函数和添加到btn上的函数 指向的空间不同 这里相当于是重新在内存中开辟了一块新的空间 所以需要通过引用来传递
                console.log("btn1")
            })
        }) */
    </script>
</body>

</html>




<!-- 事件注册 ==> dom2 ==> 事件的移除: dom对象.removeEventListener(事件名, 处理函数);
细节2 ==> 添加和移除事件时,可以将第三个参数写为一个对象,进行相关配置 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>重点 ==> 移除捕获阶段的事件 需要传入第三个参数 true (默认情况下 第三个参数的值是false)</title>
    <style>
        div {
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <button id="btn1">点击1</button>
        <button id="btnremove">移除按钮1的事件</button>
    </div>


    <script>
        var btn = document.getElementById("btn1");
        var btnRemove = document.getElementById("btnremove");
        var div = document.querySelector("div");

        function handler1() {
            console.log("btn1");
        }

        function handler2() {
            console.log("btn2");
        }

        btn.addEventListener("click", handler1, {
            once: true // 是否只触发一次
        });

        btn.addEventListener("click", handler2);

        function divHandler1() {
            console.log("div 捕获");
        }

        function divHandler2() {
            console.log("div 冒泡");
        }

        div.addEventListener("click", divHandler1, {
            capture: true // 表示是否在捕获阶段触发
        })

        div.addEventListener("click", divHandler2) // 默认 capture 为 false

        btnRemove.addEventListener("click", function () {
            btn.removeEventListener("click", handler2);
            btn.removeEventListener("click", handler1);
            div.removeEventListener("click", divHandler1, true); // 不写第三个参数(即第三个参数取默认值 false) 默认移除的是冒泡阶段的事件
            // 传入第三个参数 ==> 可以移除捕获阶段的事件
            // 测试 ==> 先点击button ==> "移除按钮1的事件" ==> 然后点击button ==> "点击1" ==> 只会触发冒泡事件 divHandler2
            // 然后将第三个参数 true 去掉 再试试 ==> 会发现 divHandler1 和 divHandler2 都会触发 ==> 并没有移除

            // [补充]
            // 写法 1 ==> div.removeEventListener("click", divHandler1, true);
            // 写法 2 ==> div.removeEventListener("click", divHandler1, {
            //      capture: true
            // });
            // 这两种写法都是等效的
        })
    </script>
</body>

</html>

事件对象

事件对象封装了事件的相关信息

获取事件对象

黄色标识出来的是非常重要的知识点

  • 通过事件处理函数的参数获取
  • 旧版本的IE浏览器通过window.event获取

e = e || window.event; // [兼容处理]

事件对象的通用成员

  • target & srcElement

事件目标(事件源对象)

var source = e.target || e.srcElement; // [兼容处理]

事件委托: 通过给祖先元素注册事件,在处理程序中判断事件源进行不同的处理。就是把子元素的事件委托给父元素处理

通常,事件委托用于动态生成元素的区域。这句话 貌似蛮重要的, 目前的理解就是说, 额... 就拿TODOList来说吧, 如果在按钮身上注册事件, 那么当用户 新增 item 时 我们还需要给用户新增的 item 里面的按钮注册事件 但是如果我们将事件注册在祖先元素上 而不是 按钮上, 那么

  • currentTarget

当前目标:获取绑定事件的元素,等效于this

  • type

字符串,得到事件的类型

  • preventDefault & returnValue

preventDefault方法

阻止浏览器默认行为。

注意 ==> returnValue 是IE中的阻止默认行为的写法 比如: 给默认带有默认行为的元素的事件处理程序中的事件对象内部的returnValue属性赋值为false ==> e.returnValue = false;

dom0的方式:在事件处理程序中返回false

也可以直接在 dom 元素身上直接onclick = "return false" 比如: <button type="submit" onclick="return false;">提交</button>

针对a元素,可以设置为功能性链接解决跳转问题

<a href="javascript:;">啥也不做</a>
<a href="javascript:console.log('abc');">输出abc</a>
<a href="javascript:void 0;">void 0</a>
<a href="javascript:void(0);">void(0)</a>
[补充] ==> void 是一个运算符 返回 undefined
以上的几种写法都是以后可能会见到的一些写法
需要知道这些写法的作用都是为了阻止a元素的默认行为即可

js DOM 事件(notes) - 图2

  • stopPropagation方法

阻止事件冒泡

  • eventPhase

得到事件所处的阶段

根据不同的返回值 得知事件所处的不同阶段

1 ==> 事件捕获

2 ==> 事件目标

3 ==> 事件冒泡

test.html

<!-- 获取事件对象 并 了解事件对象的通用成员
    1. 获取事件对象
        - 通过事件处理函数的参数获取
        - 旧版本的IE浏览器通过window.event获取
    2. 事件对象的通用成员(获取目标对象)
        - target ==> 事件目标(事件源对象)
        - srcElement ==> 事件目标(事件源对象) ==> (旧版本的IE浏览器) -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>获取事件对象 并 了解事件对象的通用成员</title>
    <style>
        div {
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <button>Lorem.</button>
        <button>Repudiandae.</button>
        <button>Consequatur!</button>
        <button>Vero.</button>
        <button>Porro.</button>
    </div>

    <script>
        var div = document.querySelector("div")
        // 事件对象封装了事件的相关信息
        div.onclick = function (e) { // 参数 e 不用咋们传 浏览器会自动将这个事件对象传入
            // e = e || window.event; // [兼容处理] 补充 ==> 兼容处理这一部分不需要掌握 后期使用 框架时 兼容性基本是不需要考虑的问题
            // var source = e.target || e.srcElement; // [兼容处理]
            console.log(e.target);
        }
    </script>
</body>

</html>


<!-- 事件委托 ==> 通过给祖先元素注册事件,在程序处理程序中判断事件源进行不同的处理。

    通常 ==> 事件委托用于动态生成元素的区域。(重点 需要理解) -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>事件委托</title>
    <style>
        div {
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <p><span>Lorem ipsum dolor sit amet.</span><button>删除</button></p>
        <p><span>Architecto adipisci provident omnis placeat!</span><button>删除</button></p>
        <p><span>Impedit, optio labore. Alias, quaerat!</span><button>删除</button></p>
        <p><span>Quo molestias tenetur unde excepturi.</span><button>删除</button></p>
        <p><span>Labore tempore inventore at aspernatur?</span><button>删除</button></p>
        <p><span>Repellendus sapiente veniam doloremque adipisci!</span><button>删除</button></p>
        <p><span>Vitae animi error hic asperiores!</span><button>删除</button></p>
        <p><span>Veritatis placeat quibusdam voluptas natus!</span><button>删除</button></p>
        <p><span>Itaque cumque ad culpa porro?</span><button>删除</button></p>
        <p><span>Quas, rerum. Unde, odio dolore!</span><button>删除</button></p>
    </div>

    <script>
        // p*10>(span>lorem5)+button{删除}
        var div = document.querySelector("div")

        div.onclick = function (e) {
            if (e.target.tagName === "BUTTON") {
                e.target.parentElement.remove();
                // button 的 parentElement 是 p 元素
            }
        }
    </script>
</body>

</html>


<!-- 事件委托的注意事项 ==> 具体情况具体分析 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>事件委托的注意事项</title>
    <style>
        div {
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <p><span>Lorem ipsum dolor sit.</span><button><span>删除</span></button></p>
        <p><span>Nostrum placeat sint odit.</span><button><span>删除</span></button></p>
        <p><span>Perferendis, deleniti! Quo, unde.</span><button><span>删除</span></button></p>
        <p><span>Nesciunt facilis atque eos.</span><button><span>删除</span></button></p>
        <p><span>Aliquam doloremque at dolorum.</span><button><span>删除</span></button></p>
        <p><span>Recusandae inventore quasi tempora?</span><button><span>删除</span></button></p>
        <p><span>Vel optio numquam adipisci?</span><button><span>删除</span></button></p>
        <p><span>Aperiam, error molestiae? Aut!</span><button><span>删除</span></button></p>
        <p><span>Eaque ipsam vel est.</span><button><span>删除</span></button></p>
        <p><span>Architecto assumenda atque esse.</span><button><span>删除</span></button></p>
    </div>

    <script>
        // 事件委托的注意事项 ==> 具体情况具体分析
        var div = document.querySelector("div")

        div.onclick = function (e) {
            console.log(e.target); // 尝试点击页面中的不同区域 会发现 e.target
            // 可能是 <span>删除</span> <button>...</button> <p>...</p> 等等
            // 所以说这种情况下 再使用事件委托就有点不合适了 具体还要视html的结构而定
            // 之所以说不合适 是因为判断条件变得更加苛刻了 首先 要明确可以删除的条件
            // 1. 点击 button区域时 可以删除
            // 2. 点击 <span>删除</span> 区域时 可以删除 (但是这里还需要判断点击的 span 是button里面的span)

            if (e.target.tagName === "BUTTON") {
                e.target.parentElement.remove();
            }else if(e.target.tagName === "SPAN" && e.target.innerText === "删除"){
                e.target.parentElement.parentElement.remove();
            }
        }
    </script>
</body>

</html>




<!-- 事件对象的通用成员 ==> currentTarget 和 type
    1. currentTarget ==> 当前目标 ==> 获取绑定事件的元素 ==> 等效于this ==> 事件绑定在哪个dom上 就返回哪个dom
    2. type ==> 得到事件的类型 ==> 返回String类型 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>currentTarget 和 type</title>
    <style>
        div {
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <p><span>Lorem ipsum dolor sit.</span><button><span>删除</span></button></p>
        <p><span>Nostrum placeat sint odit.</span><button><span>删除</span></button></p>
        <p><span>Perferendis, deleniti! Quo, unde.</span><button><span>删除</span></button></p>
        <p><span>Nesciunt facilis atque eos.</span><button><span>删除</span></button></p>
        <p><span>Aliquam doloremque at dolorum.</span><button><span>删除</span></button></p>
        <p><span>Recusandae inventore quasi tempora?</span><button><span>删除</span></button></p>
        <p><span>Vel optio numquam adipisci?</span><button><span>删除</span></button></p>
        <p><span>Aperiam, error molestiae? Aut!</span><button><span>删除</span></button></p>
        <p><span>Eaque ipsam vel est.</span><button><span>删除</span></button></p>
        <p><span>Architecto assumenda atque esse.</span><button><span>删除</span></button></p>
    </div>

    <script>
        // 事件委托的注意事项 ==> 具体情况具体分析
        var div = document.querySelector("div")

        div.onclick = function (e) {
            // e.currentTarget === this ==> 事件绑定在谁身上 就返回谁
            console.log(e.currentTarget)
            console.log(this)
            console.log(this === e.currentTarget) // true
            console.log(e.type) // "click"
        }
    </script>
</body>

</html>



<!-- 事件对象的通用成员 ==> type -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>type</title>
    <style>
        div {
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <p><span>Lorem ipsum dolor sit.</span><button><span>删除</span></button></p>
        <p><span>Nostrum placeat sint odit.</span><button><span>删除</span></button></p>
        <p><span>Perferendis, deleniti! Quo, unde.</span><button><span>删除</span></button></p>
        <p><span>Nesciunt facilis atque eos.</span><button><span>删除</span></button></p>
        <p><span>Aliquam doloremque at dolorum.</span><button><span>删除</span></button></p>
        <p><span>Recusandae inventore quasi tempora?</span><button><span>删除</span></button></p>
        <p><span>Vel optio numquam adipisci?</span><button><span>删除</span></button></p>
        <p><span>Aperiam, error molestiae? Aut!</span><button><span>删除</span></button></p>
        <p><span>Eaque ipsam vel est.</span><button><span>删除</span></button></p>
        <p><span>Architecto assumenda atque esse.</span><button><span>删除</span></button></p>
    </div>
    <script>
        var div = document.querySelector("div");

        function handler(e){
            console.log(e.type)
        }

        div.onclick = handler // 鼠标抬起时 输出 "click"
        div.onmousedown = handler // 鼠标按下时 输出 "mousedown"
    </script>
</body>
</html>



<!-- 事件对象的通用成员 ==> preventDefault & returnValue -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>preventDefault & returnValue</title>
    <style>

    </style>
</head>

<body>
    <a href="https://www.baidu.com">百度</a>

    <form action="https://www.taobao.com">
        <p>
            <input type="text" name="account">
        </p>
        <p>
            <button>提交</button>
        </p>
    </form>

    <script>
        var a = document.querySelector("a");
        var btn = document.querySelector("button");
        // a 远元素有默认行为 跳转页面
        a.addEventListener("click", function(e) {
            e.preventDefault(); //阻止浏览器的默认行为
            console.log("被点击了!");
        })

        // form 表单中的提交按钮 也有默认行为 跳转页面
        // [注意] <button>提交</button> 按钮 默认是 type="submit" 即: <button type="submit">提交</button>
        // 如果将 button 的 type属性值 设置为 button 那么就不会有默认行为
        btn.onclick = function(e){
            e.preventDefault();
            // return false; // 这是 dom0 中阻止默认行为的写法
            // e.returnValue = false; // 这是 ie 中阻止默认行为的写法
        }
    </script>
</body>

</html>




<!-- 针对a元素,可以设置为功能性链接解决跳转问题 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>针对a元素,可以设置为功能性链接解决跳转问题</title>
    <style>

    </style>
</head>

<body>
    <a href="javascript:;">啥也不做</a>
    <a href="javascript:console.log('abc');">输出abc</a>
    <a href="javascript:void 0;">void 0</a>
    <a href="javascript:void(0);">void(0)</a>
</body>

</html>





<!-- 阻止事件冒泡 ==> stopPropagation -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>阻止事件冒泡 ==> 常见的面试题</title>
    <style>

    </style>
</head>

<body>
    <div id="div1">
        <p id="p">
            <span id="span">
                <button>点击</button>
            </span>
        </p>
    </div>

    <script>
        var div1 = document.getElementById("div1");
        var p = document.getElementById("p");
        var span = document.getElementById("span");
        var btn = document.querySelector("button");

        div1.onclick = p.onclick = span.onclick = btn.onclick = function(e){
            console.log(this.tagName);
        }

        p.onclick = function(e){
            e.stopPropagation();
        }
        /* 输出内容如下
        BUTTON
        SPAN

        [解释] ==> 最先输出的是 BUTTON 这是因为它是事件目标 所以经过事件捕获阶段 最先输出的就是 BUTTON
        ==> 其次输出 SPAN 它之所以能够输出 是因为 SPAN 在结构上 位于 P 的内部 p.onclick 事件处理函数
        仅仅阻止了 p 及 p 之后的点击事件的触发 所以 SPAN 是能够输出的 P 和 DIV 是不能够输出的
        */

    </script>
</body>

</html>



<!-- 得到事件所处的阶段 ==> eventPhase -->
<!DOCTYPE html>
<html lang="en">

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

<body>
    <div id="div1">
        <button>点击</button>
    </div>

    <script>
        var div1 = document.getElementById("div1");
        var btn = document.querySelector("button");

        div1.addEventListener("click", function (e) {
            console.log(e.eventPhase + ' ==> ' + "这是在事件捕获阶段运行的事件");
        }, true)

        div1.addEventListener("click", function (e) {
            console.log(e.eventPhase + ' ==> ' + "这是在事件冒泡阶段运行的事件");
        })

        btn.onclick = function (e) {
            console.log(e.eventPhase + ' ==> ' + "这是在事件目标阶段运行的事件");
        }
        /* 输出内容如下:
        1 ==> 这是在事件捕获阶段运行的事件
        2 ==> 这是在事件目标阶段运行的事件
        3 ==> 这是在事件冒泡阶段运行的事件
        */
    </script>
</body>

</html>

[作业讲解]事件对象

<!-- 1. 动态列表 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>动态列表</title>
</head>

<body>
    <p>
        <input type="text" id="txt">
        <button id="btnAdd">添加</button>
    </p>
    <ul>
        <li>
            <span>列表1</span>
            <button>删除</button>
        </li>
        <li>
            <span>列表2</span>
            <button>删除</button>
        </li>
        <li>
            <span>列表3</span>
            <button>删除</button>
        </li>
    </ul>

    <script>
        var btnAdd = document.getElementById("btnAdd");
        var txt = document.getElementById("txt");
        var ul = document.getElementsByTagName("ul")[0];
        btnAdd.onclick = function() {
            var value = txt.value; // 获取输入框的值
            var li = document.createElement("li");
            li.innerHTML = `<span>${value}</span>
            <button>删除</button>`;
            ul.appendChild(li);
            txt.value = ""; // 将输入框的值清空
        }

        // 删除的时候使用了 事件委托
        ul.onclick = function(e) {
            if (e.target.tagName === "BUTTON") {
                e.target.parentElement.remove();
                // 注意点 ==> e.target ==> 表示事件源 ==> 即: 点击事件是发生在哪个dom上的
                // e.currentTarget ==> 表示当前目标 ==> 获取到的是绑定事件的元素 ==> 等效于this
            }
        }
        // 过程 ==> 用户点击 button (事件源 e.target 也就是 button) ==> 事件冒泡 冒到 ul.onclick ==> 触发相应的事件处理程序
        // 思考 ==> 在这种情况下 如果还是直接直接将事件绑定在每一个li内部的button中
        // 那么操作起来就会变得比较繁琐 首先要获取到 每一个 button 然后遍历 并 注册事件
        // 显然 直接给button注册事件的做法 在这种情况下是相当低效的
    </script>
</body>

</html>




<!-- 2. 更换图片 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>变换图片</title>
    <style>
        #buttons button {
            margin-right: 20px;
        }

        img {
            width: 200px;
            height: 200px;
            margin-top: 30px;
        }
    </style>
</head>

<body>
    <div id="buttons"></div>
    <!-- 一个div元素 ==> 作为 button 的容器 -->
    <img src="" alt="">
    <!-- 一个img元素 ==> 作为一个图片容器 -->
    <script>
        // 基本配置信息
        var imgs = [
            "img/1.jpg",
            "img/2.jpg",
            "img/3.jpg",
            "img/4.jpg"
        ];
        var img = document.getElementsByTagName("img")[0]; // 获取到图片容器
        // 默认图片容器中所展示的第一张图片是 配置信息 中的第一张图片
        img.src = imgs[0];


        // 获取到装 button 的容器 ==> divButtons
        var divButtons = document.getElementById("buttons");
        // 根据 配置信息 中的成员(图片)数量 ==> 确定需要生成的按钮数量
        imgs.forEach(function(src, i) {
            // 给 配置信息 中的每一张图片都创建一个对应的 button
            var btn = document.createElement("button");
            // 设置按钮上显示的文本内容
            btn.innerText = `第${i+1}张图片`;
            // 将每一次创建出来的按钮 append 到 button 容器 divButtons 中
            divButtons.appendChild(btn);
            // 将图片的地址 暂存到 button 的自定义属性 data-src 中
            btn.dataset.src = src;
        });

        // 事件委托
        divButtons.onclick = function(e) {
            // if (e.target.dataset.src) {
            //     img.src = e.target.dataset.src;
            // }
            if (e.target.tagName === 'BUTTON') {
                img.src = e.target.dataset.src;
            }
        }
    </script>
</body>

</html>





<!-- 3. 变更栏目框 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>变更栏目框</title>
    <style>
        * {
            margin: 0;
            padding: 0;
        }

        .container {
            width: 390px;
            height: 655px;
            margin: 0 auto;
            margin-top: 20px;
        }

        .top {
            width: 100%;
            height: 10%;
            border-bottom: 2px solid #ccc;
        }

        .content {
            width: 100%;
            height: 90%;
        }

        .top ul {
            width: 100%;
            height: 100%;
            display: flex;
            justify-content: space-around;
        }

        li {
            width: 25%;
            height: 100%;
            list-style: none;
            text-align: center;
            /* 将box-sizing设置为border-box,可以随意修改padding而不用修改盒子大小 */
            box-sizing: border-box;
            padding-top: 35px;
            font-size: 18px;
            color: #2c7bbb;
            cursor: pointer;
        }

        li:hover {
            color: #d32228;
        }

        .current {
            border-bottom: 4px solid #d32228;
            color: #d32228;
        }

        img {
            margin-top: 5px;
        }
    </style>
</head>

<body>
    <div class="container">
        <div class="top">
            <ul>
                <li data-src="images/1.png">电玩</li>
                <li data-src="images/2.png" class="current">网游</li>
                <li data-src="images/3.png">手游</li>
                <li data-src="images/4.png">数码</li>
            </ul>
        </div>
        <div class="content">
            <img src="./images/1.png" alt="" width="390">
        </div>
    </div>
    <script>
        /* 上面的样式暂时先不管 重点在于实现 栏目框的变更4效果 */
        var ul = document.querySelector(".container .top ul");
        var img = document.querySelector("img");
        ul.onclick = function (e) {
            if (e.target.dataset.src) {
                document.querySelector("li.current").classList.remove("current");
                img.src = e.target.dataset.src;
                e.target.classList.add("current");
            }
        }
    </script>
</body>

</html>




<!-- 4. 手风琴 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        ul {
            margin: 0;
            list-style: none;
            padding: 0;
        }

        .wrap {
            width: 200px;
            border: 1px solid #ccc;
            text-align: center;
            line-height: 2;
        }

        .title {
            background: rgb(236, 135, 52);
            display: block;
            cursor: pointer;
        }
    </style>
</head>

<body>
    <div class="wrap" id="wrap">
        <ul class="menu"></ul>
    </div>

    <script>
        // if (!("currentStyle" in Element.prototype)) {
        Object.defineProperty(Element.prototype, "currentStyle", {
            get: function () {
                return window.getComputedStyle(this);
            }
        });

        var menu = [{
                name: "菜单一",
                children: ["商品一", "商品二", "商品三", "商品四"]
            },
            {
                name: "菜单二",
                children: ["商品一", "商品二", "商品三", "商品四"]
            }, {
                name: "菜单三",
                children: ["商品一", "商品二", "商品三", "商品四"]
            }, {
                name: "菜单四",
                children: ["商品一", "商品二", "商品三", "商品四"]
            }
        ];
        var ul = document.querySelector("ul.menu");
        menu.forEach(function (item) {
            var li = document.createElement("li");
            li.innerHTML = `<span class="title">${item.name}</span>`;

            var childUl = document.createElement("ul");
            item.children.forEach(function (child) {
                childUl.innerHTML += `<li>${child}</li>`;
            })
            li.appendChild(childUl);
            ul.appendChild(li);
        })

        // 事件委托 ==> 实现 展开 和 收缩
        ul.onclick = function (e) {
            if (e.target.className === "title") {
                var nextUl = e.target.nextElementSibling;
                // var style = getComputedStyle(nextUl);
                // if (style.display === "none") {
                //     nextUl.style.display = "block";
                // } else {
                //     nextUl.style.display = "none";
                // }

                if (nextUl.currentStyle['display'] === "none") {
                    nextUl.style.display = "block";
                } else {
                    nextUl.style.display = "none";
                }
            }
        }
    </script>
</body>

</html>

事件类型-鼠标事件

事件类型

  • click:用户单击主鼠标按钮(一般是左键)或者按下在聚焦时按下回车键时触发
  • dblclick:用户双击主鼠标按键触发(频率取决于系统配置)
  • mousedown:用户按下鼠标任意按键时触发
  • mouseup:用户抬起鼠标任意按键时触发
  • mousemove:鼠标在元素上移动时触发
  • mouseover:鼠标进入元素时触发
  • mouseout:鼠标离开元素时触发
  • mouseenter:鼠标进入元素时触发,该事件不会冒泡
  • mouseleave:鼠标离开元素时触发,该事件不会冒泡

区别:(面试题爱考)

  • over和out,不考虑子元素,从父元素移动到子元素,对于父元素而言,仍然算作离开
  • enter和leave,考虑子元素,子元素仍然是父元素的一部分
  • mouseenter和mouseleave不会冒泡

事件对象

所有的鼠标事件,事件处理程序中的事件对象,都为 MouseEvent

  • altKey:触发事件时,是否按下了键盘的alt键
  • ctrlKey:触发事件时,是否按下了键盘的ctrl键
  • shiftKey:触发事件时,是否按下了键盘的shift键
  • button:触发事件时,鼠标按键类型
    • 0:左键
    • 1:中键
    • 2:右键
  1. 前3个 e.altKey e.ctrlKey e.shiftKey 得到的结果是一个 Boolean 类型;
  2. [细节] e.button ==> 如果是 clickmousemove 事件 ==> 那么得到的结果永远都是 0, 可以使用 mousedownmouseup 来测试;

与鼠标位置相关的属性:

  • page:pageX、pageY,当前鼠标距离页面的横纵坐标
  • client: clientX、clientY,鼠标相对于视口的坐标
  • offset:offsetX、offsetY,鼠标相对于事件源的内边距的坐标
  • screen: screenX、screenY,鼠标相对于屏幕
  • x、y,等同于clientX、clientY
  • movement:movementX、movementY,只在鼠标移动事件中有效,相对于上一次鼠标位置,偏移的距离
<!-- 触发鼠标事件 以及 这些相关事件的触发先后顺序问题 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>鼠标事件</title>
</head>

<body>
    <div>
        <button>点击</button>
    </div>

    <script>
        // click:用户单击主鼠标按钮(一般是左键)或者按下在聚焦时按下回车键时触发
        var btn = document.querySelector("button")
        // btn.onclick = function(){
        //     console.log("被点击了!");
        //     /* 细节
        //     1. 需要在按钮上 按下 并在 按钮上 弹起 ==> 才能触发 click事件
        //         若弹起鼠标时 鼠标不位于按钮上 那么不会触发 click事件
        //     2. 另外一种触发 click事件 的方式 ==> 按tab聚焦 再按回车 */
        // }


        // dblclick:用户双击主鼠标按键触发(频率取决于系统配置)
        // btn.ondblclick = function(){
        //     console.log("被双击了!");
        //     /* 细节
        //     1. 如果双击的比较慢 那么不会触发 dblclick */
        // }


        // mousedown:用户按下鼠标任意按键时触发
        // btn.onmousedown = function(){
        //     console.log("按下!");
        // }

        // mouseup:用户抬起鼠标任意按键时触发
        // btn.onmouseup = function(){
        //     console.log("抬起");
        // /* 细节
        // 1. 顺序问题 ==> onmousedown ==> onmouseup ==> click ==> dblclick */
        // }

        // mousemove:鼠标在元素上移动时触发
        // btn.onmousemove = function(){
        //     console.log("移动");
        // }

        // mouseover:鼠标进入元素时触发
        btn.onmouseover = function () {
            console.log("进入元素:over");
        }


        // mouseout:鼠标离开元素时触发
        btn.onmouseout = function () {
            console.log("离开元素:out");
        }

        // mouseenter:鼠标进入元素时触发,该事件不会冒泡
        btn.onmouseenter = function () {
            console.log("进入元素:enter");
        }

        // mouseleave:鼠标离开元素时触发,该事件不会冒泡
        btn.onmouseleave = function () {
            console.log("离开元素:leave");
            /* 细节
            1. 顺序问题 ==> mouseover ==> mouseenter ==> mouseout ==> mouseleave */
        }
    </script>
</body>

</html>





<!-- - mouseover:鼠标进入元素时触发
- mouseout:鼠标离开元素时触发
- mouseenter:鼠标进入元素时触发,该事件不会冒泡
- mouseleave:鼠标离开元素时触发,该事件不会冒泡

区别:==>(面试题爱考)

- over和out,不考虑子元素,从父元素移动到子元素,对于父元素而言,仍然算作离开
- enter和leave,考虑子元素,子元素仍然是父元素的一部分
- mouseenter和mouseleave不会冒泡

运行一下下面的三个案例 体会体会 -->
<!-- 案例1 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>两对 进入和离开 之间的区别</title>
    <style>
        div {
            width: 300px;
            height: 300px;
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <button>点击</button>
    </div>

    <script>
        var div = document.querySelector("div")
        var btn = document.querySelector("button")

        div.onmouseover = function () {
            console.log("进入:over", e.bubbles);
        }

        div.onmouseout = function () {
            console.log("离开:out", e.bubbles);
        }
    </script>
</body>

</html>





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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>两对 进入和离开 之间的区别</title>
    <style>
        div {
            width: 300px;
            height: 300px;
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <button>点击</button>
    </div>

    <script>
        var div = document.querySelector("div")
        var btn = document.querySelector("button")

        btn.onmouseover = function (e) {
            e.stopPropagation();
        }
        btn.onmouseout = function (e) {
            e.stopPropagation();
        }
        div.onmouseover = function () {
            console.log("进入:over", e.bubbles);
        }

        div.onmouseout = function () {
            console.log("离开:out", e.bubbles);
        }
    </script>
</body>

</html>




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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>两对 进入和离开 之间的区别</title>
    <style>
        div {
            width: 300px;
            height: 300px;
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <button>点击</button>
    </div>

    <script>
        var div = document.querySelector("div")
        var btn = document.querySelector("button")

        div.onmouseenter = function (e) {
            console.log("鼠标进入:enter", e.bubbles)
        }

        div.onmouseleave = function (e) {
            console.log("鼠标离开:leave", e.bubbles)
        }
    </script>
</body>

</html>





<!-- 事件对象 - 1 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>事件对象 - 1</title>
</head>

<body>
    <button>按钮</button>
    <script>
        var btn = document.querySelector("button")
        btn.onclick = function (e) {
            console.log(e.altKey, e.ctrlKey, e.shiftKey);
        }
    </script>

</body>

</html>




<!-- 事件对象 - 2 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>事件对象 - 2</title>
</head>

<body>
    <button>按钮</button>
    <script>
        var btn = document.querySelector("button")
        btn.onclick = function (e) {
            console.log(e.button);
        }
        btn.onmousedown = function (e) {
            console.log(e.button);
        }
        btn.onmouseup = function (e) {
            console.log(e.button);
        }
    </script>

</body>

</html>



<!-- 鼠标事件对象 ==> 鼠标位置属性 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        div {
            border: 2px solid;
            background: lightblue;
            width: 500px;
            height: 500px;
            padding: 50px;
            margin: 0 auto;
        }

        p {
            border: 2px solid;
            background: rgb(252, 81, 81);
            width: 100px;
            height: 100px;
            padding: 50px;
            background-clip: content-box;
            /* 让背景仅填充内容盒 */
        }
    </style>
</head>

<body>
    <header>
        lorem1000
    </header>
    <div>
        <p></p>
    </div>

    <footer>
        lorem1000
    </footer>

    <script>
        var p = document.querySelector("p");
        var div = document.querySelector("div");

        div.onmousemove = function (e) {
            // console.log(e.pageX, e.pageY); // 相对于整个页面
            // console.log(e.clientX, e.clientY); // 相对于可视区域
            // console.log(e.offsetX, e.offsetY); // 相对于事件源的内边距
            // console.log(e.screenX, e.screenX); // 相对于屏幕
            // console.log(e.movementX, e.movementY); // 相对于上一次触发时的鼠标位置
        }
    </script>
</body>

</html>





<!-- 拖拽效果 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>拖拽效果</title>
    <style>
        div {
            width: 200px;
            height: 200px;
            border-radius: 50%;
            background: rgb(247, 93, 93);
            cursor: move;
            position: absolute;
            left: 100px;
            top: 100px;
        }

        p {
            width: 100px;
            height: 100px;
            background: lightblue;
        }
    </style>
</head>

<body>
    <div>
        <!-- <p></p> -->
    </div>

    <script>
        var div = document.querySelector("div");
        div.onmousedown = function(e) {
            if (e.button !== 0) { //如果不是鼠标左键
                return;
            }
            var pageX = e.pageX;
            var pageY = e.pageY;
            var style = getComputedStyle(div);
            var divLeft = parseFloat(style.left);
            var divTop = parseFloat(style.top);
            window.onmousemove = function(e) {
                var disX = e.pageX - pageX;
                var disY = e.pageY - pageY;
                div.style.left = divLeft + disX + "px";
                div.style.top = divTop + disY + "px";
            }
            window.onmouseup = window.onmouseleave = function(e) {
                if (e.button === 0) {
                    window.onmousemove = null; //移除鼠标移动事件
                }
            }
        }

        // var div = document.querySelector("div");
        // var style = getComputedStyle(div);
        // var divLeft = parseFloat(style.left);
        // var divTop = parseFloat(style.top);
        // div.onmousedown = function(e) {
        //     if (e.button !== 0) { //如果不是鼠标左键
        //         return;
        //     }
        //     window.onmousemove = function(e) {
        //         divLeft += e.movementX;
        //         divTop += e.movementY;
        //         div.style.left = divLeft + "px";
        //         div.style.top = divTop + "px";
        //     }
        //     window.onmouseup = window.onmouseleave = function(e) {
        //         if (e.button === 0) {
        //             window.onmousemove = null; //移除鼠标移动事件
        //         }
        //     }
        // }
    </script>
</body>

</html>

[作业讲解]星星评分、放大镜

<!-- 星星评分 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>星星评分</title>
    <style>
        .left {
            float: left;
            margin-left: 10px;
            line-height: 30px;
        }
    </style>
</head>

<body>
    <div id="divstars" class="left">
        <img src="images/empty.png" alt="">
        <img src="images/empty.png" alt="">
        <img src="images/empty.png" alt="">
        <img src="images/empty.png" alt="">
        <img src="images/empty.png" alt="">
    </div>
    <div id="divword" class="left">

    </div>

    <script>
        var words = ["满意", "一般满意", "还不错", "很满意", "非常满意"];
        var divstars = document.getElementById("divstars");
        var divword = document.getElementById("divword");
        var star = -1; //记录评分,点击的是第几个星星
        divstars.onmouseover = function(e) {
            if (e.target.tagName === "IMG") {
                e.target.src = "images/shining.png";
                //处理之前的
                var prev = e.target.previousElementSibling;
                while (prev) {
                    prev.src = "images/shining.png";
                    prev = prev.previousElementSibling;
                }
                //处理之后的
                var next = e.target.nextElementSibling;
                while (next) {
                    next.src = "images/empty.png";
                    next = next.nextElementSibling;
                }
                //处理文字
                var index = Array.from(divstars.children).indexOf(e.target)
                divword.innerHTML = words[index];
            }
        }

        divstars.onclick = function(e) {
            if (e.target.tagName === "IMG") {
                star = Array.from(divstars.children).indexOf(e.target);
            }
        }

        divstars.onmouseleave = function() {
            divword.innerHTML = words[star] || "";
            for (var i = 0; i < divstars.children.length; i++) {
                if (i <= star) {
                    divstars.children[i].src = "images/shining.png";
                } else {
                    divstars.children[i].src = "images/empty.png";
                }
            }
        }
    </script>
</body>

</html>



<!-- 放大镜 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>放大镜</title>
    <style>
        .small {
            width: 350px;
            height: 350px;
            border: 1px solid #ccc;
            float: left;
            background-clip: padding-box;
            position: relative;
        }

        .big {
            width: 540px;
            height: 540px;
            border: 1px solid #ccc;
            float: left;
            margin-left: 10px;
            background-clip: padding-box;
            display: none;
        }

        .small .move {
            position: absolute;
            background: rgba(200, 200, 50, 0.5);
            border: 1px solid #999;
            box-sizing: border-box;
            cursor: move;
            display: none;
        }
    </style>
</head>

<body>
    <div class="small">
        <div class="move"></div>
    </div>
    <div class="big"></div>

    <script>
        /**
         * 初始化
         */
        (function () {
            //配置
            var config = {
                smallBg: "images/mouse.jpg", // 小图背景路径
                bigBg: "images/mouseBigSize.jpg", //大图背景路径
                divBig: document.querySelector(".big"), //大图div dom元素
                divSmall: document.querySelector(".small"), //小图div dom元素
                divMove: document.querySelector(".small .move"), //可移动的div
                smallImgSize: { //小图尺寸
                    width: 350,
                    height: 350
                },
                divBigSize: { //大的div的尺寸
                    width: 540,
                    height: 540
                },
                bigImgSize: { //大图尺寸
                    width: 800,
                    height: 800
                }
            };
            //计算可移动的div的宽高
            config.moveSize = {
                width: config.divBigSize.width / config.bigImgSize.width * config.smallImgSize.width,
                height: config.divBigSize.height / config.bigImgSize.height * config.smallImgSize.height,
            };

            initDivBg();
            initMoveDiv();
            initDivSmallEvent();

            /**
             * 初始化div背景
             */
            function initDivBg() {
                config.divSmall.style.background = `url("${config.smallBg}") no-repeat left top/100% 100%`;
                config.divBig.style.background = `url("${config.bigBg}") no-repeat`;
            }

            /**
             * 初始化可移动的div
             */
            function initMoveDiv() {
                config.divMove.style.width = config.moveSize.width + "px";
                config.divMove.style.height = config.moveSize.height + "px";
            }

            /**
             * 初始化小图div的鼠标事件
             */
            function initDivSmallEvent() {
                config.divSmall.onmouseenter = function () {
                    config.divMove.style.display = "block";
                    config.divBig.style.display = "block";
                }
                config.divSmall.onmouseleave = function () {
                    config.divMove.style.display = "none";
                    config.divBig.style.display = "none";
                }

                config.divSmall.onmousemove = function (e) {
                    var offset = getOffset(e);
                    setPosition(offset);
                    setBigBgPosition();
                }

                /**
                 * 设置大图背景图位置
                 */
                function setBigBgPosition() {
                    var style = getComputedStyle(config.divMove);
                    var left = parseFloat(style.left);
                    var top = parseFloat(style.top);

                    var bgLeft = left / config.smallImgSize.width * config.bigImgSize.width;
                    var bgTop = top / config.smallImgSize.height * config.bigImgSize.height;
                    config.divBig.style.backgroundPosition = `-${bgLeft}px -${bgTop}px`;
                }

                /**
                 * 根据鼠标坐标,设置divMove的坐标
                 * @param {*} offset
                 */
                function setPosition(offset) {
                    var left = offset.x - config.moveSize.width / 2;
                    var top = offset.y - config.moveSize.height / 2;
                    if (left < 0) {
                        left = 0;
                    }
                    if (top < 0) {
                        top = 0;
                    }
                    if (left > config.smallImgSize.width - config.moveSize.width) {
                        left = config.smallImgSize.width - config.moveSize.width;
                    }
                    if (top > config.smallImgSize.height - config.moveSize.height) {
                        top = config.smallImgSize.height - config.moveSize.height;
                    }
                    config.divMove.style.left = left + "px";
                    config.divMove.style.top = top + "px";
                }

                /**
                 * 根据鼠标事件参数,得到鼠标在divsmall中的坐标
                 * @param {MouseEvent} e
                 */
                function getOffset(e) {
                    if (e.target === config.divSmall) {
                        return {
                            x: e.offsetX,
                            y: e.offsetY
                        }
                    } else {
                        //事件源是divMove
                        var style = getComputedStyle(config.divMove);
                        var left = parseFloat(style.left);
                        var top = parseFloat(style.top);
                        return {
                            x: e.offsetX + left + 1, //加1是因为边框
                            y: e.offsetY + top + 1
                        }
                    }
                }
            }
        }())
    </script>
</body>

</html>

事件类型-键盘事件

事件类型

  • keydown:按下键盘上任意键触发,如果按住不放,会重复触发此事件
  • keypress:按下键盘上一个字符键时触发
  • keyup:抬起键盘上任意键触发

keydown、keypress 如果阻止了事件默认行为,文本不会显示。

事件对象

KeyboardEvent

  • code:得到按键字符串,适配键盘布局。
  • key:得到按键字符串,不适配键盘布局。能得到打印字符。
  • keyCode、which:得到键盘编码
<!-- # 键盘事件

## 事件对象

KeyboardEvent

- code:得到按键字符串,适配键盘布局。
- key:得到按键字符串,不适配键盘布局。能得到打印字符。
- keyCode、which:得到键盘编码 -->

<!-- 键盘事件的事件类型 及 键盘事件的事件对象中的相关属性 -->
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>键盘事件的事件类型 及 键盘事件的事件对象中的相关属性</title>
</head>

<body>
    <input type="text">
    <script>
        // keydown:按下键盘上任意键触发,如果按住不放,会重复触发此事件
        // keypress:按下键盘上一个**字符键**时触发
        // keyup:抬起键盘上任意键触发
        // 注意 ==> keydown、keypress 如果阻止了事件默认行为,文本不会显示。
        var inp = document.querySelector("input")

        inp.onkeydown = function (e) {
            console.log("键盘按下了 key down", e.code, e.key, e.keyCode)
            // return false; // 阻止事件默认行为 ==> input 输入框中的文本将不会显示
        }

        inp.onkeypress = function (e) {
            console.log("键盘按下了!key press", e.charCode); // e.charCode ==> 得到字符 Unicode 编码
            // 注意 触发 keypress 的按键一般是有反馈的一些按键(字符键) 比如: 26个英文字母 或者 0-9 等 [无法触发的按键 如: Ctrl]
            // return false; // 阻止事件默认行为 ==> input 输入框中的文本将不会显示
        }

        inp.onkeyup = function () {
            console.log("键盘抬起了!key up")
            // return false; // 阻止事件默认行为 ==> 由于已经按键已经按下了 所以input文本框中的内容 依旧会正常显示
        }
    </script>
</body>

</html>






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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>不能在div等元素上绑定键盘事件 因为div无法聚焦</title>
    <style>
        div{
            width: 200px;
            height: 200px;
            background: red;
        }
    </style>
</head>

<body>
    <div>
        <input type="text">
    </div>
    <script>
        //
        var div = document.querySelector("div");
        div.onkeydown = function(e) {
            console.log("键盘按下了", e.code, e.key, e.keyCode)
        }

        div.onkeypress = function(e) {
            console.log("键盘按下了!key press")
        }

        div.onkeyup = function() {
            console.log("键盘抬起了!key up")
        }
    </script>
</body>

</html>

[作业讲解]键盘事件

1. 坦克移动

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>坦克移动</title>
    <style>
        body {
            background: #000;
        }

        img {
            position: fixed;
            left: 500px;
            top: 400px;
        }
    </style>
</head>

<body>
    <img src="imgs/tankU.gif" alt="">
    <script src="index.js"></script>
</body>

</html>
var tank = {
    direction: "U",// U L R D
    left: 500,
    top: 400,
    dom: document.querySelector("img"),
    show: function () { //显示
        this.dom.style.left = this.left + "px";
        this.dom.style.top = this.top + "px";
        this.dom.src = "imgs/tank" + this.direction + ".gif";
    }
}

//切换方向 和 移动
document.addEventListener("keydown", function (e) {
    console.log(e.key); // 得到按键字符创, 不适配键盘布局. ==> 能得到打印字符
    if (e.key === "ArrowUp") {
        tank.direction = "U";
        tank.top -= 4;
    }
    else if (e.key === "ArrowDown") {
        tank.direction = "D";
        tank.top += 4;
    }
    else if (e.key === "ArrowLeft") {
        tank.direction = "L";
        tank.left -= 4;
    }
    else if (e.key === "ArrowRight") {
        tank.direction = "R";
        tank.left += 4;
    }
    tank.show();
})

// 小 bug ==> 边界问题

2. 数字文本框

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>数字文本框</title>
</head>

<body>
    <input type="text">

    <script>
        var inp = document.querySelector("input");
        inp.onkeypress = function(e) {
            var code = e.key[0].charCodeAt(0);
            // e.key[0].charCodeAt(0) === e.key.charCodeAt(0)
            if (code < 48 || code > 57) {
                e.preventDefault(); // 阻止事件默认行为
                // return false;
            }
        }

        // 下面这一部分是后边的相关知识 用于解决复制粘贴非数字的bug [但是在输入法下 还是存在此bug。。]
        inp.onpaste = function(e) { // onpaste 事件在用户向元素中粘贴文本时触发。
            var text = e.clipboardData.getData("text/plain");
            if (!text || !/\d+/.test(text)) {
                e.preventDefault();
            }
        }
    </script>
</body>

</html>

其他事件

表单事件

  • focus:元素聚焦的时候触发(能与用户发生交互的元素,都可以聚焦),该事件不会冒泡
  • blur:元素失去焦点时触发,该事件不会冒泡。
  • submit:提交表单事件,仅在form元素有效。
  • change:文本改变事件
  • input: 文本改变事件,即时触发

test.html

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>表单事件 聚焦focus 和 失焦blur</title>
    <style>
        .noinput {
            color: #ccc;
        }
    </style>
</head>

<body>
    <div>
        <input type="text" class="noinput" value="请输入关键字">
    </div>

    <script>
        var inp = document.querySelector("input")

        inp.onfocus = function () {
            // 补充 ==> 聚焦方式可以是鼠标点击文本输入框 也可以按 tab键
            // 注意 ==> focus聚焦事件 ==> 不会冒泡 ==> 所以父元素 div 上 不会触发该事件
            if (this.value === this.defaultValue && this.className === "noinput") { // 多添加一个条件判断 预防用户输入的文字 和 文本框中的默认 value 值 相同时 而被清空
                // console.log(this.defaultValue); // 表示文本框中默认的 value 值 也就是 "请输入关键字"
                this.value = "";
                this.className = "";
            }
            // console.log(e.bubbles); // false => true 表示该事件会冒泡 false 表示该事件不会冒泡
        }

        inp.onblur = function () { // blur 失去焦点时触发
            if (!this.value) { // 若没有文本
                this.value = this.defaultValue;
                this.className = "noinput";
            }
            console.log(e.bubbles); // false
        }

        // 当文本框中没有内容时 显示一些提示文字 这种交互效果 在以前是通过js来实现的 不过现在被 placeholder 这个属性来取代了
        // <input type="text" placeholder="请输入关键字">
    </script>
</body>

</html>






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

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

<body>
    <form action="https://www.taobao.com">
        <p>
            <input type="text" placeholder="请输入账号">
        </p>
        <button>按钮1</button>
        <button>按钮2</button>
    </form>

    <script>
        // 提交数据之前 先检测
        var form = document.querySelector("form");
        var inp = document.querySelector("input");
        form.onsubmit = function(e) {
            console.log(e.bubbles); // true
            if(!inp.value.trim()){
                console.log("请输入账号");
                e.preventDefault();
                // return false; // 若输入框中的内容为空 那么阻止提交
            }
        }
    </script>
</body>

</html>






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

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

<body>
    <select name="" id="sel">
        <option value="1">Lorem.</option>
        <option value="2">Porro?</option>
        <option value="3">A?</option>
        <option value="4">Ad.</option>
        <option value="5">Voluptates?</option>
        <option value="6">Blanditiis!</option>
        <option value="7">Dicta.</option>
        <option value="8">Illum!</option>
        <option value="9">Sint.</option>
        <option value="10">Sunt.</option>
    </select>

    <input type="text" id="txt1">

    <textarea id="txt2"></textarea>

    <script>
        sel.onchange = function() {
            console.log(sel.value, sel.options[sel.selectedIndex].innerHTML);
            // 此时 sel.options 和 sel.children 差不多..
        }

        // txt1.onchange = txt2.onchange = function(){}
        // 触发条件 ==> 1. 输入框中的内容发生改变;  2. 失去焦点;

        txt1.oninput = txt2.oninput = function(e) {
            // e.preventDefault(); // 阻止不了 因为这是内容输入进去 内容发生改变了之后 才触发
            if (!this.value.trim()) {
                console.log("请输入内容");
                return;
            }
            console.log(this.value, e.bubbles);
        }

        // txt1.oninput = txt2.oninput = function(){}
        // 触发条件 ==> 一旦文本框中内容发生改变就会触发
    </script>
</body>

</html>

其他事件

window全局对象

  • load、DOMContentLoaded、readystatechange

window的load:window.onload页面中所有资源全部加载完毕的事件针对图片元素还是比较有用的

图片的load:imgDom.onload图片资源加载完毕的事件图片是异步渲染的

// 这一部分的内容没看懂 ==> 视频区间 ==> (45min,51min)
// 获取图片尺寸
document.addEventListener("DOMContentLoaded", function () {
    var img = document.querySelector("img");
    getImgSize(img, function (size) {
        console.log(size);
    });
})

function getImgSize(img, callback) {
    if (img.width === 0 && img.height === 0) {
        img.onload = function () {
            callback({
                width: img.width,
                height: img.height
            });
        }
    } else {
        callback({
            width: img.width,
            height: img.height
        });
    }
}

浏览器渲染页面的过程:

  1. 得到页面源代码
  2. 创建document节点
  3. 从上到下,将元素依次添加到dom树中,每添加一个元素,进行预渲染因为后续元素可能会影响到前面元素的渲染 比如说前面的元素高度将由后续内容决定的这种情况
  4. 按照结构,依次渲染子节点

document的DOMContentLoaded:document.addElementListener("DOMContentLoaded". function(){}) // 不能使用 dom0 的方式 document.onDOMContentLoaded 来注册 dom树构建完成后发生

readystate: loading、interactive、complete网页加载资源的三种状态

interactive:触发DOMContentLoaded事件

complete:触发window的load事件

常见的面试题 ==> index.js

/* 查 mdn readyState */

console.log(document.readyState); // loading ==> 表示代码正在加载过程中 ==> mdn的解释: loading / 正在加载 document 仍在加载

document.addEventListener("DOMContentLoaded", function () {
    console.log(document.readyState); // interactive ==> 表示 DOM树 已经加载完成  ==> mdn的解释: interactive / 可交互 文档已被解析, "正在加载"状态结束, 但是诸如图像, 样式表和框架之类的子资源仍在加载.
})

window.onload = function () {
    console.log(document.readyState); // complete ==> 表示所有资源已经加载完毕 ==> mdn的解释: complete / 完成 文档和所有子类资源已完成加载 表示load状态的事件即将被触发
}
// 当 document.readyState 的值发生变化时 ==> document 对象上的 readystatechange 事件将被触发
  • css应该写到页面顶部:避免出现闪烁(如果放到页面底部,会导致元素先没有样式,使用丑陋的默认样式,然后当读到css文件后,重新改变样式)
  • JS应该写到页面底部:避免阻塞后续的渲染,也避免运行JS时,得不到页面中的元素。

js代码应该尽量写到页面底部道理很简单 代码都是从上到下依次同步执行的, 如果js代码放前面, 而且代码量还不少的话, 那么用户的体验就是, 一开始一段时间, 看不到页面, 因为这段时间正在加载功能, 功能加载好了之后, 用户才能看到页面; 当然, 这还不是主要的问题, 如果先加载js, 后加载页面元素的话, 那么在加载js时, 执行内部的代码时, 压根就获取不到页面中的元素, 所以... js代码就丢底部就对了

当然也可以将script元素变成异步渲染, 但是不推荐; 因为这么写的话, 就无法确定 js 代码是在什么时候执行的了.

  • unload、beforeunload

beforeunload: window的事件,关闭窗口时运行,可以阻止关闭窗口

unload:window的事件,关闭窗口时运行

这两个事件需要在IE浏览器中测试 chrome浏览器中测试看不到效果 不过后期会用它来实现 在关闭窗口时 发送一些 ajax 请求啥的

  • scroll

窗口发生滚动时运行的事件这个 scroll 事件 不仅可以 写在window上面 还可以写在 元素上 (监听有一定的时间间隔)

若写在元素上 那么需要给对应的元素添加上一条css声明 ==> overflow: scroll;overflow: auto;

通过scrollTop和scrollLeft,可以获取和设置 滚动距离。这两个属性 可读可写

// 得到整个网页的滚动高度 一般采用下面这种写法
document.documentElement.scrollTop + document.body.scrollTop
// [解释] 这是考虑到了兼容性的问题 ==> 在不同浏览器中 或 在同一浏览器的不同版本中 ==> 这俩值必然有一个是 正常的 另一个是 0 所以只要把两个值相加 就能得到需要的值
  • resize

窗口尺寸发生改变时运行的事件,监听的是视口尺寸window 上的属性

==第九节课程中最重要的内容 > 下面的这些尺寸

重视”许愿墙”这个作业; 这个作业里面会涉及到很多的尺寸问题;
js DOM 事件(notes) - 图3
js DOM 事件(notes) - 图4
js DOM 事件(notes) - 图5
js DOM 事件(notes) - 图6

  • contextmenu

右键菜单事件可以阻止默认行为 ==> 弹出右键菜单

看似与鼠标事件重复了 但是需要认识到一点 ==> 打开右键菜单的方式有很多种 并不一定得使用 鼠标右键的方式 ==> 实际上浏览器上的很多操作都是可以直接通过键盘来实现的 不使用鼠标也OKK

  • paste

粘贴事件

  • copy

复制事件

  • cut

剪切事件

[作业讲解]自定义右键菜单

重写后再丢上来

[作业讲解]许愿墙

重写后再丢上来

补充知识

元素位置

  • offsetParent

获取某个元素第一个定位的祖先元素,如果没有,则得到body

body的offsetParent为null

  • offsetLeft、offsetTop

相对于该元素的offsetParent的坐标

如果offsetParent是body,则将其当作是整个网页注意 chrome 浏览器的body 默认带有 margin: 8px

得到某个元素相对于整个网页的位置

function getPagePosition(dom) {
    var left = dom.offsetLeft;
    var top = dom.offsetTop;
    var parent = dom.offsetParent;
    while (parent) {
        left += parent.offsetLeft;
        top += parent.offsetTop;
        parent = parent.offsetParent;
    }
    return {
        x: left,
        y: top
    }
}
  • getBoundingClientRect方法

该方法得到一个对象,该对象记录了该元素相对于视口的距离

[补充1] 对该方法得到的对象中的一些属性的认识:
js DOM 事件(notes) - 图7
js DOM 事件(notes) - 图8

[补充2] function getPagePosition(dom){ } ==> 是相对于整个网页; dom.getBoundingClientRect() ==> 是相对于视口 [也就是说在没有出现滚动条的情况下, 这两者是等效的]

事件模拟

  • click
  • sumbit
  • dispatchEvent
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>事件模拟</title>
</head>

<body>
    <form action="https://www.taobao.com">
        <div style="width:100px; height:100px; background:red"></div>
    </form>

    <button id="btn1">按钮1</button>
    <button id="btn2">按钮2</button>

    <script>
        var div = document.querySelector("div");
        // 通过点击 div 来实现表单的提交
        div.onclick = function() {
            var form = document.querySelector("form");
            form.submit();  // 模拟form表单被提交
        }

        btn1.onclick = function() {
            console.log("按钮1被点击了")
        }

        // 实现效果 ==> 点击按钮2 等价于 点击了 按钮2 和 按钮1
        btn2.onclick = function() {
            console.log("按钮2被点击了");
            btn1.click(); // 模拟按钮1被点击
        }
    </script>
</body>

</html>





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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>事件模拟</title>
</head>

<body>
    <form action="https://www.taobao.com">
        <div style="width:100px; height:100px; background:red"></div>
    </form>

    <button id="btn1">按钮1</button>
    <button id="btn2">按钮2</button>
    <button id="btn3">鼠标移入按钮1</button>
    <button id="btn4">鼠标移出按钮1</button>

    <script>
        var div = document.querySelector("div");
        div.onclick = function() {
            var form = document.querySelector("form");
            form.submit();
        }

        btn1.onclick = function() {
            console.log("按钮1被点击了")
        }

        btn1.onmouseenter = function() {
            this.style.background = "red";
            this.style.color = "#fff";
        }
        btn1.onmouseleave = function() {
            this.style.background = "initial";
            this.style.color = "initial";
        }

        btn2.onclick = function() {
            console.log("按钮2被点击了");
            btn1.click();
        }

        // 实现效果 ==> 点击按钮3 等价于 鼠标移入 按钮1
        btn3.onclick = function() {
            var event = new MouseEvent("mouseenter", {
                bubbles: false
            });
            btn1.dispatchEvent(event);
        }

        // 实现效果 ==> 点击按钮4 等价于 鼠标移出 按钮1
        btn4.onclick = function() {
            var event = new MouseEvent("mouseleave", {
                bubbles: false
            });
            btn1.dispatchEvent(event);
        }
    </script>
</body>

</html>

其他补充

  • window.scrollX、window.pageXOffset、window.scrollY、window.pageYOffset

window.scrollX、window.pageXOffset: 相当于根元素的scrollLeft

window.scrollY、window.pageYOffset: 相当于根元素的scrollTop

  • scrollTo、scrollBy ==> 滚动条位置

scrollTo: 设置滚动条位置window.scrollTo(x,y) 将滚动条的位置设置为 (x,y)

scrollBy: 设置偏移量window.scrollBy(x,y) 在原来的基础上 偏移 (x,y)

  • resizeTo、resizeBy ==> 窗口尺寸

吸附效果(很常见)

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

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>吸附效果</title>
    <style>
        .left {
            float: left;
            width: 70%;
            border: 1px solid;
        }

        .right {
            float: right;
            width: 25%;
            border: 1px solid;
        }

        .container {
            width: 100%;
            height: 300px;
            background: pink
        }
    </style>
</head>

<body>
    <div class="left">lorem10000</div>
    <div class="right">
        <p>lorem1000</p>
        <div class="container"></div>
    </div>

    <script src="./script/index.js"></script>
</body>

</html>
var div = document.querySelector(".container");
var scrollTop = 0;
window.onscroll = function () {
    var rect = div.getBoundingClientRect();
    if (rect.top < 0) {
        div.style.position = "fixed";
        div.style.top = 0;
        div.style.left = rect.left + "px";
        // 这里需要将宽度和高度设置为原来的宽度和高度
        // 按理来说应该是不用重新设置的 但是这可能是涉及到
        // 百分比宽度相对父元素的问题 fixed 在设置 fixed 定位之前
        // 貌似是相对于 div.right 的宽度计算的
        // 但是设置了 fixed 之后 就相对于 body的宽度计算的
        div.style.width = rect.width + "px";
        div.style.height = rect.height + "px";
        // 若吸附元素还带有 padding border 啥的 那么设置一下 box-sizing: border-box 最好
        div.style.boxSizing = "border-box";
        // 记录滚动条的位置 ==> 从啥位置开始吸附的
        scrollTop = window.scrollY + rect.top;
    }
    // 当用户向上滚动 越过吸附位置时 将前面为了令吸附元素吸附而设置的行间样式全部清除
    if (window.scrollY < scrollTop) {
        div.setAttribute("style", "");
    }
}