Web APIs-事件高级

75 阅读4分钟

01-事件对象

  • 事件对象:就是一个对象,里面包含着与当前事件相关的信息
  • 获取对象:事件处理函数的第一个参数就是事件对象
<body>
    <button>点击</button>
    <input type="text" />
    <script>
      let btn = document.querySelector('button')
      let input = document.querySelector('input')

      // 鼠标按键事件
      // 事件处理函数的第一个参数就是  事件对象,里面有当前事件的相关信息
      btn.addEventListener('click', function(e) {
        console.log(e)
      })

      // 键盘按键事件
      input.addEventListener('input', function(e) {
        console.log(e)
      })
    </script>
  </body>
复制代码

02-事件对象常用属性

  • 事件类型不一样,属性成员不一样

  • 鼠标事件

    • clientX,clientY:参照浏览器可见区域的左上角
    • offsetX,offsetY:参照元素左上角
  • 键盘事件

    • key:按下的键的名称
    • which:按下的键的键码----Enter:13 Esc:27
  • e.target:真正触发事件的对象

    <style>
      body {
        height: 3000px;
      }

      div {
        width: 3000px;
        height: 300px;
        background-color: pink;
        margin: 100px;
      }
    </style>
  </head>

  <body>
    <div></div>
    <script>
      let div = document.querySelector('div')

      div.addEventListener('click', function(e) {
        console.log(e)
      })
      // 可以为文档添加按键事件
      document.addEventListener('keydown', function(e) {
        console.log(e)
      })
    </script>
  </body>
复制代码

03-跟随鼠标的天使

    <style>
      html,
      body {
        width: 100%;
        height: 100%;
        background-color: #ff0;
      }
      img {
        position: absolute;
        top: 0;
        left: 0;
      }
    </style>
  </head>

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

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

      // 为页面添加鼠标移动的监听事件:以后如果你不知道为谁添加事件绑定,就为document
      document.addEventListener('mousemove', function(e) {
        console.log(e.clientX, e.clientY)
        img.style.top = e.clientY - 40 + 'px'
        img.style.left = e.clientX - 40 + 'px'
      })
    </script>
  </body>
复制代码

04-事件流(捕获事件 冒泡事件)

  • 触发事件时的事件流向

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

    • 模拟捕获过程:第三个参数添加true
  • 事件冒泡:从内到外,从下到上,从子到父

    • 子元素触发事件之后,还会将事件冒泡给父容器
    • 如果父容器添加了同名事件,父容器的事件也会触发
  • 阻止事件传播

    • 可以阻止捕获也可以阻止冒泡
    • e.stopPropagation()
  • 阻止默认行为

    • 场景

      • 阻止超链接的跳转
      • 阻止表单元素csubmit的默认提交行为
    • e.preventDefault()

4.1 捕获事件

  • 事件捕获概念:

    • 从DOM的根元素开始去执行对应的事件 (从外到里)
    • 事件捕获需要写对应代码才能看到效果
  • 代码:

    • DOM.addEventListener(事件类型, 事件处理函数, 是否使用捕获机制)
  • 说明:

    • addEventListener第三个参数传入true代表是捕获阶段触发(很少使用)
    • 若传入false代表冒泡阶段触发,默认就是false
    • 若是用 L0 事件监听,则只有冒泡阶段,没有捕获
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      html,
      body {
        width: 100%;
        height: 100%;
      }
      .father {
        margin: 100px auto;
        width: 500px;
        height: 500px;
        background-color: pink;
      }

      .son {
        width: 200px;
        height: 200px;
        background-color: purple;
      }
    </style>
  </head>

  <body>
    <div class="father">
      <div class="son"></div>
    </div>
    <script>
      let father = document.querySelector('.father')
      let son = document.querySelector('.son')

      // 第三个参数为true就代表事件在捕获阶段触发,如果没有设置默认为false,false代表冒泡阶段触发
      document.addEventListener(
        'click',
        function(e) {
          console.log('document')
          // 阻止事件流动可以阻止事件捕获,也可以阻止事件冒泡
          e.stopPropagation()
        },
        true
      )

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

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

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

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

复制代码

4.2 冒泡事件

  • 事件冒泡概念:

    • 当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡
  • 简单理解:当一个元素触发事件后,会依次向上调用所有父级元素的同名事件

  • 事件冒泡是默认存在的

<body>
    <div class="father">
      <div class="son"></div>
    </div>
    <script>
      let father = document.querySelector('.father')
      let son = document.querySelector('.son')

      // 第三个参数为true就代表事件在捕获阶段触发,如果没有设置默认为false,false代表冒泡阶段触发
      document.addEventListener('click', function(e) {
        console.log('document')
        // 阻止事件流动可以阻止事件捕获,也可以阻止事件冒泡
        // e.stopPropagation()
      })

      document.documentElement.addEventListener('click', function() {
        console.log('documentElement')
      })

      document.body.addEventListener('click', function() {
        console.log('body')
      })

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

      son.addEventListener('click', function(e) {
        console.log('son')
      })
    </script>
  </body>
复制代码

4.3 阻止元素的默认行为

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

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

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

  • 语法:

    • 事件对象.stopPropagation()
  • 此方法可以阻断事件流动传播,不光在冒泡阶段有效,捕获阶段也有效

    <style>
      .father {
        margin: 100px auto;
        width: 500px;
        height: 500px;
        background-color: pink;
      }

      .son {
        width: 200px;
        height: 200px;
        background-color: purple;
      }
    </style>
  </head>

  <body>
    <a href="http://www.baidu.com">我不想去百度啊</a>

    <script>
      let a = document.querySelector('a')
      a.addEventListener('click', function(e) {
        // 阻止默认行为:阻止元素的默认行为,后期只有两个场景
        // 1。超链接的跳转
        // 2。表单元素submit的默认提交行为
        e.preventDefault()
        console.log('自己做处理,不去跳转')
      })
    </script>
  </body>
</html>
复制代码

05-事件委托

  • 表现形式:将事件绑定给已存在的父容器,每一个子元素都能触发

  • 好处:

    • 简化代码
    • 对已有元素和未来元素都有效
    • 给父级元素加事件(可以提高性能)
  • 原理:是利用了事件冒泡的原理:子元素触发事件,也会将事件冒泡给父容器

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

  • 重点细节

    • 事件绑定给父容器(父窗口一定要已经存在)

    • 通过e.target获取真正触发事件的元素

    • 一般情况下,需要判断是否有需要的元素才进行相应的业务处理

      • localName判断元素类型
      • className判断样式
      • classList.contains判断是否包含某个样式标识

5.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>Document</title>
    <style>
      div {
        width: 300px;
        height: 300px;
        border: solid;
      }
    </style>
  </head>
  <body>
    <div>
      <p>我是现成的p元素1</p>
      <p>我是现成的p元素2</p>
    </div>
    <button>添加一个新的元素</button>
    <script>
      let btn = document.querySelector('button')
      let div = document.querySelector('div')
      let divps = document.querySelectorAll('div > p')

      // divps.forEach(function(ele) {
      //   ele.addEventListener('click', function() {
      //     console.log(123)
      //   })
      // })

      btn.addEventListener('click', function() {
        let newP = document.createElement('p')
        newP.innerText = '我也是p元素,我是新来的'
        div.appendChild(newP)

        // newP.addEventListener()
      })

      // 这种将事件绑定给父容器,从而让每一个子元素 都能触发事件的方式就叫   ---- 事件委托
      // 事件委托是利用了事件冒泡的原理:子元素会将事件传递给父容器
      // 1。父容器必须已存在
      // 2。这种绑定对已有子元素和未来子元素都有效
      div.addEventListener('click', function() {
        console.log(123)
      })
    </script>
  </body>
</html>
复制代码

5.2 基于冒泡的原理我们可以将子元素的事件绑定给父容器

<body>
    <ul>
      <li>第1li元素</li>
      <li>第2li元素</li>
      <li>第3li元素</li>
      <li>第4li元素</li>
      <li>第5li元素</li>
      <li>第6li元素</li>
    </ul>

    <script>
      // let lis = document.querySelectorAll('li')

      // lis.forEach(function(ele) {
      //   ele.addEventListener('click', function() {
      //     console.log(123)
      //   })
      // })

      let ul = document.querySelector('ul')
      ul.addEventListener('click', function() {
        console.log(123)
      })
    </script>
  </body>
复制代码

5.3 事件委托

<body>
    <div class="box">
      <button>添加li元素</button>
      <ul>
        <p>aaa</p>
        <p>aaa</p>
        <p>aaa</p>
        <p>aaa</p>
        <li class="comm myli">我是第1个小li</li>
        <li class="comm myli">我是第2个小li</li>
        <li class="comm myli">我是第3个小li</li>
        <li class="comm myli">我是第4个小li</li>
        <li class="comm myli">我是第5个小li</li>
      </ul>
    </div>

    <script>
      let ul = document.querySelector('ul')
      let button = document.querySelector('button')

      // 添加委托事件:绑定给父容器
      ul.addEventListener('click', function(e) {
        // 将li元素设置为红色
        // 1.如何拿到当前真正触发事件的元素
        console.log(e)
        // 2.判断当前目标元素是否是我需要的元素
        // if (e.target.className == 'myli') {
        if (e.target.classList.contains('myli')) {
          e.target.style.color = 'red'
        }
      })
    </script>
  </body>
复制代码

用户信息案例

<!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>
        <!-- <tr>
          <td>1</td>
          <td>这是名称</td>
          <td>这是年龄</td>
          <td>这是性别</td>
          <td>这是工资</td>
          <td>这是所在城市</td>
          <td>
            <a href="javascript:" class="del">删除</a>
          </td>
        </tr> -->
      </tbody>
    </table>
    <script>
      // 模拟数据:数据一定要有id做为数据的唯一标识,这个标识是以后数据  修改和删除的凭据
      let students = [
        {
          id: 1,
          name: '刘奕',
          age: 18,
          gender: '女',
          salary: 20000,
          city: '广州'
        },
        {
          id: 2,
          name: '王涛',
          age: 19,
          gender: '男',
          salary: 20000,
          city: '广州'
        },
        {
          id: 3,
          name: '泉林',
          age: 18,
          gender: '女',
          salary: 30000,
          city: '广州'
        },
        {
          id: 4,
          name: '王鹏',
          age: 19,
          gender: '男',
          salary: 40000,
          city: '广州'
        }
      ]

      // 获取元素
      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.数据渲染
      function init() {
        // 遍历拼接
        let htmlStr = ''
        students.forEach(function(value, index) {
          htmlStr += `<tr>
                      <td>${index + 1}</td>
                      <td>${value.name}</td>
                      <td>${value.age}</td>
                      <td>${value.gender}</td>
                      <td>${value.salary}</td>
                      <td>${value.city}</td>
                      <td>
                        <a href="javascript:" class="del" id='${
                          value.id
                        }'>删除</a>
                      </td>
                    </tr>`
        })
        tbody.innerHTML = htmlStr
      }
      init() // 一开始需要调用函数,否则函数不会自动的执行

      // 2.委托方式绑定事件,一般情况下是绑定给最近的一级父容器
      tbody.addEventListener('click', function(e) {
        // 判断是否是单击了删除按钮
        if (e.target.className == 'del') {
          // 实现删除:filter方法的使用
          // filter是数组非变更方法,所以要将fitler的结果覆盖原数组
          students = students.filter(function(value) {
            // 假如删除id为1的值
            return value.id != e.target.id
          })
          // 重新渲染
          init()
        }
      })

      // 3.实现数据的新增
      let id = 5
      add.addEventListener('click', function() {
        // 准备数据
        let newObj = {
          id: id,
          name: uname.value,
          age: age.value,
          gender: gender.value,
          salary: salary.value,
          city: city.value
        }
        // 为下一次新增做准备,将id+1
        id++
        // 将数据添加到数组
        students.push(newObj)
        // 重新渲染
        init()
      })
    </script>
  </body>
</html>
复制代码
* {
  margin: 0;
  padding: 0;
}

a {
  text-decoration: none;
  color:#721c24;
}
h1 {
  text-align: center;
  color:#333;
  margin: 20px 0;
 
}
table {
  margin:0 auto;
  width: 800px;
  border-collapse: collapse;
  color:#004085;
}
th {
  padding: 10px;
  background: #cfe5ff;
  
  font-size: 20px;
  font-weight: 400;
}
td,th {
  border:1px solid #b8daff;
}
td {
  padding:10px;
  color:#666;
  text-align: center;
  font-size: 16px;
}
tbody tr {
  background: #fff;
}
tbody tr:hover {
  background: #e1ecf8;
}
.info {
  width: 900px;
  margin: 50px auto;
  text-align: center;
}
.info  input {
  width: 80px;
  height: 25px;
  outline: none;
  border-radius: 5px;
  border:1px solid #b8daff;
  padding-left: 5px;
}
.info button {
  width: 60px;
  height: 25px;
  background-color: #004085;
  outline: none;
  border: 0;
  color: #fff;
  cursor: pointer;
  border-radius: 5px;
}
.info .age {
  width: 50px;
}
复制代码

购物车案例

<!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">
        <!-- <tr>
            <th><input type="checkbox" /></th>
            <th>商品名称</th>
            <th>100</th>
            <th>2</th>
            <th>200</th>
            <th>操作</th>
          </tr> -->
      </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 src="./data.js"></script>

  <script>
    let carBody = document.querySelector('#carBody')
    let all = document.querySelector('#all')
    let totalCount = document.querySelector('#totalCount')
    let totalPrice = document.querySelector('#totalPrice')
    // 渲染--封装为一个函数
    function init() {
      let htmlStr = ''
      let count = 0,
        sum = 0
      data.forEach(function (value, index) {
        htmlStr += `<tr>
                        <td><input class='sck'  id='${index}' type="checkbox" ${
            value.state ? 'checked' : ''
          }/></td>
                        <td><img src='${value.img}'/> <p>${value.name}</p></td>
                        <td>${value.price}</td>
                        <td><button class='reduce' id='${index}'>-</button><input type='text' value='${
            value.count
          }'/>&nbsp;&nbsp;&nbsp;<button class='add' id='${index}'>+</button></td>
                        <td>${value.price * value.count}</td>
                        <td>操作</td>
                      </tr>`
        if (value.state) {
          count++
          sum += value.price * value.count
        }
      })
      carBody.innerHTML = htmlStr
      // 设置全选复选框的选中状态
      all.checked = count == data.length
      totalCount.innerHTML = count
      totalPrice.innerHTML = '¥' + sum
      console.log(all.checked);
    }
    init()

    // 实现数量增加,复选框的单击
    carBody.addEventListener('click', function (e) {
      if (e.target.className == 'add') {
        data[e.target.id].count++
        init()
      } else if (e.target.className == 'reduce') {
        data[e.target.id].count > 1 ? data[e.target.id].count-- : ''
        init()
      } else if (e.target.className == 'sck') {
        data[e.target.id].state = !data[e.target.id].state
        init()
      }
    })

    // 全选和全不选
    all.addEventListener('click', function () {
      let state = all.checked
      data.forEach(function (value) {
        value.state = state
      })
      init()
    })
  </script>
</body>

</html>
复制代码
let data = [
  {
    id: 1, //任何数据都会有id号
    state: true,
    img: './images/01.jpg',
    name: '牛奶',
    price: 5,
    count: 2
  },
  {
    id: 2,
    state: true,
    img: './images/01.jpg',
    name: '奶牛',
    price: 10,
    count: 5
  },
  {
    id: 3,
    state: false,
    img: './images/01.jpg',
    name: '酸奶',
    price: 3,
    count: 1
  }
]


作者:Hsu_Ee
链接:juejin.cn/post/711300… 来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。