JavaScript - DOM 事件详解

160 阅读7分钟

这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战

注册事件的其他方法

打点调用onclick

之前介绍过, 这里不再赘述.

    var btn = document.getElementById("btn"); 
    
    btn.onclick = function(){
        alert(1);
    };
    

element.addEventListener() 方法

• element.addEventListener() 方法。

• 参数:

第一个参数:事件类型的字符串(直接书写”click”,不需要加 on)

第二个参数:事件函数

注意:

  • 同一个元素可以多次绑定事件监听,同一个事件类型可以注册多个事件函数, 事件会根据书写的顺序进行一个事件排队

  • 兼容性问题:不支持 IE9 以下的浏览器

    var btn = document.getElementById("btn"); 
    
    // DOM 2 级事件绑定方式
    btn.addEventListener("click",function () {
      alert(1);
    });
    // 多次绑定相同的事件类型,事件会根据书写的顺序进行一个事件排队
    btn.addEventListener("click",clickEvent);
    function clickEvent() {
      alert(2);
    }

element.attachEvent() 方法

• element.attachEvent() 方法。

• 参数:

第一个参数:事件类型的字符串(需要加 on)

第二个参数:事件函数

注意:

同一个元素可以多次绑定事件监听,同一个事件类型可以注册多个事件函数

兼容性问题:只支持 IE10 及以下的浏览器. 但在IE中也有兼容性问题, IE8 及以下的浏览器处理事件队列时,会出现顺序错乱

var btn = document.getElementById("btn");    
    // DOM 2 级事件绑定方式
    // 兼容:IE 10 及以下浏览器
    btn.attachEvent("onclick",function () {
      alert(3);
    });
    btn.attachEvent("onclick",clickEvent);
    function clickEvent() {
      alert(4);
    }

注册事件的兼容写法

自定义一个注册事件函数

• 参数:事件源,事件类型(不加 on),事件函数

IE9 及以上的浏览器,使用 addEventListener() 方法

IE9 以下的浏览器,使用 attachEvent() 方法

判断浏览器时,不需要判断它的版本,可以检测浏览器能力

浏览器能力检测:将某个方法的调用作为 if 语句的判断条件,如果浏览器认识该方法返回

true,否则返回 false。

<body>
  <input type="button" value="点击" id="btn">
  <script>
    var btn = document.getElementById("btn");  
    // 调用函数
    addEvent(btn,"click",function () {
      alert(1);
    }); 
    // DOM 2 级事件绑定方式
    // 自己制作一个兼容所有浏览器的绑定事件的函数
    // 参数:事件源,事件类型,事件函数
    function addEvent(ele,type,fn) {
      // IE 9 及以上的浏览器和其他浏览器,使用 addEventListener 方法
      // 浏览器能力检测
      if (ele.addEventListener) {
        ele.addEventListener(type,fn);
        
        // IE 9 以下的浏览器,使用 attachEvent 方法
      } else if (ele.attachEvent) {
        ele.attachEvent("on" + type,fn);
      }
    }
    </script>
</body>

移除绑定事件

onclick的移除

btn.onclick = null;

element.removeEventListener() 方法

• element.removeEventListener() 方法。

参数:

第一个参数:事件类型的字符串(直接书写”click”,不需要加 on)

第二个参数:事件函数引用名

注意:

没有办法移除一个匿名函数,所以在注册事件时需要单独声明一个有函数名的事件函数。

兼容性问题:不支持 IE9 以下的浏览器

btn.removeEventListener("click",fun);

element.detachEvent() 方法

• element.detachEvent() 方法。

• 参数:

第一个参数:事件类型的字符串(需要加 on)

第二个参数:事件函数

注意:

没有办法移除一个匿名函数,所以在注册事件时需要单独声明一个有函数名的事件函数。

兼容性问题:只支持 IE10 及以下的浏览器

btn.detachEvent("onclick",fun);

移除事件的兼容写法

自定义一个移除事件函数

• 参数:事件源,事件类型(不加 on),事件函数

IE9 及以上的浏览器,使用 removeEventListener 方法

IE9 以下的浏览器,使用 detachEvent 方法

建议:将自己封装的一些常用函数和方法,放到一个单独的 .js 文件中。

<body>
  <input type="button" value="点击" id="btn">
  <script>
    var btn = document.getElementById("btn");  
    // 调用函数
    addEvent(btn,"click",fun); 
    // 移除事件
    removeEvent(btn,"click",fun);
    function fun() {
      alert(1);
    }
    // DOM 2 级事件绑定方式
    // 自己制作一个兼容所有浏览器的绑定事件的函数
    // 参数:事件源,事件类型,事件函数
    function addEvent(ele,type,fn) {
      // IE 9 及以上的浏览器和其他浏览器,使用 addEventListener 方法
      // IE 9 以下的浏览器,使用 attachEvent 方法
      // 浏览器能力检测
      if (ele.addEventListener) {
        ele.addEventListener(type,fn);
      } else if (ele.attachEvent) {
        ele.attachEvent("on" + type,fn);
      }
    }

    // 兼容所有浏览器的 解除绑定事件的函数
    // 参数:事件源,事件类型,事件函数
    function removeEvent(ele,type,fn) {
      // 浏览器能力检测
      if (ele.removeEventListener) {
        ele.removeEventListener(type,fn);
      } else if (ele.detachEvent) {
        ele.detachEvent("on" + type,fn);
      }
    }
    </script>
</body>

DOM 事件流

image.png

捕获: 从祖先级流向子级

冒泡: 从子级流向祖先级

先捕获后冒泡

事件流的三个阶段

• 第一个阶段:事件捕获

• 第二个阶段:事件执行过程

• 第三个阶段:事件冒泡

addEventListener()

addEventListener 有第三个参数,用来决定事件流的方向. 参数值是布尔类型的值.

addEventListener() 第三个参数为 false 时,事件冒泡

addEventListener() 第三个参数为 true 时,事件捕获

参数默认值是 false

onclick

onclick 类型:只能进行事件冒泡过程,没有捕获阶段

attachEvent()

attachEvent() 方法:只能进行事件冒泡过程,没有捕获阶段

事件委托

利用事件冒泡,将子级的事件委托给父级加载

同时,需要利用事件函数的一个 e 参数,内部存储的是事件对象. 只要触发事件,函数内部都可以得到一个事件对象,对象中存储了关于事件的一系列数据. e.target 属性记录的就是真正触发事件的事件源.

<body>
    <ul id="list">
        <li>刘亦菲</li>
        <li>杨幂</li>
        <li>唐嫣</li>
        <li>赵丽颖</li>
        <li>刘诗诗</li>
    </ul>
    <script>
        // 让每个 li 被点击后,自己添加特殊的背景色,而其他兄弟不添加
        // 以前的思路:获取所有的 li 标签元素,批量添加事件
        // 事件委托:可以将一些子级的公共类型的事件委托给他们的父级添加,在父级内部想办法找到真正触发事件的最底层的事件源
        // 获取元素
        var list = document.getElementById("list");
        var lis = list.children;
        // 给 ul 添加点击事件
        list.onclick = function (e) {
            // 在内部要想办法找到真正触发事件的 li
            // 借用事件函数内部的一个参数 e,e 是事件对象
            // 只要触发事件,函数内部都可以得到一个事件对象,对象中存储了关于事件的一系列数据
            // e.target 属性记录的就是真正触发事件的事件源
            
            // 排除其他
            for (var i = 0 ; i < lis.length ; i++) {
                lis[i].style.backgroundColor = "";
            }
            e.target.style.backgroundColor = "pink";
        };
    </script>
</body>

事件对象

只要触发事件,就会有一个对象,内部存储了与事件相关的数据。

e 在低版本浏览器中有兼容问题,低版本浏览器使用的是 window.event

事件对象常用的属性:

e.eventPhase 查看事件触发时所处的阶段, 阶段是用数字表示的

( 1: 捕获阶段 2: 目标阶段 3:冒泡阶段 )

e.target 用于获取触发事件的元素

e.srcElement 用于获取触发事件的元素,低版本浏览器使用

e.currentTarget 用于获取绑定事件的事件源元素, 也就是this的指代

e.type 获取事件类型 (没有on)

client系列: 客户端尺寸,点击的点参考浏览器窗口左上角的距离

e.clientX/e.clientY 所有浏览器都支持,返回鼠标点击位置距离浏览器窗口左上角的距离 (可以用于设置图片随鼠标移动等)

page 系列:html 页面尺寸,点击的点参考html文档左上角的距离

e.pageX/e.pageY IE8 以前不支持,鼠标点击位置距离整个HTML页面左上顶点的距离

例1 e.target e.currentTarget this 之间的关系:

<body>
    <div id="box1">
        <div id="box2">
            <div id="box3"></div>
        </div>
    </div>
    <script>
        // 获取元素
        var box1 = document.getElementById("box1");
        var box2 = document.getElementById("box2");
        var box3 = document.getElementById("box3");
        
        // 添加事件
        box1.onclick = function (e) {
            // e指的就是存储事件对象的参数,只要事件被触发,e就会自动接收数据
            // 兼容问题
            e = e || window.event;

            // 获取真正触发事件的元素
            var target = e.target || e.srcElement;
            console.log(target);
            // 获取绑定事件的事件源元素
            console.log(e.currentTarget);
            // this 指向
            console.log(this);
        };
        
    </script>
</body>

当点击box3这个元素时, 控制台输出如下:

image.png

可以看出:

e.target 指的是鼠标直接点击的元素

e.currentTarget 指的是绑定事件的事件源元素

this 与e.currentTarget一样, 也是指绑定事件的事件源元素

例2 e.type

        // 很多时候可能给同一个元素对象添加不同的事件类型,对应执行的事件函数内部的代码不同
        //按照以前的方法需要分别写两个函数
        
        box1.onmouseover = function () {
            box1.style.backgroundColor = "skyblue";
        };
        box1.onmouseout = function () {
            box1.style.backgroundColor = "yellowgreen";
        };
        

如果使用e.type, 就可以简化. 可以将所有给一个元素绑定的事件的事件函数写在一个函数内,通过函数内部的e.type 判断走不同的分支, 避免添加多个函数,占用更多的内存.

        //两种时间都调用同一个函数
        box1.onmouseover = fn;
        box1.onmouseout = fn;
        
        function fn(e) {
            e = e || window.event;
            
            // 根据事件类型,执行不同的代码
            switch (e.type) {
                case "mouseover":
                    box1.style.backgroundColor = "pink";
                    break; 
                case "mouseout":
                    box1.style.backgroundColor = "yellowgreen";  
                    break;                   
            }
        }

取消默认行为和阻止事件传播的方式

• e.preventDefault() 取消默认行为

• e.returnValue 取消默认行为,低版本浏览器使用, 高版本浏览器也能认识

• e.stopPropagation(); 阻止冒泡,标准方式

• e.cancelBubble = true; 阻止冒泡,IE 低版本,(标准中已废弃), 高版本中浏览器也认识

例1 取消默认行为

<body>
  <a id="link" href="https://juejin.cn/">点击</a>
  <script>
    var link = document.getElementById('link');
    
    link.onclick = function (e) {
      e = e || window.event;
      alert('hello');
      
      // 普通的方式阻止默认行为
      return false;
      // DOM 的方法
      e.preventDefault();
      
      // 低版本浏览器需要使用一个对象的属性
      e.returnValue = false;
    }
  </script>
</body>

例2 阻止冒泡

<body>
  <div id="box1">
    <div id="box2">
      <div id="box3">
      </div>
    </div>
  </div>
  <script>
    // 事件冒泡
    var box1 = document.getElementById('box1');
    var box2 = document.getElementById('box2');
    var box3 = document.getElementById('box3');

    var array = [box1, box2, box3];

    for (var i = 0; i < array.length; i++) {
      var box = array[i];
      box.onclick = function (e) {
        e = e || window.event;
        console.log(this.id);
        
        // 阻止事件冒泡
        e.stopPropagation();
        // 低版本浏览器使用属性
        e.cancelBubble = true;
      }
    }
   

  </script> 

其他事件类型

MDN web 事件参考:developer.mozilla.org/zh-CN/docs/…

DOM 特效

DOM 提供了一套与元素自身有关的位置和大小的属性。

偏移量属性

• offsetParent 偏移参考父级,距离自己最近的有定位的父级,如果没有定位参考body(html)

• offsetLeft/offsetTop 偏移位置 (如果父元素有定位, 以父元素为参考; 如果父元素没有定位, 参考body)

• offsetWidth/offsetHeight 偏移大小 (指的是元素自身border以内的宽度和高度, 包括width, padding和border)

image.png

客户端大小

• client 系列没有参考父级元素。

• clientLeft/clientTop 边框区域尺寸,不常用

• clientWidth/clientHeight 边框内部大小 (不包括border)

image.png

滚动偏移属性

• scrollLeft/scrollTop 盒子内部滚动出去的尺寸

• scrollWidth/scrollHeight 盒子内容的宽度和高度(scrollWidth只加了一个内边距, scrollHeight上下两个内边距都加了)

注意: 谷歌浏览器不识别 window.scrollTop, 可以使用它的兼容写法 document.documentElement.scrollTop 来获取.

image.png

比如有下面一个结构:

<div id="box">
    <p></p>
  </div>

它们的属性分别为:

#box {
      width: 200px;
      height: 200px;
      margin: 50px;
      border: 30px solid red;
      padding: 40px;
      background-color: green;
      overflow: auto;
    }

    #box p {
      width: 300px;
      height: 300px;
      background-color: pink;
    }

这时候scrollWidth和scrollHeight的值分别为:

      console.log(box.scrollWidth);
      console.log(box.scrollHeight);

image.png

340 = 300 (p的width) + padding-left

380 = 300 (p的width) + padding-top + padding-bottom