DOM事件高级

206 阅读5分钟

事件对象

获取事件对象

●事件对象是什么

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

●如何获取

在事件绑定的回调函数的第一个参数就是事件对象 一般命名为event、ev、e

语法:

<script>
	元素.addEventListener('click',function(e事件对象){
})
</script>

事件对象常用属性

●部分常用属性

type:获取当前的事件类型

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

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

key:用户按下的键盘键的值,现在不提倡使用keyCode

●鼠标跟随案例

分析: ①:鼠标在页面中移动,用到 mousemove 事件 ②:不断把鼠标在页面中的坐标位置给图片left和top值即可

代码:

<body>
  <img src="./images/tianshi.gif" alt="" />

  <script>
    let img = document.querySelector('img')
    document.addEventListener('mousemove', function (e) {
      console.log(e.clientX, e.clientY)

      // 将鼠标的相对于视口的坐标位置赋值给img标签的top和left(图片高度的一半)
      img.style.left = e.clientX - img.offsetWidth / 2 + 'px'
      img.style.top = e.clientY - img.offsetHeight / 2+ 'px'
    })

  </script>
</body>

●按下回车键发布微博案例

分析: ①:用到按下键盘事件 keydown 或者 keyup 都可以 ②:如果用户按下的是回车键盘,则发布信息 ③:按下键盘发布新闻,其实和点击发布按钮效果一致 send.click()

代码:

 <script>
 //(以上内容省略,在DOM节点操作查看)
// 为输入框添加按键事件,当按下的键是enter键时,实现发布内容
    area.addEventListener('keyup',function(){
      // 判断是否按下了enter键
      if(e.key==='Enter'){
        // 实现发布功能
        send.click()
      }
    })
</script>

事件流

事件流与两个阶段说明

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

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

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

事件捕获和事件冒泡

●事件冒泡概念:

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

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

●事件冒泡是默认存在的

代码:

<script>
    let father = document.querySelector('.father')
    let son = document.querySelector('.son')

    father.addEventListener('click', function () {
      console.log('father'),
        true
    })

    son.addEventListener('click', function () {
      console.log('son'),
        true
    })

    document.body.addEventListener('click', function () {
      console.log('body'),
        true
    })
  </script>

●事件捕获概念:

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

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

●代码:

<script>
	DOM.addEventListener(事件类型,事件处理函数,是否使用捕获机制)
</script>

●说明:

1、addEventListener第三个参数传入true代表是捕获阶段触发(很少使用)

2、若传入false代表冒泡阶段触发,默认就是false

3、若是用 L0 事件监听,则只有冒泡阶段,没有捕获

阻止事件流动

●因为默认就有冒泡模式的存在,所以容易导致事件影响到父级元素

●若想把事件就限制在当前元素内,就需要阻止事件流动

●阻止事件流动需要拿到事件对象

语法:

<script>
	事件对象.stopPropagation()
</script>

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

代码:

<script>
      let father = document.querySelector('.father')
      let son = document.querySelector('.son')
      // 事件捕获
      father.addEventListener('click', function () {
      console.log('father')
    })

    son.addEventListener('click', function (e) {
      // 阻止事件冒泡
      e.stopPropagation()
      console.log('son')
    })

    document.body.addEventListener('click', function () {
      console.log('body')
    })
    </script>

mouseover和mouseenter区别

●鼠标经过事件:

mouseover 和 mouseout 会有冒泡效果

mouseenter 和 mouseleave 没有冒泡效果(推荐)

代码:

<script>
      let father = document.querySelector('.father')
      let son = document.querySelector('.son')
      // 事件捕获
      father.addEventListener('mouseover', function() {
        console.log('father-mouseover')
      })
      father.addEventListener('mouseout', function() {
        console.log('father-mouseout')
      })

      son.addEventListener('mouseover', function(e) {
        console.log('son-mouseover')
      })
      son.addEventListener('mouseout', function(e) {
        console.log('son-mouseout')
      })
    </script>

阻止默认行为

比如链接点击不跳转,表单域的不提交

语法:

<script>
	e.preventDefault()
</script>

代码:

<body>
    <a href="http://www.baidu.com">跳转到百度</a>

    <script>
      let a = document.querySelector('a')
      a.addEventListener('click', function(e) {
        //   阻止元素的默认行为
        e.preventDefault()
        console.log(123)
      })
    </script>
  </body>

事件的解绑

●两种注册事件的区别:

​ 传统on注册(L0)

​ 1、同一个对象,后面注册的事件会覆盖前面注册(同一个事件)

​ 2、直接使用null覆盖偶就可以实现事件的解绑

​ 3、都是冒泡阶段执行的

​ ●事件监听注册(L2)

​ 1、语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)

​ 2、后面注册的事件不会覆盖前面注册的事件(同一个事件)

​ 3、可以通过第三个参数去确定是在冒泡或者捕获阶段执行

​ 4、必须使用removeEventListener(事件类型, 事件处理函数, 获取捕 获或者冒泡阶段)

​ 5、匿名函数无法被解绑

​ 代码:

<body>
    <button>按钮</button>
    <script>
      let btn = document.querySelector('button')

      // btn.onclick = function() {
      //   console.log(123)
      //   btn.onclick = null
      // }

      function deal() {
        console.log(123)
        btn.removeEventListener('click', deal)
      }

      btn.addEventListener('click', deal)
    </script>
  </body>

事件委托

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

●总结: 1、优点:给父级元素加事件(可以提高性能)

​ 2、原理:事件委托其实是利用事件冒泡的特点, 给父元素添加事件, 子元素可以触发

​ 3、实现:事件对象.target 可以获得真正触发事件的元素

代码:

<body>
    <button>添加li元素</button>
    <ul>
      <li class="a">我是第1个小li</li>
      <li class="a">我是第2个小li</li>
      <li class="a">我是第3个小li</li>
      <li class="a">我是第4个小li</li>
      <li class="a">我是第5个小li</li>
    </ul>

    <script>
      // 需求:单击li元素,为其添加背景色

      //   这里获取元素的时候,只能获取到页面已存在的li元素,对于未来的li无法获取到
      let lis = document.querySelectorAll('li')
      let button = document.querySelector('button')
      let ul = document.querySelector('ul')

      button.addEventListener('click', function() {
        let newLi = document.createElement('li')
        newLi.innerText = '我是新的li元素'
        newLi.className = 'a'
        ul.appendChild(newLi)
      })

      // 事件捕获:单击ul区域,它会传递给子元素进行响应

      // 子响应响应事件之后会将事件冒泡给父元素,如果父元素有同名事件,那么就会触发
      document.body.addEventListener('click', function(e) {
        // e.target:真正触发事件的元素,说白了,就是你当前单击的元素
        console.dir(e.srcElement.localName)
        // 判断当前触发事件的元素是否是我想要的元素
        if (e.target.className === 'a') {
          e.target.style.background = 'red'
        }
      })
    </script>
  </body>

综合案例

●渲染学生信息案例

需求:点击录入按钮,可以增加学生信息

●分析:

​ 不管添加还是删除,都是操作的数据(数组),然后从新渲染页面

​ 需求①:添加数据 ​ 1、点击录入按钮,把表单里面的值都放入数组里面 ​ 2、学号自动生成,是数组最后一个数据的学号+1

​ 需求②:渲染 ​ 1、把数组的数据渲染到页面中,同时清空表单里面的值,下拉列 表的值复原 ​ 2、注意,渲染之前,先清空以前渲染的内容,因为多次渲染,最 好封装为函数

​ 需求③:删除数据 ​ 1、为了提高性能,最好使用事件委托方式,找到点击的是链 e.target.tagName

​ 2、根据当前的删除链接,找到这条数据 ​ 3、需要得到当前数据的索引号,可以渲染a的时候,把当前索引 号给 id属性,然后通过 e.target.id 来获取,然后使用 splice来删 除对应数据,重新渲染

代码:

<!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>
    <link rel="stylesheet" href="css/user.css" />
  </head>

  <body>
    <h1>新增学员</h1>
    <div class="info">
      姓名:<input type="text" class="uname" /> 年龄:<input
        type="text"
        class="age"
      />
      性别:
      <select name="gender" id="" class="gender">
        <option value="男"></option>
        <option value="女"></option>
      </select>
      薪资:<input type="text" class="salary" /> 就业城市:<select
        name="city"
        id=""
        class="city"
      >
        <option value="北京">北京</option>
        <option value="上海">上海</option>
        <option value="广州">广州</option>
        <option value="深圳">深圳</option>
        <option value="曹县">曹县</option>
      </select>
      <button class="add">录入</button>
    </div>

    <h1>就业榜</h1>
    <table>
      <thead>
        <tr>
          <th>学号</th>
          <th>姓名</th>
          <th>年龄</th>
          <th>性别</th>
          <th>薪资</th>
          <th>就业城市</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody></tbody>
    </table>
    <script>
      // 0.获取元素
      let tbody = document.querySelector('tbody')
      let add = document.querySelector('.add')
      // 获取几个表单元素
      let uname = document.querySelector('.uname')
      let age = document.querySelector('.age')
      let gender = document.querySelector('.gender')
      let salary = document.querySelector('.salary')
      let city = document.querySelector('.city')

      //  1. 准备好数据后端的数据
      let arr = [
        {
          stuId: 1001,
          uname: '欧阳霸天',
          age: 19,
          gender: '男',
          salary: '20000',
          city: '上海'
        },
        {
          stuId: 1002,
          uname: '令狐霸天',
          age: 29,
          gender: '男',
          salary: '30000',
          city: '北京'
        },
        {
          stuId: 1003,
          uname: '诸葛霸天',
          age: 39,
          gender: '男',
          salary: '2000',
          city: '北京'
        }
      ]

      // 1.实现数据的动态渲染
      function init() {
        // 1.1 定义一个就是用于拼接html结构
        let htmlStr = ''
        // 1.2 遍历数组,生成动态结构
        arr.forEach(function(value, i) {
          htmlStr += `<tr>
                      <td>${value.stuId}</td>
                      <td>${value.uname}</td>
                      <td>${value.age}</td>
                      <td>${value.gender}</td>
                      <td>${value.salary}</td>
                      <td>${value.city}</td>
                      <td>
                        <a href="javascript:" class='del' id='${i}'>删除</a>
                      </td>
                    </tr>`
        })
        // 1.3 将生成的动态结构填充到指定的标签中
        tbody.innerHTML = htmlStr
      }
      init()

      // 2.实现数据的添加
      add.addEventListener('click', function() {
        // 用户输入的验证
        // if (uname.value.trim().length == 0) {
        //   alert('请输入用户名')
        //   uname.focus()
        //   return
        // }
        // if (age.value.trim().length == 0) {
        //   alert('请输入年龄')
        //   age.focus()
        //   return
        // }
        // if (salary.value.trim().length == 0) {
        //   alert('请输入薪资')
        //   salary.focus()
        //   return
        // }
        // 2.1 收集用户数据,生成一个对象
        let obj = {
          // 如果数据有成员,就取最后一个元素的id+1,否则默认设置为1001
          stuId: arr.length > 0 ? arr[arr.length - 1].stuId + 1 : 1001,
          uname: uname.value,
          age: age.value,
          gender: gender.value,
          salary: salary.value,
          city: city.value
        }

        // 2.2 将生成的对象添加到数组
        arr.push(obj)

        // 2.3 重新渲染
        init()
      })

      // 3.委托方式绑定事件
      tbody.addEventListener('click', function(e) {
        if (e.target.className == 'del') {
          // 说明单击了删除
          // 删除数组中的某个元素
          arr.splice(e.target.id, 1)
          // 重新渲染
          init()
        }
      })
    </script>
  </body>
</html>

●购物车案例

需求:渲染数据,数量增减,删除

image.png

<!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>
      * {
        margin: 0;
        padding: 0;
      }

      ul {
        list-style: none;
      }

      a {
        text-decoration: none;
        color: #666;
      }

      body {
        background: #fff;
        color: #666;
        font-size: 14px;
      }

      input {
        outline: none;
      }

      .clearfix::before,
      .clearfix::after {
        content: '';
        display: block;
        clear: both;
      }

      .clearfix {
        *zoom: 1;
      }
    </style>
    <!-- 引入购物车样式 -->
    <style>
      table {
        width: 800px;
        margin: 0 auto;
        border-collapse: collapse;
      }

      th {
        font: normal 14px/50px '宋体';
        color: #666;
      }

      th,
      td {
        border: none;
        text-align: center;
        border-bottom: 1px dashed #ccc;
      }

      input[type='checkbox'] {
        width: 13px;
        height: 13px;
      }

      tbody p {
        position: relative;
        bottom: 10px;
      }

      tbody .add,
      tbody .reduce {
        float: left;
        width: 22px;
        height: 22px;
        border: 1px solid #ccc;
        text-align: center;
        background: none;
        outline: none;
        cursor: pointer;
      }

      tbody input[type='text'] {
        width: 50px;
        float: left;
        height: 18px;
        text-align: center;
      }

      tbody .count-c {
        width: 98px;
        margin: 0 auto;
      }

      button[disabled] {
        color: #ddd;
        cursor: not-allowed;
      }

      tbody tr:hover {
        background: #eee;
      }

      tbody tr.active {
        background: rgba(241, 209, 149, 0.945);
      }

      .controls {
        width: 790px;
        margin: 10px auto;
        border: 1px solid #ccc;
        line-height: 50px;
        padding-left: 10px;
        position: relative;
      }

      .controls .del-all,
      .controls .clear {
        float: left;
        margin-right: 50px;
      }

      .controls p {
        float: right;
        margin-right: 100px;
      }

      .controls span {
        color: red;
      }

      .controls .pay {
        position: absolute;
        right: 0;
        width: 80px;
        height: 54px;
        background: red;
        font: bold 20px/54px '宋体';
        color: #fff;
        text-align: center;
        bottom: -1px;
      }

      .controls .total-price {
        font-weight: bold;
      }
    </style>
  </head>

  <body>
    <div class="car">
      <table>
        <thead>
          <tr>
            <th><input type="checkbox" id="all" />全选</th>
            <th>商品</th>
            <th>单价</th>
            <th>商品数量</th>
            <th>小计</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody id="carBody"></tbody>
      </table>
      <div class="controls clearfix">
        <a href="javascript:" class="del-all">删除所选商品</a>
        <a href="javascript:" class="clear">清理购物车</a>
        <a href="javascript:" class="pay">去结算</a>
        <p>
          已经选中<span id="totalCount">0</span>件商品;总价:<span
            id="totalPrice"
            class="total-price"
            >0¥</span
          >
        </p>
      </div>
    </div>
    <script>
      // 0 获取元素
      let carBody = document.querySelector('#carBody')
      //  1.模拟数据
      let datas = [
        {
          id: 1, //任何数据都会有id号
          state: true,
          img: './images/01.jpg',
          name: '牛奶',
          price: 5,
          count: 2
        },
        {
          id: 2,
          state: false,
          img: './images/01.jpg',
          name: '奶牛',
          price: 10,
          count: 5
        },
        {
          id: 3,
          state: true,
          img: './images/01.jpg',
          name: '酸奶',
          price: 3,
          count: 1
        }
      ]

      // 2.动态渲染:数据 + 静态结构  遍历拼接
      function init() {
        let htmlStr = ''
        datas.forEach(function(value, index) {
          htmlStr += `<tr>
                        <td>
                          <input class="s_ck" type="checkbox" ${
                            value.state ? 'checked' : ''
                          } />
                        </td>
                        <td>
                          <img src="${value.img}" />
                          <p>${value.name}</p>
                        </td>
                        <td class="price">${value.price}¥</td>
                        <td>
                          <div class="count-c clearfix">
                            <button class="reduce" disabled>-</button>
                            <input type="text" value="${value.count}" />
                            <button class="add" id=${index}>+</button>
                          </div>
                        </td>
                        <td class="total">${value.price * value.count}¥</td>
                        <td>
                          <a href="javascript:" class="del">删除</a>
                        </td>
                      </tr>`
        })
        carBody.innerHTML = htmlStr
      }
      init()

      // 数量增加
      carBody.addEventListener('click', function(e) {
        if (e.target.className == 'add') {
          let pre = e.target.previousElementSibling
          // pre.value = (+pre.value) + 1
          datas[e.target.id].count++
          init()
        } else if (e.target.className == 'reduce') {
          console.log('----')
        }
      })
    </script>
  </body>
</html>