JavaScript初级篇——DOM事件

796 阅读6分钟

一、注册事件

给元素添加事件称为注册事件或绑定事件,有两种方式:传统注册方式与监听注册方式

1-1、传统注册方式

  • 利用on开头的事件
  • 注册事件有唯一性(同一个元素同一个事件只能设置同一个处理函数,最后注册的处理函数会覆盖前面注册的处理函数
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div {
        height: 200px;
        width: 200px;
        background: salmon;
      }
      p {
        width: 100px;
        height: 100px;
        background: springgreen;
      }
    </style>
  </head>
  <body>
    <div>
      <p></p>
    </div>
    <script>
      var div = document.querySelector('div');
      // 当点击div时,只会输出2
      // 事件绑定 下面的会覆盖上面的
      div.onclick = function() {
        console.log(1);
      }
      div.onclick = function() {
        console.log(2);
      }
    </script>
  </body>
</html>

1-2、监听注册方式

  • W3C标准,推荐方式
  • 使用addEventListener()方法(IE9之前不支持,可使用attchEvent()代替)
  • 同一个元素同一个事件可以注册多个监听器,按注册顺序依次执行:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div {
        height: 200px;
        width: 200px;
        background: salmon;
      }
      p {
        width: 100px;
        height: 100px;
        background: springgreen;
      }
    </style>
  </head>
  <body>
    <div>
      <p></p>
    </div>
    <script>
      var div = document.querySelector('div');
      // 通过addEventListener绑定事件不会覆盖,会依次执行,依次输出1 2
      div.addEventListener('click', function(){
        console.log(1);
      });
      div.addEventListener('click', function() {
        console.log(2);
      });
    </script>
  </body>
</html>

执行事件流有两种方式:事件冒泡(默认方式)事件捕获

语法:addEventListener(type, listener[, options|useCapture])

  • type: 事件名(不需要加on)
  • listener:执行函数
  • 第三个参数可以是布尔值,也可以是一个对象:
    • options:是一个对象,表示事件监听相关配置,包含以下属性:
      • capture:是否再捕获阶段执行(true/false)
      • once:是否只执行一次(true/false)
      • passive:阻止取消默认事件(true/ false): true不阻止默认事件,false阻止默认事件
    • useCapture:是否捕获执行(布尔值):true捕获执行,false不捕获执行也就是冒泡执行
      • true:开启捕获执行,执行顺序由大到小 如body > div > p(要给对应元素的对应事件都加上true才行,不然不会按照期望的捕获方式去执行)
      • false:不捕获执行,也就是冒泡执行,冒泡的执行顺序,由小到大:p > div > body

1-2-1、捕获执行

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div {
        height: 200px;
        width: 200px;
        background: salmon;
      }
      p {
        width: 100px;
        height: 100px;
        background: springgreen;
      }
    </style>
  </head>
  <body>
    <div>
      <p></p>
    </div>
    <script>
      var div = document.querySelector('div');
      var p = document.querySelector('p');

      // 开启捕获执行,点击p标签时,依次会输出 body div p
      // 注意!要为每个元素的对应事件都要开启捕获,不然不会按照预期执行
      document.body.addEventListener('click', function() {
        console.log('body');
      }, true);
      div.addEventListener('click', function() {
        console.log('div');
      },true);
      p.addEventListener('click', function() {
        console.log('p');
      },true);
			// 或这样写:
      // document.body.addEventListener('click', function() {
      //   console.log('body');
      // }, {
      //   capture: true
      // });
      // div.addEventListener('click', function() {
      //   console.log('div');
      // },{
      //   capture: true
      // });
      // p.addEventListener('click', function() {
      //   console.log('p');
      // },{
      //   capture: true
      // });
    </script>
  </body>
</html>

1-2-2、冒泡执行

// 冒泡执行,点击p时,依次输出p div
// 再次点击,只输出p,因为div开启了只执行一次
div.addEventListener('click', function() {
  console.log('div');
},{
  once: true
});
p.addEventListener('click', function() {
  console.log('p');
});

二、删除事件

  1. 删除传统注册方式事件

如:div.onclick = null

  1. 删除监听注册方式事件

removeEventListener(事件名,要移除的绑定的函数)

  • 要注意,要保证保定函数与要移除的函数是同一个
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div {
        width: 100px;
        height: 100px;
        background: springgreen;
      }
    </style>
  </head>
  <body>
    <div></div>
    <input type="button" value="取消事件">
    <script>
      var div = document.querySelector('div');
      var inp = document.querySelector('input');

      function fn() {
        console.log('111');
      }

      div.addEventListener('click', fn);

      inp.addEventListener('click', function() {
        div.removeEventListener('click', fn);
      });
    </script>
  </body>
</html>

三、DOM事件流

事件流描述的是从页面中接收事件的顺序。

事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即DOM事件流

DOM事件流分为3个阶段:

  1. 捕获阶段
  2. 当前目标阶段
  3. 冒泡阶段

比如,给div绑定了一个点击事件:

注意:

  1. JS代码中只能执行捕获或冒泡其中的一个阶段
  2. onclick只能得到冒泡阶段
  3. addEventListener的第三个参数如果是true,表示事件捕获阶段调用事件处理程序;如果是false,表示事件冒泡阶段调用事件处理程序
  4. 有些事件是没有冒泡的,如:onblur、onfocus、onmouseenter、onmouseleave

四、事件对象

事件对象event中包含了事件发生后,跟该事件相关的一系列信息数据的集合

事件对象的常见属性和方法:

事件对象属性与方法说明
e.target返回触发事件的对象
e.currentTarget事件绑定的元素
e.srcElement返回触发事件的对象(非标准,ie6-8)
e.type返回事件的类型(如:click mouseover),不带on
e.cancelBubble阻止事件冒泡(非标准,ie6-8)
e.returnValue阻止默认事件(非标准,ie6-8)
e.preventDefault()阻止默认事件,标准
e.stopPropagation()阻止事件冒泡,标准
  • 事件对象本身的获取存在兼容问题:
    1. 在标准浏览器中给方法传递的参数,只需要定义形参e就可以获取到
    2. 在IE6~8中,浏览器不会给发给发传递参数,需要到window.event中获取

解决: e = e || window.event;

  • e.target & e.currentTarget

    1. target:事件触发的目标源元素
    2. currentTarget: 事件绑定的元素
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div {
        height: 200px;
        width: 200px;
        background: salmon;
        margin: 100px auto;
      }
      p {
        width: 100px;
        height: 100px;
        background: springgreen;
      }
    </style>
  </head>
  <body>
    <div>
      <p></p>
    </div>
    <script>
      {
        var div = document.querySelector('div');
        var p = document.querySelector('p');

        div.addEventListener('click', function(e) {
          // 点击p标签所在的区域会打印出<p></p>及其中的内容
          // 点击p标签之外的区域会打印出<div></div>及其中的内容
          console.log(e.target);
          // 点击p标签之外的区域会打印出<div></div>及其中的内容
          // 点击p标签所在的区域也还会会打印出<div></div>及其中的内容
          console.log(e.currentTarget);
        })
      }
    </script>
  </body>
</html>

五、阻止事件冒泡

  • 标准写法:e.stopPropagation()
  • 非标准写法(ie6-8): e.cancelBubble = true;

阻止事件冒泡的兼容解决方案:

if(e && e.stopPropagation){
  e.stopPropagation();
}else{
  window.event.cancelBubble = true;
}

六、事件委托(代理、委派)

利用事件冒泡的特性,为一个元素添加了某个事件,相当于其子元素也会触发相应的事件

  • 优点:

    1. 可减少需要添加事件绑定的元素
    2. 可给新增DOM元素添加事件
    3. 只操作一次DOM,提高性能

如下面的例子,不是每个子节点都要单独设置事件监听器,而是事件监听器设置在其父节点上,然后利用冒泡原理影响设置的每个子节点,点击li也会触发ul的click,只给ul添加click即可:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      li {
        width: 100px;
        height: 100px;
        background: springgreen;
        margin: 50px auto;
      }
      p {
        width: 50px;
        height: 50px;
        background: tomato;
      }
    </style>
  </head>
  <body>
    <div>
      <ul>
        <li>
          <p></p>
        </li>
        <li>
          <p></p>
        </li>
        <li>
          <p></p>
        </li>
        <li>
          <p></p>
        </li>
        <li>
          <p></p>
        </li>
      </ul>
    </div>
    <script>
      {
        var ul = document.querySelector('ul');

        ul.addEventListener('click', function(e) {
          if (e.target.tagName ===  'LI') {
            e.target.style.background = 'yellow';
          } 
        });
      }
    </script>
  </body>
</html>

七、常用的鼠标事件

7-1、常用鼠标事件

鼠标事件触发条件
onclick鼠标点击左键触发
onmouseover鼠标经过触发
onmouseout鼠标离开触发
onmouseenter鼠标移入时触发
onmouseleave鼠标移出时触发
onfocus获得鼠标焦点触发
onblur失去鼠标焦点触发
onmousemove鼠标移动触发
onmouseup鼠标按键弹起触发
onmousedown鼠标按下触发
oncontextmenu鼠标右击触发
onselectstart鼠标选中触发

7-1-1、对比mouseover mouseout与mouseenter mouseleave

  • mouseover与mouseout会受子元素影响:
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div
      {
        width: 200px;
        height: 200px;
        background: tomato;
        margin: 100px auto;
      }
      p 
      {
        width: 100px;
        height: 100px;
        background: springgreen;
        margin: 50px;
      }
    </style>
  </head>
  <body>
    <div>
      <p></p>
    </div>
    <script>
      const div = document.querySelector('div');

      /*
      当移入div时会打印出:移入了
      当移入div中子元素p时会同时打印出: 移出了移入了
      当移出div中子元素p但还未移出div时会同时打印出:移出了移入了
      当移出div时会打印出:移出了

      即: 如果鼠标移入和移出子元素的范围也会触发:mouseover和mouseout
    */
      div.addEventListener('mouseover', function() {
        console.log('移入了');
      });
      div.addEventListener('mouseout', function() {
        console.log('移出了');
      })
    </script>
  </body>
</html>
  • mouseenter与mouseleave不会受子元素影响:
/*
      mouseenter 和 mouseleave 不受子元素范围干扰
    */
div.addEventListener('mouseenter', function() {
  console.log('移入了');
});
div.addEventListener('mouseleave', function() {
  console.log('移出了');
})

7-1-2、禁止鼠标右键菜单

document.addEventListener('contextmenu', function(e) {
  e.preventDefault();
})

7-1-3、禁止鼠标选中

document.addEventListener('selectstart', function(e) {
  e.preventDefault();
})

7-2、鼠标事件对象

鼠标事件对象说明
e.clientX返回鼠标相对于浏览器窗口可视区的X坐标
e.clientY返回鼠标相对于浏览器窗口可视区的Y坐标
e.pageX返回鼠标相对于文档页面的X坐标(IE9+)
e.pageY返回鼠标相对于文档页面的Y坐标(IE9+)
e.screenX返回鼠标相对于电脑屏幕的X坐标
e.screenY返回鼠标相对于电脑屏幕的Y坐标

7-2-1、鼠标跟随案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div
      {
        width: 200px;
        height: 200px;
        background: palegreen;
        position: absolute;
        top: 0;
        left: 0;
      }
    </style>
  </head>
  <body style="height: 3000px;">
    <div></div>
    <script>
      var div = document.querySelector('div');
      // 鼠标移动触发事件: mousemove
      document.addEventListener('mousemove', function(e) {
        div.style.left = e.pageX + 'px';
        div.style.top = e.pageY + 'px';
      })
    </script>
  </body>
</html>

7-2-2、鼠标右键事件(自定义菜单)

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      * {margin: 0; padding: 0; list-style: none;}
      ul {
        width: 200px;
        border: 1px solid #000;
        position: absolute;
        top: 0;
        left: 0;
        display: none;
      }
      li {
        padding: 10px;
        border: 1px solid #333;
      }
    </style>
  </head>
  <body>
    <ul>
      <li>刷新</li>
      <li>跳转</li>
      <li>加载</li>
      <li>首页</li>
    </ul>
    <script>
      const ul = document.querySelector('ul');
      document.addEventListener('contextmenu', function(e) {
        ul.style.display = 'block';
        const x = e.pageX;
        const y = e.pageY;
        ul.style.left = x + 'px';
        ul.style.top = y + 'px';
        // 阻止默认行为,阻止弹出默认的菜单
        e.preventDefault();
      });

    </script>
  </body>
</html>

7-2-3、鼠标拖拽案例

拖拽涉及到如下事件:

  • 鼠标按下:mousedown
  • 鼠标移动:mousemove
  • 鼠标抬起:mouseup
  1. 原理:元素初始位置x y + 鼠标移动的x y
  • 鼠标移动的x y 可通过当前鼠标位置 - 初始鼠标位置获得
  1. 实现步骤

    1. 保留鼠标的初始位置与元素的初始位置
    2. 鼠标移动过程中不断改变元素的位置
    3. 获取鼠标移动的距离(当前位置 - 初始位置)
    4. 计算元素的最新位置(初始位置 + 移动距离)
    5. 鼠标抬起时,清除移动事件
  2. 注意:鼠标移动事件要加载document上,而不是div上,防止鼠标移动过快甩出div区域时,div就不受控制了

  3. 代码示例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div {
        width: 100px;
        height: 100px;
        background: palegreen;
        position: absolute;
      }
    </style>
  </head>
  <body>
    <div></div>
    <script>
      const div = document.querySelector('div');

      // 1. 鼠标点击的位置
      const startPos = {}
      // 2. 元素的初始位置
      const boxPos = {}

      div.addEventListener('mousedown',function(e){
        startPos.x = e.clientX;
        startPos.y = e.clientY;
        boxPos.x = parseFloat(getComputedStyle(div)['left']);
        boxPos.y = parseFloat(getComputedStyle(div)['top']);

        // 鼠标移动过程中改变元素的位置
        document.addEventListener('mousemove',drag)

        // 清除鼠标移动事件
        document.addEventListener('mouseup',function(){
          // 清除事件 - 必须清除的是命名函数!
          document.removeEventListener('mousemove',drag)
        },{
          // 只绑定一次事件
          once:true
        })
      })

      function drag(e){
        // console.log('move')
        // 当前位置
        var nowPos = {
          x:e.clientX,
          y:e.clientY
        }

        // 当前鼠标位置 - 初始鼠标位置 = 移动距离
        var dis = {
          x:nowPos.x - startPos.x,
          y:nowPos.y - startPos.y
        }

        // 移动距离+元素的初始位置 = 元素最新的位置
        var newPos = {
          l:dis.x + boxPos.x,
          t:dis.y + boxPos.y
        }

        // 限制范围 - 浏览器可视区
        if(newPos.l < 0){
          newPos.l = 0;
        }

        // 限制右侧

        // 浏览器可视区宽度 - 自身宽度  = 最大left
        var maxL = document.documentElement.clientWidth - div.offsetWidth;
        if(newPos.l > maxL){
          newPos.l = maxL;
        }


        if(newPos.t < 0){
          newPos.t = 0;
        }

        // 限制下侧

        // 浏览器可视区高度 - 自身高度  = 最大top
        var maxT = document.documentElement.clientHeight - div.offsetHeight;
        if(newPos.t > maxT){
          newPos.t = maxT;
        }

        // 修改样式
        div.style.top = newPos.t + 'px';
        div.style.left = newPos.l + 'px';
        // css(div,'top',newPos.t)
        // css(div,'left',newPos.l)
      }
    </script>
  </body>
</html>

八、常用的键盘事件

8-1、常用键盘事件

键盘事件触发条件
onkeyup某个键盘按键被松开时触发
onkeydown某个键盘按键被按下时触发
onkeypress某个键盘按键被按下时并弹起时触发
  • 添加键盘事件时要添加给文档document
  • onkeypress和前两个事件区别是:它不识别功能键,如左右箭头、shift等
  • onkeydown与onkeyup不区分字母大小写(无论是否开启了大小写,获取keyCode时,都返回大写字母的ASCII值),onkeypress区分字母大小写

8-2、键盘事件对象常用属性

常用属性描述
keyCode键码,返回该键的ASCII值
key键值,如"a" "b" "enter"
ctrlKey是否按下ctrl键,布尔值
altKey是否按下alt键,布尔值
shiftKey是否按下shift键,布尔值
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>

    </style>
  </head>
  <body>

    <script>
      document.addEventListener('keypress', function(e) {
        // console.log(e); // keyboardEvent
        console.log(e.keyCode, e.key);
        console.log(e.ctrlKey);
      })
    </script>
  </body>
</html>

8-3、键盘事件案例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
      div {
        width: 200px;
        height: 200px;
        background: palegreen;
        position: absolute;
      }
    </style>
  </head>
  <body>
    <div></div>
    <script>
      var div = document.querySelector('div');
      // 通过上下左右控制元素位置
      /*
      keyCode
      左 : 37
      上 : 38
      右 : 39
      下 : 40
    */
      document.addEventListener('keydown', function(e) {
        if (e.keyCode == 39) {
          let l = parseFloat(getComputedStyle(div).left);
          div.style.left = l + 5 + 'px';
          // css(div, 'left', l+5);
        }

        if (e.keyCode == 37) {
          let l = parseFloat(getComputedStyle(div).left);
          div.style.left = l - 5 + 'px';
        }

        if (e.keyCode == 38) {
          let l = parseFloat(getComputedStyle(div).top);
          div.style.top = l - 5 + 'px';
        }

        if (e.keyCode == 40) {
          let l = parseFloat(getComputedStyle(div).top);
          div.style.top = l + 5 + 'px';
        }
      })

    </script>
  </body>
</html>