十、事件类型(鼠标事件、焦点.. 、键盘.. 、文本.. 、滚动..)、事件对象、事件流(事件捕获、事件冒泡、阻止冒泡和默认行为、事件委托)

1. 事件类型

1.1 鼠标事件

1.1.1 click 鼠标点击

1.1.2 mouseenter 鼠标进入

1.1.3 mouseleave 鼠标离开

html 复制代码
<!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>
    .box {
      width: 200px;
      height: 200px;
      background-color: pink;
    }
  </style>
</head>

<body>
  <div class="box">div 盒子</div>

  <!-- 
  鼠标事件
    click 鼠标点击
    mouseenter 鼠标进入
    mouseleave 鼠标离开 
  -->
  <script>
    const box = document.querySelector('.box')
    box.addEventListener('mouseenter', function () {
      console.log('mouseenter')
    })
    box.addEventListener('mouseleave', function () {
      console.log('mouseleave')
    })
  </script>
</body>

</html>

1.2 焦点事件

1.2.1 focus 获得焦点

1.2.2 blur 失去焦点

html 复制代码
<!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>
    /* css选择器 → 属性选择器 */
    [type=text] {
      width: 245px;
      height: 50px;
      padding-left: 20px;
      border: 1px solid #ccc;
      font-size: 17px;
      outline: none;
    }
  </style>
</head>

<body>
  <input type="text" class="inp1" value="原本value">
  <input type="text" class="inp2">

  <!-- 
  焦点事件
    focus 获得焦点
    blur 失去焦点
  -->
  <script>
    const inp1 = document.querySelector('.inp1')
    const inp2 = document.querySelector('.inp2')
    inp1.addEventListener('focus', function () {
      console.log('focus')
    })
    inp1.addEventListener('blur', function () {
      console.log('blur')
      console.log(inp1.value)

    })
  </script>
</body>

</html>

案例1_小米搜索框显示隐藏案例

html 复制代码
<!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>小米搜索框显示隐藏案例</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    ul {
      list-style: none;
    }

    .mi {
      position: relative;
      width: 223px;
      margin: 100px auto;
    }

    .mi .search-text {
      width: 223px;
      height: 48px;
      padding: 0 10px;
      font-size: 14px;
      line-height: 48px;
      border: 1px solid #e0e0e0;
      outline: none;
    }

    /* 搜索框边框颜色 */
    .mi .search {
      border: 1px solid #ff6700;
    }

    /* 下拉菜单 */
    .result-list {
      /* 先隐藏下拉菜单 */
      display: none;
      position: absolute;
      left: 0;
      top: 48px;
      width: 223px;
      border: 1px solid #ff6700;
      border-top: 0;
      background: #fff;
    }

    .result-list a {
      display: block;
      padding: 6px 15px;
      font-size: 12px;
      color: #424242;
      text-decoration: none;
    }

    .result-list a:hover {
      background-color: #eee;
    }
  </style>

</head>

<body>
  <div class="mi">
    <input type="search" placeholder="小米笔记本" class="search-text">
    <ul class="result-list">
      <li><a href="#">全部商品</a></li>
      <li><a href="#">小米11</a></li>
      <li><a href="#">小米10S</a></li>
      <li><a href="#">小米笔记本</a></li>
      <li><a href="#">小米手机</a></li>
      <li><a href="#">黑鲨4</a></li>
      <li><a href="#">空调</a></li>
    </ul>
  </div>
  <script>
    const searchText = document.querySelector('.search-text')
    const list = document.querySelector('.result-list')
    // 获得焦点:输入框的边框颜色变橙色search类;子菜单显示出来
    // display:none;none是隐藏;block(块级)
    searchText.addEventListener('focus', function () {
      this.classList.add('search')
      list.style.display = 'block'
    })
    // 失去焦点:输入框的边框颜色变灰色;子菜单隐藏起来
    searchText.addEventListener('blur', function () {
      this.classList.remove('search')
      list.style.display = 'none'
    })
  </script>
</body>

</html>

1.3 键盘事件

1.3.1 keydown 键盘按下

1.3.2 keyup 键盘抬起

1.4 文本事件 input

html 复制代码
<!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>
    textarea {
      width: 300px;
      height: 30px;
      padding: 10px;
      border-color: transparent;
      outline: none;
      resize: none;
      background: #f5f5f5;
      border-radius: 4px;
    }
  </style>
</head>

<body>
  <!-- input 不换行 -->
  <textarea id="tx" placeholder="发一条友善的评论" rows="2"></textarea>

  <!-- 
  键盘事件
    keydown 键盘按下
    keyup 键盘抬起
  文本事件 input 当表单value 被修改时触发

  执行顺序:keydown → input → keyup
  -->
  <script>
    const tx = document.querySelector('#tx')
    tx.addEventListener('keydown', function () {
      console.log('键盘按下')
    })
    tx.addEventListener('input', function () {
      console.log('input')
    })
    tx.addEventListener('keyup', function () {
      console.log('键盘抬起')
    })
  </script>
</body>

</html>

案例2_统计用户输入字数

html 复制代码
<!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>评论回车发布</title>
  <style>...</style>
</head>

<body>
  <div class="wrapper">
    <i class="avatar"></i>
    <!-- maxlength 输入的最大字符数 -->
    <textarea id="tx" placeholder="发一条友善的评论" rows="2" maxlength="200"></textarea>
    <button>发布</button>
  </div>
  <div class="wrapper">
    <span class="total">0/200字</span>
  </div>
  <div class="list">
    <div class="item" style="display: none;">
      <i class="avatar"></i>
      <div class="info">
        <p class="name">清风徐来</p>
        <p class="text">大家都辛苦啦,感谢各位大大的努力,能圆满完成真是太好了[笑哭][支持]</p>
        <p class="time">2099-10-10 20:29:21</p>
      </div>
    </div>
  </div>
  <!-- 
  统计用户输入字数
    需求:用户输入文字,可以计算用户输入的字数
    分析:
    ①:文本域获得焦点则显示统计 total 盒子,失去焦点则隐藏统计 total 盒子
    ②:文本域输入内容,使用input事件,不断取得字符长度(文本域.value.length )
    ③:把获得字符长度赋值给 total 字数统计盒子
  -->
  <script>
    const tx = document.querySelector('#tx')
    const total = document.querySelector('.total')

    tx.addEventListener('focus', function () {
      total.style.opacity = 1
    })
    tx.addEventListener('input', function () {
      let num = tx.value.length
      total.innerHTML = `${num}/200字`
    })
    tx.addEventListener('blur', function () {
      total.style.opacity = 0
    })

  </script>
</body>

</html>

1.5 滚动事件 scroll

html 复制代码
<!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>页面滚动事件</title>
  <style>
    body {
      height: 3000px;
    }

    a {
      position: fixed;
      right: 10px;
      bottom: 10px;
    }
  </style>
</head>

<body>
  <a href="#">返回顶部</a>

  <!-- 滚动事件scroll 当元素或页面滚动时触发 -->
  <script>
    // 监听整个页面滚动,window.addEventListener('scroll', function () { })
    // (监听某个元素的内部滚动直接给某个元素加即可)
    window.addEventListener('scroll', function () {
      // document.documentElement 获取html元素
      // document.body 获取body元素

      // scrollTop 获取被卷入的头部 scrollTop和scrollLeft可读写
      console.log(document.documentElement.scrollTop)
    })

    const btn = document.querySelector('a')
    btn.addEventListener('click', function () {
      // 页面滚动是 html元素 滚动 
      document.documentElement.scrollTop = 0
    })
  </script>
</body>

</html>

案例3_xtx返回顶部

html 复制代码
  <!-- 返回顶部 -->
  <div class="xtx-elevator">
    <ul class="xtx-elevator-list">
      <li><a href="javascript:;" id="backTop"><i class="sprites"></i>顶部</a></li>
    </ul>
  </div>

  <script>
    const elevator = document.querySelector('.xtx-elevator')

    html = document.documentElement
    window.addEventListener('scroll', function () {
      if (html.scrollTop >= 1000) {
        elevator.style.display = 'block'
      } else {
        elevator.style.display = 'none'
      }
    })

    elevator.addEventListener('click', function () {
      html.scrollTop = 0
    })
  </script>

2. 事件对象

html 复制代码
<!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>
    .box {
      width: 200px;
      height: 200px;
      margin-bottom: 20px;
      background-color: pink;
    }

    textarea {
      width: 300px;
      height: 30px;
      padding: 10px;
      border-color: transparent;
      outline: none;
      resize: none;
      background: #f5f5f5;
      border-radius: 4px;
    }
  </style>
</head>

<body>
  <div class="box"></div>
  <textarea id="tx" placeholder="发一条友善的评论" rows="2"></textarea>

  <!-- 
  事件对象:包含事件触发时的相关信息,包含属性和方法
    注册事件中,回调函数的第一个参数就是事件对象
    一般命名为event、ev、e
  -->
  <!-- 
    事件对象-常见属性
    offsetX(number):事件发生时,鼠标相对于事件源的x坐标
    offsetY(number):事件发生时,鼠标相对于事件源的y坐标
    target(object):事件源对象
    key(string):如果是键盘相关事件,则事件对象中包含该属性,表示键盘事件发生时,按下的是什么键。'Enter'回车键
  -->
  <script>
    const box = document.querySelector('.box')
    box.addEventListener('click', function (event) {
      console.log(event)
    })

    const tx = document.querySelector('#tx')
    tx.addEventListener('keydown', function (event) {
      console.log(event.key)
    })
  </script>
</body>

</html>

案例4_回车发布评论

html 复制代码
<body>
  <div class="wrapper">
    <i class="avatar"></i>
    <textarea id="tx" placeholder="发一条友善的评论" rows="2" maxlength="200"></textarea>
    <button>发布</button>
  </div>
  <div class="wrapper">
    <span class="total">0/200字</span>
  </div>
  <div class="list">
    <div class="item" style="display: none;">
      <i class="avatar"></i>
      <div class="info">
        <p class="name">清风徐来</p>
        <p class="text">大家都辛苦啦,感谢各位大大的努力,能圆满完成真是太好了[笑哭][支持]</p>
        <p class="time">2099-10-10 20:29:21</p>
      </div>
    </div>
  </div>
  <!-- 
    需求:按下回车键,可以发布评论
    功能:
    ①:按下回车,可以显示评论信息,并且评论内容显示到对应位置
    ②:输入完毕,文本域清空内容,并且字数复原为 0
  -->
  <script>
    const tx = document.querySelector('#tx')
    const list = document.querySelector('.list')
    const btn = document.querySelector('button')

    btn.addEventListener('click', function () {
      list.innerHTML = `
        <div class="item" style="display: flex;">
          <i class="avatar"></i>
          <div class="info">
            <p class="name">清风徐来</p>
            <p class="text">${tx.value}</p>
            <p class="time">2099-10-10 20:29:21</p>
          </div>
        </div>
        `
      tx.value = ''
    })

    tx.addEventListener('keydown', function (e) {
      if (e.key === 'Enter') {
        btn.click()
      }
    })
  </script>
</body>

3. 事件流

3.1 事件捕获

3.2 事件冒泡

3.3 阻止冒泡

html 复制代码
<!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>
    .father {
      margin: 100px auto;
      width: 300px;
      height: 300px;
      background-color: skyblue;
    }

    .son {
      margin: 80px auto;
      width: 100px;
      height: 100px;
      background-color: pink;
    }
  </style>
</head>

<body>
  <div class="father">
    父盒子
    <div class="son">子盒子</div>
  </div>

  <!-- 
    事件流指的是事件完整执行过程中的流动路径,
    当触发事件时,会经历两个阶段,分别是捕获阶段、冒泡阶段

    事件捕获:当一个元素的事件被触发时,会从DOM的根元素开始依次调用同名事件 (从外到里)
    addEventListener第三个参数传入 true 代表是捕获阶段触发(很少使用),若传入false代表冒泡阶段触发,默认就是 false
  
    事件冒泡: 当一个元素的事件被触发时,同样的事件将会在该元素的所有祖先元素中依次被触发。
    事件冒泡是默认存在的,实际工作都是使用事件冒泡为主(子到父)

    阻止冒泡:若想把事件就限制在当前元素内,就需要阻止事件冒泡
    事件对象.stopPropagation()
    -->
  <script>
    const father = document.querySelector('.father')
    const son = document.querySelector('.son')

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

</html>

3.4 鼠标经过/离开事件的区别

html 复制代码
<!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>
    .father {
      margin: 100px auto;
      width: 300px;
      height: 300px;
      background-color: skyblue;
    }

    .son {
      margin: 80px auto;
      width: 100px;
      height: 100px;
      background-color: pink;
    }
  </style>
</head>

<body>
  <div class="father">
    父盒子
    <div class="son">子盒子</div>
  </div>

  <!-- 
    mouseover 和 mouseout 会有冒泡
    mouseenter 和 mouseleave 没有冒泡 (常用)
  -->
  <script>
    const father = document.querySelector('.father')
    const son = document.querySelector('.son')

    father.addEventListener('mouseenter', function (event) {
      console.log('father')
    })
    son.addEventListener('mouseenter', function (event) {
      console.log('son')
    })

    /* father.addEventListener('mouseover', function (event) {
      console.log('father')
    })
    son.addEventListener('mouseover', function (event) {
      console.log('son')
    }) */
  </script>
</body>

</html>

3.5 阻止默认行为

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <!-- form 表单区域 可以提交数据 action提交地址 -->
  <form action="">
    姓名: <input type="text" name="username">
    <button>提交</button>
  </form>
  <a href="http://www.baidu.com">点击跳转</a>

  <!-- 
    阻止默认行为:阻止元素发生默认的行为
    例如:当点击提交按钮时阻止对表单的提交,阻止链接的跳转等等 
    事件对象.preventDefault()
    -->
  <script>
    const a = document.querySelector('a')
    a.addEventListener('click', function (e) {
      e.preventDefault()
    })

    // 表单的提交事件 → form submit
    const form = document.querySelector('form')
    form.addEventListener('submit', function (e) {
      console.log('触发form')
      // 阻止默认行为
      e.preventDefault()
    })
  </script>
</body>

</html>

3.6 事件委托

html 复制代码
<!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>事件委托</title>
  <link rel="stylesheet" href="./index.css">
  <script src="./data.js"></script>
</head>

<body>
  <div class="container">
    <button type="button" class="btn add" id="btn-add">
      <span>添加员工</span>
    </button>

    <table class="order">
      <thead>
        <tr>
          <th>头像</th>
          <th>姓名</th>
          <th>工号</th>
          <th>入职时间</th>
          <th>操作</th>
        </tr>
      </thead>

      <tbody>
        <tr>
          <td><span class="username">管</span></td>
          <td>管理员</td>
          <td>10000</td>
          <td>2022-10-24</td>
          <td>
            <button class="btn edit">编辑</button>
            <button class="btn del">删除</button>
          </td>
        </tr>
        <tr>
          <td><span class="username">孙</span></td>
          <td>孙彩</td>
          <td>10000</td>
          <td>2022-09-24</td>
          <td>
            <button class="btn edit">编辑</button>
            <button class="btn del">删除</button>
          </td>
        </tr>
        <tr>
          <td><span class="username">罗</span></td>
          <td>罗晓晓</td>
          <td>10002</td>
          <td>2022-08-24</td>
          <td>
            <button class="btn edit">编辑</button>
            <button class="btn del">删除</button>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
  <!-- 
    事件委托(Event Delegation):也称为事件委派、事件代理
    将原本需要注册在子元素的事件委托给父元素,让父元素担当事件监听的职务

    优点:减少注册次数,可以提高程序性能; 动态生成的元素也能触发事件

    事件委托其实是利用事件冒泡的特点
    给父元素注册事件,当我们触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
  -->
  <script>
    // 需求: 点击每个删除按钮,都会弹出询问框确认是否删除
    const tbody = document.querySelector('tbody')
    tbody.addEventListener('click', function (e) {
      // 事件对象.target.classList.contains() 可以判断真正触发事件的元素是否包含指定类名
      if (e.target.classList.contains('del')) {
        console.log('del')
      }
      if (e.target.classList.contains('edit')) {
        console.log('edit')
      }
    })

    // 新增功能(素材代码,课上无需实现,课上只需讲解事件委托代码即可)
    document.querySelector('#btn-add').addEventListener('click', function () {
      // 随机用户名
      const uname = createRandomName()
      // 随机工号
      const workNum = createWorkNum()
      // 新增一行内容
      tbody.innerHTML += `
        <tr>
          <td><span class="username">${uname[0]}</span></td>
          <td>${uname}</td>
          <td>${workNum}</td>
          <td>2021-03-13</td>
          <td>
            <button class="btn edit">编辑</button>
            <button class="btn del">删除</button>
          </td>
        </tr>
      `
    })
  </script>
</body>

</html>

案例5_价格筛选

html 复制代码
<!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>商品渲染</title>
  <style>...</style>
</head>

<body>
  <!-- 筛选链接 -->
  <div class="filter">
    <a class="tab" data-index="1" href="javascript:;">0-100元</a>
    <a class="tab" data-index="2" href="javascript:;">100-300元</a>
    <a class="tab" data-index="3" href="javascript:;">300元以上</a>
    <a class="tab" href="javascript:;">全部区间</a>
  </div>
  <!-- 渲染盒子 -->
  <div class="list">
    <!-- <div class="item">
      <img src="" alt="">
      <p class="name"></p>
      <p class="price"></p>
    </div> -->
  </div>
  <script>
    // 初始化数据
    const goodsList = [
      {
        id: '4001172',
        name: '称心如意手摇咖啡磨豆机咖啡豆研磨机',
        price: '289.00',
        picture: 'https://yanxuan-item.nosdn.127.net/84a59ff9c58a77032564e61f716846d6.jpg',
      },
    ]

    // 渲染商品列表
    function render(arr) {
      const str = arr.map(item => {
        const { name, price, picture } = item
        return `
        <div class="item">
          <img src="${picture}" alt="">
          <p class="name">${name}</p>
          <p class="price">${price}</p>
        </div>
        `
      }).join('')
      document.querySelector('.list').innerHTML = str
    }

    render(goodsList)

    // 筛选功能
    const filter = document.querySelector('.filter')
    filter.addEventListener('click', function (e) {
      // console.log(e)
      // console.log(e.target)
      // console.log(e.target.dataset)

      let index = e.target.dataset.index
      // 0-100元
      if (index == 1) {
        // console.log(11)
        const newArr = goodsList.filter(el => {
          return el.price <= 100
        })
        render(newArr)
      } else if (index == 2) {
        const newArr = goodsList.filter(el => {
          return el.price <= 300
        })
        render(newArr)
      } else if (index == 3) {
        const newArr = goodsList.filter(el => {
          return el.price > 300
        })
        render(newArr)
      } else {
        render(goodsList)
      }
    })
  </script>
</body>

</html>

4. 移除事件监听

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <button class="L2">L2事件</button>
  <button class="L0">L0事件</button>

  <!-- 
    移除事件监听, 移除事件处理函数,也称为解绑事件
  -->
  <!-- 
    两种注册事件的区别
    ⚫ 传统on注册(L0)
    ➢ 同一个对象,后面注册的事件会覆盖前面注册(同名事件)
    ➢ 直接使用null覆盖偶就可以实现事件的解绑
    ➢ 只有冒泡阶段,没有捕获阶段
    ⚫ 事件监听注册(L2)
    ➢ 语法: addEventListener(事件类型, 事件处理函数, 是否使用捕获)
    ➢ 后面注册的事件不会覆盖前面注册的事件(同名事件)
    ➢ 必须使用removeEventListener(事件类型, 事件处理函数, 获取捕获或者冒泡阶段)实现事件解绑
    ➢ 可以通过第三个参数去确定是在冒泡或者捕获阶段执行
    ➢ 匿名函数无法被解绑
  -->
  <script>
    const L2 = document.querySelector('.L2')
    const L0 = document.querySelector('.L0')

    /* L2.addEventListener('click', function () {
      console.log('L2')
    }) */
    /* 
    移除L2事件监听
      addEventListener方式注册,必须使用:removeEventListener移除
      注意:匿名函数无法解绑
    */
    function fn() {
      console.log('L2')
    }
    L2.addEventListener('click', fn)
    L2.removeEventListener('click', fn)

    /* 
    移除L0事件监听
      on事件方式,直接使用null覆盖偶就可以实现事件的解绑
    */
    L0.onclick = function () {
      console.log('L0')
    }
    L0.onclick = null
  </script>
</body>

</html>

5. 作业

5.1 顺丰快递单号查询

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

        .search {
            position: relative;
            width: 178px;
            margin: 100px;
        }

        .sf {
            height: 30px;
            padding-left: 10px;
            outline: none;
        }

        .con {
            display: none;
            position: absolute;
            top: -43px;
            min-width: 178px;
            max-width: 440px;
            /* 让连续的数字自动换行 */
            word-wrap: break-word;
            border: 1px solid rgba(0, 0, 0, .2);
            box-shadow: 0 2px 4px rgba(0, 0, 0, .2);
            padding: 5px;
            font-size: 18px;
            line-height: 20px;
            color: #333;
        }

        .con::before {
            content: '';
            width: 10px;
            height: 10px;
            position: absolute;
            top: 25px;
            left: 18px;
            border-left: 1px solid rgba(0, 0, 0, .2);
            border-bottom: 1px solid rgba(0, 0, 0, .2);
            background-color: #fff;
            transform: rotate(-45deg);
        }
    </style>
</head>

<body>
    <div class="search">
        <div class="con"></div>
        <input type="text" placeholder="请输入您的快递单号" class="sf">
    </div>

    <!-- 
    需求如下:
        输入框获得焦点,如果没有值需要隐藏放大框
        输入框焦去焦点,隐藏放大框
        输入框中输入内容,没有值就隐藏放大框,有值就显示放大框,并将输入内容显示在放大框中
    -->
    <script>
        const ipt = document.querySelector('.sf')
        const con = document.querySelector('.con')
        let num = 0
        ipt.addEventListener('focus', function () {
            // console.log(ipt.value)
            // if (this.value !== '') {
            if (num > 0) {
                con.style.display = 'block'
            }
        })
        ipt.addEventListener('blur', function () {
            con.style.display = 'none'
        })
        ipt.addEventListener('input', function () {
            num = ipt.value.length
            if (num > 0) {
                con.style.display = 'block'
                con.innerText = ipt.value
            } else {
                con.style.display = 'none'
            }
        })
    </script>
</body>

5.2 小米密码框

html 复制代码
<!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>小米密码框</title>
  <style>
    .mi-form {
      display: table;
      width: 356px;
      height: 60px;
      border-radius: 4px;
      border: 1px solid rgba(0, 0, 0, 0);
      background-color: #f9f9f9;
    }

    .mi-control {
      position: relative;
      display: table-cell;
      width: 294px;
    }

    .mi-input {
      box-sizing: border-box;
      width: 100%;
      height: 60px;
      border: 0;
      padding: 30px 20px 10px;
      outline: none;
      background: none;
      appearance: none;
      font-size: 17px;
      font-family: "Noto Color Emoji";
      color: #333;
      line-height: 20px;
    }

    .mi-control label {
      user-select: none;
      position: absolute;
      top: 20px;
      left: 20px;
      height: 20px;
      font-weight: 400;
      font-size: 17px;
      color: rgba(0, 0, 0, .4);
      line-height: 20px;
      transition: top .15s cubic-bezier(.4, 0, .2, 1), font-size .15s cubic-bezier(.4, 0, .2, 1), color .15s cubic-bezier(.4, 0, .2, 1);
      text-overflow: ellipsis;
      white-space: nowrap;
      overflow: hidden;
    }

    .mi-control label.active {
      top: 6px;
      font-size: 12px;
      color: #aaa;
    }

    .mi-password {
      display: table-cell;
      width: 60px;
      color: rgba(0, 0, 0, .85);
      font-size: 14px;
      vertical-align: middle;
      background: url(./images/close.png) center/30px 30px no-repeat;
      cursor: pointer;
      /* 防止选中文字 防止点击选中文本框的文字 */
      user-select: none;
    }

    .mi-password.active {
      background-image: url(./images/open.png);
    }
  </style>
</head>

<body>
  <div class="mi-form">
    <div class="mi-control">
      <input type="password" class="mi-input">
      <label>密码</label>
    </div>
    <div class="mi-password"></div>
  </div>

  <!-- 
    输入框获得焦点,文字移动到输入框上面
    输入框失焦且输入框无内容,文字跑下来
    点击图标动态切换图标以及输入框的类型
  -->
  <script>
    const ipt = document.querySelector('.mi-input')
    const label = document.querySelector('label')
    const password = document.querySelector('.mi-password')
    let pwClick = false
    ipt.addEventListener('focus', function () {
      label.classList.add('active')
    })
    ipt.addEventListener('blur', function () {
      if (ipt.value == '') {
        label.classList.remove('active')
      }
    })
    password.addEventListener('click', function () {
      pwClick = !pwClick
      /* if (pwClick) {
        ipt.type = 'text'
        this.style.backgroundImage = url('./images/close.png')
      } else {
        ipt.type = 'password'
        this.style.backgroundImage = url('./images/open.png')
      } */
      password.classList.toggle('active')
      ipt.type = pwClick ? 'text' : 'password'
    })
  </script>
</body>

</html>

5.3 掘金快捷导航

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <link rel="stylesheet" href="./fonts/iconfont.css">
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      background-color: #f4f5f5;
    }

    ul {
      list-style: none;
    }

    nav {
      padding-top: 16px;
      padding-bottom: 24px;
      background: #3f7ef7;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    nav .txt {
      background-color: #588ff8;
      padding: 0px 11px 0 4px;
      color: white;
      height: 26px;
      font-size: 14px;
      border-radius: 13px;
      margin: 0 5px;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .search {
      background-color: #3f7ef7;
      text-align: center;
      padding-top: 24px;
      margin: 0 auto;
    }

    .search-wrapper {
      margin: 0 auto;
      width: 40%;
      position: relative;
    }

    input {
      padding-left: 60px;
      width: 100%;
      height: 40px;
      border: none;
      outline: none;
      border-radius: 4px;
    }

    header {
      width: 1440px;
      height: 54px;
      margin: 0 auto;
    }

    header img {
      width: 1440px;
      height: 54px;
      display: block;
    }

    .web-icon {
      width: 28px;
      height: 28px;
      position: absolute;
      top: 6px;
      left: 6px;
    }

    .web-icon img {
      width: 100%;
      height: 100%;
      display: block;
    }

    .triangle {
      position: absolute;
      top: 12px;
      left: 38px;
      width: 16px;
      height: 16px;
      line-height: 16px;
      color: #c3c8d0;
      font-size: 12px;
      transition: all .3s;
      cursor: pointer;
    }

    .triangle.active {
      transform: rotate(180deg);
    }

    .txt .iconfont {
      width: 20px;
      height: 20px;
      border-radius: 10px;
      color: white;
      background-color: black;
      text-align: center;
      line-height: 20px;
      margin-right: 6px;
    }

    .icon-github {
      color: white;
      background-color: black;
    }

    .nav-list {
      width: 100%;
      margin-top: 10px;
      background-color: #fff;
      padding: 8px;
      border-radius: 4px;
      position: absolute;
      left: 0;
      top: 40px;
      display: none;
    }

    .nav-list.show {
      display: block;
      backdrop-filter: blur(20px);
    }

    .item {
      float: left;
      width: 90px;
      height: 90px;
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      margin-right: 8px;
      margin-bottom: 8px;
      position: relative;
    }

    .item.checked::after {
      content: '✓';
      display: block;
      position: absolute;
      right: 2px;
      top: 2px;
      width: 14px;
      height: 14px;
      border-radius: 50%;
      background-color: #3f7ef7;
      color: white;
      font-size: 12px;
    }

    .item:hover {
      background-color: #eaf3fe;
    }

    .item:hover span {
      color: #3f7ef7;
    }

    .item.checked {
      background-color: #eaf3fe;
      color: #3f7ef7;
    }

    .item img {
      width: 36px;
      height: 36px;
      pointer-events: none;
    }

    .item span {
      font-size: 12px;
      margin-top: 8px;
      color: rgb(29, 33, 41);
      pointer-events: none;
    }
  </style>
</head>

<body>
  <header>
    <img src="./imgs/juejin.png" alt="">
  </header>
  <div class="search">
    <div class="search-wrapper">
      <input type="text" id="ipt" placeholder="输入关键词 | tab键切换搜索引擎">
      <div class="web-icon">
        <img src="./imgs/chrome.png" class="curr-nav" alt="">
      </div>
      <!-- 三角 -->
      <span class="triangle iconfont icon-xiangxia"></span>

      <ul class="nav-list">
        <li class="item checked" data-src="./imgs/chrome.png">
          <img src="./imgs/chrome.png" alt="">
          <span>谷歌</span>
        </li>
        <li class="item" data-src="./imgs/toutiao.png">
          <img src="./imgs/toutiao.png" alt="">
          <span>头条</span>
        </li>
        <li class="item" data-src="./imgs/baidu.png">
          <img src="./imgs/baidu.png" alt="">
          <span>百度</span>
        </li>
        <li class="item" data-src="./imgs/bing.png">
          <img src="./imgs/bing.png" alt="">
          <span>必应</span>
        </li>
        <li class="item" data-src="./imgs/github.png">
          <img src="./imgs/github.png" alt="">
          <span>github</span>
        </li>
        <li class="item" data-src="./imgs/npm.png">
          <img src="./imgs/npm.png" alt="">
          <span>npm</span>
        </li>
        <li class="item" data-src="./imgs/stack.png">
          <img src="./imgs/stack.png" alt="">
          <span>stack overflow</span>
        </li>
      </ul>
    </div>
  </div>
  <nav>
    <span class="txt">
      <span>+</span>
      xxx
    </span>
  </nav>

  <!-- 
    新增需求:
    点击某个导航选项,隐藏下拉导航列表,同时将当前展示的导航项切换成点击的导航项
    按tab键也能够切换当前的导航项,同时,如果下拉导航列表是显示状态,能看到选中的样式切换
  -->
  <script>
    const triangle = document.querySelector('.triangle')
    const list = document.querySelector('.nav-list')
    const webIcon = document.querySelector('.web-icon')
    /* 
      在浏览器中,当HTML文档被加载时,所有带有id属性的元素会自动在全局作用域(通常是window对象)中创建一个对应的属性。
      这意味着,如果你有一个id为ipt的元素,理论上你可以通过window.ipt来访问这个元素。
      然而,这种做法并不推荐,因为它依赖于全局作用域,可能会导致命名冲突和代码难以维护。
    */
    // const ipt = document.querySelector('#ipt')
    const items = document.querySelectorAll('.item')
    const currNav = document.querySelector('.curr-nav')

    triangle.addEventListener('click', function () {
      list.classList.toggle('show')
      triangle.classList.toggle('active')
    })

    list.addEventListener('click', function (e) {
      // 点到item才会触发界面变换
      if (e.target.classList.contains('item')) {
        // webIcon.innerHTML = `<img src="${e.target.dataset.src}" class="curr-nav" alt="">`
        currNav.src = e.target.dataset.src
        document.querySelector('.checked')?.classList.remove('checked')
        e.target.classList.add('checked')
        list.classList.toggle('show')
        triangle.classList.toggle('active')
      }
    })

    /* 
      按tab键切换导航项
        核心思路:用一个变量记录导航索引位置并监听键盘按下事件,
        当键盘按下时,索引变量加1;当索引变量超出范围时,重置索引变量的值为0
        2.1 定义变量记录当前展示导航的索引
        2.2 按键判断
        2.3 让input聚焦
        2.4 记录索引加1
        2.5 边界判断
        2.6 更新导航图片
        2.7 修改导航列表中选中项的类名
    */
    let num = 0
    /* ipt.addEventListener('keydown', function (e) {
      if (e.key == 'Enter') {
        // console.log(e.key)
        // console.log(items.length)
        // console.log(`num ${num}`)

        // num++ 先赋值再自增,增加数据未保存,num一直为0
        // num = num < items.length - 1 ? num++ : 0
        // ++num 可以成功实现
        // num = num < items.length - 1 ? ++num : 0
        num < items.length - 1 ? num += 1 : num = 0

        webIcon.innerHTML = `<img src="${items[num].dataset.src}" class="curr-nav" alt="">`
        document.querySelector('.checked')?.classList.remove('checked')
        items[num].classList.add('checked')
        // console.log(items[num])
      }
    }) */
    document.addEventListener('keydown', function (e) {
      if (e.key != 'Tab') return
      // 阻止默认行为
      e.preventDefault()
      // input 聚焦
      ipt.focus()

      num < items.length - 1 ? num += 1 : num = 0
      currNav.src = items[num].dataset.src
      // webIcon.innerHTML = `<img src="${items[num].dataset.src}" class="curr-nav" alt="">`
      document.querySelector('.checked')?.classList.remove('checked')
      items[num].classList.add('checked')
    })
  </script>
</body>

</html>
相关推荐
小宋102127 分钟前
实现Excel文件和其他文件导出为压缩包,并导入
java·javascript·excel·etl
码喽哈哈哈27 分钟前
day01
前端·javascript·html
XT462538 分钟前
解决 npm install 卡住不动或执行失败
前端·npm·node.js
前端小魔女1 小时前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端
mubeibeinv1 小时前
分页/列表分页
java·前端·javascript
林太白1 小时前
js属性-IntersectionObserver
前端·javascript
爱吃羊的老虎1 小时前
【WEB开发.js】getElementById :通过元素id属性获取HTML元素
前端·javascript·html
lucifer3111 小时前
未集成Jenkins、Kubernetes 等自动化部署工具的解决方案
前端
妙哉7361 小时前
零基础学安全--HTML
前端·安全·html
咔叽布吉2 小时前
【前端学习笔记】AJAX、axios、fetch、跨域
前端·笔记·学习