十一、排他思想、window、延时定时器、间歇函数、时间戳、location、navigator、history、本地存储localStorage

1. 排他思想

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

    li {
      list-style-type: none;
    }

    .tab {
      width: 978px;
      margin: 100px auto;
    }

    .tab ul {
      height: 39px;
      border: 1px solid #ccc;
      background-color: #f1f1f1;
    }

    .tab ul li {
      float: left;
      height: 39px;
      line-height: 39px;
      padding: 0 20px;
      text-align: center;
      cursor: pointer;
    }

    .tab ul .current {
      background-color: #c81623;
      color: #fff;
    }
  </style>
</head>

<body>
  <div class="tab">
    <ul>
      <li class="nav current">商品介绍</li>
      <li class="nav">规格与包装</li>
      <li class="nav">售后保障</li>
      <li class="nav">商品评价(50000)</li>
      <li class="nav">手机社区</li>
    </ul>
  </div>

  <!-- 
    排他思想(突出显示某个元素) 排除其他人,保留我自己
    比如,有多个元素,当鼠标经过时,只有当前元素会添加高亮样式,其余的元素移除样式 
  -->
  <script>
    // 1. 遍历加点击事件
    // querySelectorAll选所有,querySelector选第一个
    const navs = document.querySelectorAll('.nav')
    /* array.forEach(element => {
      
    }) */
    /* navs.forEach(nav => {
      nav.addEventListener('click', function () {
        if (document.querySelector('.current')) {
          document.querySelector('.current').classList.remove('current')
        }
        nav.classList.add('current')
      })
    }) */

    // 2. 事件委托
    const ul = document.querySelector('ul')
    ul.addEventListener('click', function (e) {
      // console.log(e.target.classList.contains('nav'))
      // console.log(e.target.innerText)

      if (e.target.classList.contains('nav')) {
        if (document.querySelector('.current')) {
          document.querySelector('.current').classList.remove('current')
        }
        e.target.classList.add('current')
      }
    })
  </script>
</body>

</html>

案例1_tab栏切换

<!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>综合案例-tab栏切换</title>
  <style>
    * {
      margin: 0;
      padding: 0;
    }

    .tab {
      width: 590px;
      height: 340px;
      margin: 20px;
      /* margin-top: 2000px; */
      border: 1px solid #e4e4e4;
    }

    .tab-nav {
      display: flex;
      justify-content: space-between;
      width: 100%;
      height: 60px;
      line-height: 60px;
    }

    .tab-nav h3 {
      margin-left: 20px;
      font-size: 24px;
      font-weight: normal;
    }

    .tab-nav ul {
      display: flex;
      justify-content: flex-end;
      list-style: none;
    }

    .tab-nav ul li {
      margin: 0 20px;
      font-size: 14px;
    }

    .tab-nav ul li a {
      border-bottom: 2px solid transparent;
      text-decoration: none;
      color: #333;
    }

    .tab-nav ul li a.active {
      border-color: #e1251b;
      color: #e1251b;
    }

    .tab-content {
      padding: 0 16px;
    }

    .tab-content .item {
      display: none;
    }

    .tab-content .item.active {
      display: block;
    }
  </style>
</head>

<body>
  <div class="tab">
    <div class="tab-nav">
      <h3>每日特价</h3>
      <ul>
        <li><a data-index="0" class="nav active" href="#">精选</a></li>
        <li><a data-index="1" class="nav" href="#">美食</a></li>
        <li><a data-index="2" class="nav" href="#">百货</a></li>
        <li><a data-index="3" class="nav" href="#">个护</a></li>
        <li><a data-index="4" class="nav" href="#">预告</a></li>
      </ul>
    </div>
    <div class="tab-content">
      <div class="item active"><img src="./images/tab00.png" alt="" /></div>
      <div class="item"><img src="./images/tab01.png" alt="" /></div>
      <div class="item"><img src="./images/tab02.png" alt="" /></div>
      <div class="item"><img src="./images/tab03.png" alt="" /></div>
      <div class="item"><img src="./images/tab04.png" alt="" /></div>
    </div>
  </div>

  <!-- 
    Tab栏切换
    需求:鼠标点击不同的选项卡,底部可以显示 不同的内容
  -->
  <script>
    const ul = document.querySelector('.tab-nav ul')
    const tabContent = document.querySelector('.tab-content')
    const items = document.querySelectorAll('.item')

    // 事件委托
    ul.addEventListener('click', function (e) {
      // 如果点的是a标签/nav类才触发
      if (e.target.classList.contains('nav')) {

        // document.querySelector('.active').classList.remove('active')

        // 先移除其余元素身上的 active 类,再给当前元素添加 active 类(排他思想)
        document.querySelector('.tab-nav .active').classList.remove('active')
        e.target.classList.add('active')

        // 下方内容切换
        let index = e.target.dataset.index
        // 1. 方法1
        // console.log(index)
        /* tabContent.innerHTML = `
          <div class="item active"><img src="./images/tab0${index}.png" alt="" /></div>
        ` */

        // 2. 方法2
        // console.log(items[index]) // 打印出来一样
        document.querySelector('.tab-content .active').classList.remove('active')
        items[index].classList.add('active')
      }
    })
  </script>
</body>

</html>

2. window对象

<!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>


  <!-- 
  JS
    1. ECMAScript:规定了js基础语法核心知识。比如:变量、分支语句、循环语句、对象等
    2. Web APIs
      2.1 DOM 文档对象模型, 定义了一套操作HTML文档的API
      2.2 BOM 浏览器对象模型, 定义了一套操作浏览器窗口的API
     -->
  <!-- 
    BOM 浏览器对象模型
    window对象是一个全局对象,也可以说是JS中的顶级对象
    像document、alert()、console.log()这些都是window的属性,基本BOM的属性和方法都是window的
      navigator:提供了有关浏览器的信息,比如浏览器的名称、版本、平台等
      location(重要):当前文档的URL信息
      document:整个HTML或XML文档的入口点
      history(重要):提供了对浏览器会话历史的访问
      screen:提供了有关用户屏幕的信息
  -->
  <!-- 
    window 里面 load 事件在整个页面及所有依赖资源如样式表和图片都已完成加载时触发。
    DOMContentLoaded 事件才是页面 DOM 加载完成触发,无需等待依赖资源的加载。
  -->
  <script>
    console.log(window)
    // alert('alert')
    // window.alert('window.alert')

    // var 全局定义的变量 和 function 函数都是window对象的属性和方法
    // window对象下的属性和方法调用的时候可以省略window
    var username = 'Tom'
    console.log(username) // Tom
    console.log(window.username) // Tom

    function fn() { console.log('函数') }
    fn() // 函数
    window.fn() // 函数

    let age = 13
    console.log(age) // 13
    console.log(window.age) // undefined

  </script>
</body>

</html>

3. 延时器、定时器

3.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>
</head>

<body>

  <!-- 
    延时器:JS内置的一个用来让代码延迟执行的函数,叫 setTimeout
    setTimeout(回调函数,等待的毫秒数) --(1秒 = 1000毫秒)
    返回值是一个正整数,表示定时器的编号

    延时函数 setTimeout() 只会执行一次
  -->
  <script>
    // 开启延时器 timer为延时器的id
    let timer = setTimeout(() => {
      console.log('延时3秒打印')
    }, 3000)
    console.log(timer) // 1(number)

    // 清除延时器
    // clearTimeout(timer)
  </script>
</body>

</html>

案例2_5秒自动关闭广告

<!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>5秒钟自动关闭广告</title>
  <style>
    body {
      background-color: #07090c;
    }

    .ad {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }

    .close {
      position: absolute;
      top: 95px;
      right: 4px;
      width: 20px;
      height: 20px;
      cursor: pointer;
    }
  </style>
</head>

<body>
  <div class="ad">
    <img src="./images/ad.png" alt="">
    <span class="close"></span>
  </div>
  <script>
    // 单击按钮关闭广告;不点关闭,5秒后自动关闭
    const ad = document.querySelector('.ad')
    const adClose = document.querySelector('.close')
    // 点击 关
    adClose.addEventListener('click', function () {
      ad.style.display = 'none'
    })
    // 不点 5秒后关
    setTimeout(() => {
      // ad.style.display = 'none'
      adClose.click()
    }, 5000)
  </script>
</body>

</html>

3.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>
</head>

<body>
  <button class="btn1">开启定时器</button>
  <button class="btn2">关闭定时器</button>

  <!-- 
    定时器:每隔指定时间自动重复执行某些代码(倒计时)
    setInterval(函数,间隔时间为毫秒)

    间歇函数 setInterval() 会重复执行,必须要手动清除
  -->
  <script>
    const btn1 = document.querySelector('.btn1')
    const btn2 = document.querySelector('.btn2')

    let timer = null
    btn1.addEventListener('click', function () {
      // 清空之前已开启的定时器(防抖?)
      clearInterval(timer)

      // 开启定时器 每隔一段时间调用这个函数
      timer = setInterval(() => {
        console.log('定时器')
      }, 2000)
    })
    btn2.addEventListener('click', function () {
      // 关闭定时器 需要定时器变量名来关闭
      clearInterval(timer)
    })
  </script>
</body>

</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>

  <!-- 
    时间戳:
      指1970年01月01日00时00分00秒起至现在的总毫秒数(数字型),
      是一种特殊的计量时间的方式。
    使用场景:计算倒计时效果,需要借助于时间戳完成
  -->
  <!-- 
    倒计时
      将来的时间戳 - 现在的时间戳 = 剩余时间毫秒数 
      剩余时间毫秒数转换为年月日时分秒 就是倒计时时间
  -->
  <script>
    // 三种方式获取时间戳:
    // 1. getTime() 日期对象来调用
    const date1 = new Date()
    console.log(date1.getTime()) // 1732420922292

    const date2 = new Date('2035-1-1 00:00:00')
    console.log(date2.getTime()) // 2051193600000

    // 2. +new Date() 本质转换为数字
    console.log(+new Date()) // 1732421042801
    console.log(+new Date('2035-1-1 00:00:00')) // 2051193600000

    // 3. Date.now() 只能得到当前的时间戳
    console.log(Date.now()) // 1732421106391

    // get获取时间 没有时区问题
    let h = new Date().getHours()
    let m = new Date().getMinutes()
    let s = new Date().getSeconds()
    console.log(h, m, s) // 10 56 21

  </script>
</body>

</html>

案例3_倒计时

<!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>
    .countdown {
      width: 240px;
      height: 305px;
      text-align: center;
      line-height: 1;
      color: #fff;
      background-color: brown;
      /* background-size: 240px; */
      /* float: left; */
      overflow: hidden;
    }

    .countdown .next {
      font-size: 16px;
      margin: 25px 0 14px;
    }

    .countdown .title {
      font-size: 33px;
    }

    .countdown .tips {
      margin-top: 80px;
      font-size: 23px;
    }

    .countdown small {
      font-size: 17px;
    }

    .countdown .clock {
      width: 142px;
      margin: 18px auto 0;
      overflow: hidden;
    }

    .countdown .clock span,
    .countdown .clock i {
      display: block;
      text-align: center;
      line-height: 34px;
      font-size: 23px;
      float: left;
    }

    .countdown .clock span {
      width: 34px;
      height: 34px;
      border-radius: 2px;
      background-color: #303430;
    }

    .countdown .clock i {
      width: 20px;
      font-style: normal;
    }
  </style>
</head>

<body>
  <div class="countdown">
    <p class="next">今天是2024年11月24日</p>
    <p class="title">下课倒计时</p>
    <p class="clock">
      <span id="hour">00</span>
      <i>:</i>
      <span id="minutes">25</span>
      <i>:</i>
      <span id="second">20</span>
    </p>
    <p class="tips">18:30:00下课</p>
  </div>
  <script>
    // 倒计时案例 当前距离  2023年1月28日 18:30 还剩多少时间

    //  转换公式
    //  h = parseInt(总秒数 / 60 / 60 % 24)   //   计算小时
    //  m = parseInt(总秒数 / 60 % 60)        //   计算分数
    //  s = parseInt(总秒数 % 60)             //   计算当前秒数

    getTimes()
    setInterval(getTimes, 1000)

    function getTimes() {
      const dateEnd = +new Date('2024-11-24 18:30:00')
      const dateNow = +Date.now()
      // console.log(dateEnd, dateNow)
      let num = (dateEnd - dateNow) / 1000

      let h = parseInt(num / 60 / 60 % 24)   //   计算小时
      h = h < 10 ? '0' + h : h
      let m = parseInt(num / 60 % 60)        //   计算分数
      m = m < 10 ? '0' + m : m
      let s = parseInt(num % 60)             //   计算当前秒数
      s = s < 10 ? '0' + s : s
      // console.log(h, m, s)

      document.querySelector('#hour').innerHTML = h
      document.querySelector('#minutes').innerHTML = m
      document.querySelector('#second').innerHTML = s
    }
  </script>
</body>

</html>

4. location对象

<!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 → 提交数据 -->
  <form action="">
    <input type="text" name="username">
    <button>提交</button>
  </form>

  <a href="#/music">音乐</a>

  <button class="btn">刷新</button>

  <!-- location对象:拆分并保存了 URL 地址的各个组成部分 -->
  <!-- 
    href(常用): 属性,获取完整的 URL 地址,赋值时用于地址的跳转
    search: 属性,获取地址中携带的参数,符号 ?后面部分
    hash: 属性,获取地址中的啥希值,符号 # 后面部分
    reload(): 方法,用来刷新当前页面
  -->
  <script>
    // console.log(location)
    // 页面跳转
    // location.href = 'https://baidu.com'

    const form = document.querySelector('form')
    // 表单点击按钮可以触发提交事件 → submit
    form.addEventListener('submit', function (e) {
      // 获取路径?后面的 -- 查询参数
      console.log(location.search) // ?username=
      // 阻止默认行为
      e.preventDefault()
    })

    const a = document.querySelector('a')
    a.addEventListener('click', function () {
      // 获取#后面
      console.log(location.hash) // #/music
    })

    const btn = document.querySelector('.btn')
    btn.addEventListener('click', function () {
      // 刷新页面
      location.reload()
      console.log('刷新')
    })
  </script>
</body>

</html>

案例4_5秒之后跳转页面

<!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>
    a {
      text-decoration: none;
    }

    strong {
      color: red;
    }
  </style>
</head>

<body>
  <!-- 支付成功<strong>5</strong>秒钟之后跳转首页 -->
  <a href="#">支付成功<strong>5</strong>秒钟之后跳转首页</a>

  <!-- 
    5秒钟之后跳转页面
    需求:用户点击可以跳转,如果不点击,则5秒之后自动跳转,要求里面有秒数倒计时
  -->
  <script>
    const a = document.querySelector('a')
    const strong = document.querySelector('strong')
    a.addEventListener('click', function () {
      location.href = 'https://baidu.com'
    })
    let num = 5
    let timer = setInterval(() => {
      num--
      strong.innerHTML = num
      if (num === 0) {
        // clearInterval(timer)
        location.href = 'https://baidu.com'
        return
      }
    }, 1000)

  </script>
</body>

</html>

5. navigator对象

<!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>navigator对象</title>
</head>

<body>
  PC端页面

  <!-- 
    navigator对象,记录了浏览器自身的相关信息
    通过 userAgent 检测浏览器的版本及平台 
  -->
  <!-- 
    `navigator.userAgent`属性 返回浏览器的 User Agent 字符串,表示用户设备信息,包含了浏览器的厂商、版本、操作系统等信息。
    `navigator.onLine`属性 返回一个布尔值,表示用户当前在线还是离线(浏览器断线)
    `navigator.geolocation`属性 返回一个 Geolocation 对象,包含用户地理位置的信息。注意,该 API 只有在 HTTPS 协议下可用。
    `navigator.clipboard` 来访问系统剪切板,以读取系统剪切板的内容。
  -->
  <script>
    // 检测 userAgent(浏览器信息)
    (function () {
      const userAgent = navigator.userAgent
      // 验证是否为Android或iPhone
      const android = userAgent.match(/(Android);?[\s\/]+([\d.]+)?/)
      const iphone = userAgent.match(/(iPhone\sOS)\s([\d_]+)/)
      // 如果是Android或iPhone,则跳转至移动站点
      if (android || iphone) {
        location.href = 'http://m.itcast.cn'
      }
    })()

  </script>
</body>

</html>

6. history对象

<!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>history对象</title>
</head>

<body>
  <button class="back">←后退</button>
  <button class="forward">前进→</button>

  <!-- 
    history(历史)对象:主要管理历史记录,该对象与浏览器地址栏的操作相对应,如前进、后退等
    history 对象一般在实际开发中比较少用,但是会在一些 OA 办公系统中见到。
  -->
  <!-- 
    back() 后退功能
    forward() 前进功能
    go(参数) 前进后退功能 参数如果是1,前进1个页面; 如果是-1,后退1个页面
  -->
  <script>
    const back = document.querySelector('.back')
    const forward = document.querySelector('.forward')
    back.addEventListener('click', function () {
      history.back()
      // history.go(-1)
    })
  </script>
</body>

</html>

7. 本地存储(重点)

7.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>
</head>

<body>

  <!-- 
    本地存储:将数据存储在本地浏览器中,页面刷新数据不丢失

    优点:
      页面刷新或者关闭不丢失数据,实现数据持久化
      容量较大,sessionStorage 和 localStorage 约 5M 左右
  -->
  <!-- 
    1. localStorage(重点)
      数据可以长期保留在本地浏览器中,刷新页面和关闭页面,数据也不会丢失
      以键值对的形式存储,并且存储的是字符串,省略了window
      存储:localStorage.setItem(key, value)
      获取:localStorage.getItem(key)
      删除:localStorage.removeItem(key)
  -->
  <!-- 
    2. sessionStorage(了解)
      当页面浏览器被关闭时,存储在 sessionStorage 的数据会被清除
      存储:sessionStorage.setItem(key, value)
      获取:sessionStorage.getItem(key)
      删除:sessionStorage.removeItem(key)
  -->
  <script>
    // 存储数据
    localStorage.setItem('uname', '小红')
    // 读取数据
    console.log(localStorage.getItem('uname'))
    // 删除数据
    localStorage.removeItem('uname')
  </script>
</body>

</html>

7.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>
</head>

<body>


  <!-- 
    localStorage 存储复杂数据类型

    本地只能存储字符串,无法存储复杂数据类型
    存储复杂数据类型:
      将复杂数据类型转换成JSON字符串,再存储到本地 JSON.stringify(复杂数据类型)
    读取复杂数据类型:
      把取出来的字符串转换为对象 JSON.parse(JSON字符串)

    JSON字符串:
    ➢ 首先是1个字符串
    ➢ 属性名使用双引号引起来,不能单引号
    ➢ 属性值如果是字符串型也必须双引号
  -->
  <script>
    const pig = {
      name: '佩奇',
      age: 5
    }
    console.log(pig) // {name: '佩奇', age: 5}(对象)

    localStorage.setItem('zhu', JSON.stringify(pig))
    console.log(localStorage.getItem('zhu')) // {"name":"佩奇","age":5}(JSON字符串)
    console.log(JSON.parse(localStorage.getItem('zhu'))) // {name: '佩奇', age: 5}(对象)

  </script>
</body>

</html>

7.3 set类型

<!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>

  <script>
    // 不允许包含重复元素
    // set 集合
    // 创建Set数据类型
    const set = new Set()
    // 添加数据
    set.add(10)
    set.add(20)
    set.add(20)
    console.log(set) // {10, 20}
    // 删除数据
    set.delete(10)
    console.log(set) // {20}

    // 数组去重
    const arr = [10, 20, 30, 40, 20]
    console.log(arr) // [10, 20, 30, 40, 20]

    const s = new Set(arr)
    console.log(s) // {10, 20, 30, 40}

    const newArr = [...s]
    console.log(newArr) // [10, 20, 30, 40]

  </script>
</body>

</html>

案例5_历史搜索记录

<script>
  // 单击搜索按钮跳转到 search 页面
  const search = document.querySelector('.ic_search_tab')
  search.addEventListener('click', function () {
    location.href = './search.html'
  })
</script>
<!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="./iconfonts/iconfont.css">
  <link rel="stylesheet" href="./css/search.css">
</head>

<body>
  <div class="m-search">
    <div class="form">
      <i class="iconfont icon-search"></i>
      <input type="text" placeholder="凡人修仙传" class="search-input">
      <i class="iconfont icon-guanbi"></i>
    </div>
    <a href="javascript:void(0);" class="cancel">取消</a>
  </div>
  <div class="search-hot">
    <h2>大家都在搜</h2>
    <div class="list">
      <span class="hotword-item">开学前一天的中国速度</span>
      <span class="hotword-item">LOL新英雄大招全图范围</span>
      <span class="hotword-item">央视曝光飙车炸街产业链</span>
    </div>
  </div>
  <div class="gap"></div>
  <div class="history">
    <h2>历史搜索</h2>
    <ul>
      <!-- <li>
        <i class="iconfont icon-lishijilu_o"></i>
        <span>凡人修仙传</span>
      </li> -->
    </ul>
    <div class="clear-history">
      <a href="javascript:void(0);" class="clear">清除历史记录</a>
    </div>
  </div>

  <!-- 搜索列表展示 -->
  <ul class="recommend-list hide">
    <li class="list-item">xxxx</li>
  </ul>

  <!-- 
    开发中数据持久化,使用localStorage
      先获取本地存储数据、
      后面的增删 渲染基于此数组操作
      清空清两个(页面中数据和本地存储数据)
  -->
  <script>
    // 用户搜索后回车,历史记录增加
    // 数组 → 增加数据 → 重新渲染页面

    // 打开界面 → 获取本地数据
    // localStorage.getItem('history')
    // 逻辑中断
    let arr = JSON.parse(localStorage.getItem('history')) || []

    const ul = document.querySelector('.history ul')
    // 页面随数据变化需多次渲染 封装函数 多次调用即可
    function render(data = []) {
      let str = data.map(item => {
        return `
        <li>
          <i class="iconfont icon-lishijilu_o"></i>
          <span>${item}</span>
          </li>
          `
      }).join('')
      ul.innerHTML = str
    }
    render(arr)

    const ipt = document.querySelector('.search-input')
    ipt.addEventListener('keyup', function (e) {
      if (e.key === 'Enter') {
        // 用户输入无效数据 弹窗提示
        if (this.value.trim() == '') {
          alert('请输入搜索内容')
          this.value = ''
          return
        }
        // 向数组中新增元素
        arr.unshift(this.value)
        // 数组去重
        arr = [...new Set(arr)]

        render(arr)
        // input内内容置空
        this.value = ''

        // 本地存储历史记录数组 转JSON字符串
        localStorage.setItem('history', JSON.stringify(arr))
      }
    })

    const clear = document.querySelector('.clear')
    clear.addEventListener('click', function () {
      localStorage.removeItem('history')
      arr = []
      render(arr)
    })

  </script>

</body>

</html>

8. 作业

8.1 电子时钟

<!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="style.css" />
</head>

<body>
  <h2>电子时钟</h2>
  <div class="clock">
    <div>
      <span id="hour">00</span>
      <span class="text">时</span>
    </div>
    <div>
      <span id="minutes">00</span>
      <span class="text">分</span>
    </div>
    <div>
      <span id="seconds">00</span>
      <span class="text">秒</span>
    </div>
    <div>
      <span id="ampm">AM</span>
    </div>
  </div>

  <!-- 
    页面打开时展示当前时间,并且持续更新。
    根据上午或下午分别显示AM或PM。

    定时器setInterval
  -->
  <!-- 
    调用new Date()时,JavaScript会创建一个表示当前日期和时间的Date对象。
    这个对象内部存储的是自1970年1月1日00:00:00 UTC(协调世界时)以来的毫秒数,这被称为Unix时间戳或Epoch时间戳。

    当使用console.log打印时,浏览器会根据当前设置的时区来显示这个时间。
    这个过程是由浏览器根据系统时区设置自动完成的,而不是由JavaScript代码本身决定的

    浏览器在解析Date对象时,会将其内部存储的UTC时间戳转换为浏览器所在时区的本地时间,并按照该时区的日期时间格式进行显示。
  -->
  <script>
    const hour = document.querySelector('#hour')
    const minutes = document.querySelector('#minutes')
    const seconds = document.querySelector('#seconds')
    const ampm = document.querySelector('#ampm')

    function getTimes() {

      // new Date()获取当前时间 +new Date()时间戳 +new Date()/1000毫秒变秒
      let date = +new Date() / 1000

      // 转换公式
      // h = parseInt(总秒数 / 60 / 60 % 24) // 计算小时
      // m = parseInt(总秒数 / 60 % 60) // 计算分数
      // s = parseInt(总秒数 % 60) // 计算当前秒数

      let h = parseInt(date / 60 / 60 % 24) + 8
      // let h = parseInt(date / 60 / 60 % 24)
      let m = parseInt(date / 60 % 60)
      let s = parseInt(date % 60)
      let ap = hour <= 12 ? 'AM' : 'PM'

      // 补0操作需要多次执行,可以提取为函数
      function zeroAdd(t) {
        return t < 10 ? '0' + t : t
      }

      h = h > 12 ? h - 12 : h
      h = zeroAdd(h)
      m = zeroAdd(m)
      s = zeroAdd(s)

      hour.innerHTML = h
      minutes.innerHTML = m
      seconds.innerHTML = s
      ampm.innerHTML = ap
    }

    getTimes()
    setInterval(getTimes, 1000)

    // console.log(new Date())
    // console.log(+new Date())
  </script>
</body>

</html>

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

<body>
    <div class="container">
        <h1 class="title">番茄时钟</h1>
        <p class="timer" id="timer">25:00</p>
        <div class="button-wrapper">
            <button id="start">开始</button>
            <button id="stop" class="disabled">暂停</button>
            <button id="reset">重置</button>
        </div>
    </div>

    <!-- 
    新增需求:
        点击开始按钮开始倒计时,时间倒计时开始,按钮状态改变
        点击暂停按钮暂停倒计时,时间倒计时暂停,按钮状态改变
        点击重置按钮重置所有数据
        备注:番茄时钟一般是25分钟,
    -->
    <script src="index.js"></script>
</body>

</html>

css

body {
  background: #5D4157;
  /* fallback for old browsers */
  background: -webkit-linear-gradient(to right, #A8CABA, #5D4157);
  /* Chrome 10-25, Safari 5.1-6 */
  background: linear-gradient(to right, #A8CABA, #5D4157);
  /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */

  color: white;
}

.container {
  margin     : 0 auto;
  max-width  : 400px;
  text-align : center;
  padding    : 20px;
  font-family: "Roboto", sans-serif;
}

.title {
  font-size    : 36px;
  margin-bottom: 10px;
}

.timer {
  font-size: 72px;
}

button {
  font-size: 18px;
  padding  : 10px 20px;
  margin   : 10px;
  color    : white;

  border        : none;
  border-radius : 4px;
  cursor        : pointer;
  text-transform: uppercase;
  transition    : opacity 0.3s ease-in-out;
}

#start {
  background-color: #58c193;
}

#stop {
  background-color: #e15241;
}

#reset {
  background-color: #e0e0e0;
}

.disabled {
  opacity: 0.5;
  cursor : not-allowed;
}

js

// 获取页面所需元素
const startEl = document.getElementById('start') // 开始按钮
const stopEl = document.getElementById('stop') // 结束按钮
const resetEl = document.getElementById('reset') // 重置按钮
const timerEl = document.getElementById('timer') // 时间容器

// 定义所需变量
let interval // 定时器变量
let defaultDuration = 3 // 默认时长,单位秒
let timeLeft = defaultDuration // 剩余时间,设置为默认时长
let isStart = false // 状态是否开始,默认为false

// 初始化时长
setPageTime()
// 开始计时
startEl.addEventListener('click', startTimer)
// 结束计时
stopEl.addEventListener('click', stopTimer)
// 重置计时
resetEl.addEventListener('click', resetTimer)

let timer = null

function startTimer() {
  // 1.1 如果已开始直接返回
  if (isStart) return
  // 1.2 状态设置为开始
  isStart = true
  // 1.3 更新按钮状态
  updateElStatus()
  // TODO:你的代码
  clearInterval(timer)
  timer = setInterval(() => {
    timeLeft--
    setPageTime()
    if (timeLeft === 0) {
      alert('时间到了!') 
      clearInterval(timer)
      timeLeft = defaultDuration
      setPageTime()
      isStart = !isStart
      updateElStatus()
      // return
    }
  }, 1000)
}

function stopTimer() {
  // TODO:你的代码
  if (!isStart) return
  isStart = false
  updateElStatus()
  clearInterval(timer)
}

function resetTimer() {
  // TODO:你的代码
  clearInterval(timer)
  isStart = false
  updateElStatus()
  timeLeft = defaultDuration
  setPageTime()
}

// 设置页面时间
function setPageTime() {
  // 获取剩余时长的分钟和秒数,并展示到页面容器中
  let minutes = Math.floor(timeLeft / 60)
  let seconds = timeLeft % 60
  let formattedTime = `${addZero(minutes)}:${addZero(seconds)}`
  timerEl.innerHTML = formattedTime
}

// 更新按钮状态
function updateElStatus() {
  // 更新开始按钮状态(操作类名disabled)
  updateStartElStatus()
  // 更新结束按钮状态(操作类名disabled)
  updateStopElStatus()
}

function updateStartElStatus() {
  isStart ? startEl.classList.add('disabled') : startEl.classList.remove('disabled')
}

function updateStopElStatus() {
  isStart ? stopEl.classList.remove('disabled') : stopEl.classList.add('disabled')
}

// 补0操作
function addZero(time) {
  return time < 10 ? '0' + time : time
}

8.3 BMI计算器

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>BMI 计算器</title>
    <link rel="stylesheet" href="style.css">
</head>

<body>
    <div class="container">
        <h1 class="heading">BMI计算器</h1>
        <span class="label">身高(cm):</span>
        <input type="number" class="input" id="height" value="180" placeholder="输入你的高度cm">
        <span class="label">体重(kg):</span>
        <input type="number" class="input" id="weight" value="80" placeholder="输入你的体重kg">
        <button class="btn" id="btn">计算BMI</button>
        <input disabled type="text" class="input" id="bmi-result">
        <!-- <h4 class="info-text">BMI情况: <span id="weight-condition"></span></h4> -->
        <div class="bmi-result">
            <div class="bmi-desc under-weight">偏瘦</div>
            <div class="bmi-desc normal">正常</div>
            <div class="bmi-desc over-weight">过重</div>
            <div class="bmi-desc obesity">肥胖</div>
        </div>
        <div class="history">
            <h4><span>历史记录</span> <a class="clear">清空记录</a></h4>
            <ul>

            </ul>
        </div>
    </div>

    <!-- 
    新增需求:
        刷新页面,渲染历史记录
        计算BMI之后,显示状态同步更新
        计算BMI之后,同步产生一条历史记录
        点击清空记录,清除历史记录
    -->
    <script src="index.js"></script>
</body>

</html>

js

// 分类	BMI 范围
// 偏瘦	<= 18.4
// 正常	18.5 ~ 23.9
// 过重	24.0 ~ 27.9
// 肥胖	>= 28.0

// 获取页面所需元素
const btnEl = document.getElementById('btn') // 计算按钮
const bmiInputEl = document.getElementById('bmi-result') // BMI结果输入框
const historyEl = document.querySelector('.history ul') // 历史记录容器
const clearBtn = document.querySelector('.clear') // 清空记录按钮

let localBmiData = JSON.parse(localStorage.getItem('history')) || []
// 1. 渲染BMI历史记录
renderBMIHistory()
// 2. 计算BMI的结果
btnEl.addEventListener('click', calculateBMI)
// 3. 清空历史记录
clearBtn.addEventListener('click', clearBMIHistory)

function renderBMIHistory(bmiData=localBmiData) {
  // 1.1 TODO:获取本地存储的BMI数据
  /* let bmiData = [
    {
      "time": 1697695106171,
      "height": 1.9,
      "weight": "75",
      "bmi": "24.69"
    }
  ] */

  let str = ''
  if (bmiData.length > 0) {
    // 1.2 如果有历史记录,则根据数据生成字符串
    bmiData.forEach(item => {
      str += `<li>
          <span>${formatTime(item.time)}</span>
          <span>身高: ${item.height * 100} cm</span>
          <span>体重:${item.weight} kg</span>
          <span>BMI:${item.bmi}</span>
      </li>`
    })
  } else {
    // 1.3 如果没有历史记录,显示提示信息
    str = "<li style='text-align: center; padding-left: 10px;'>暂无历史记录!</li>"
  }
  // 1.4 将生成字符串渲染到页面
  historyEl.innerHTML = str
}

function formatTime(timeStamp) {
  let time = new Date(timeStamp)
  let year = time.getFullYear()
  let month = time.getMonth() + 1
  let date = time.getDate()
  return `${year}-${month}-${date}`
}

function clearBMIHistory() {
  // 3.1 移除localStorage中的BMI数据
  localStorage.removeItem('history')
  localBmiData = []

  // 3.2 渲染更新后的BMI历史记录
  renderBMIHistory()
}

/* 
  增加功能:
  1. 体重输入框内回车计算BMI;
  2.身高和体重框内为空时清空BMI结果栏 
*/
const height = document.getElementById('height')
const weight =document.getElementById('weight')
height.addEventListener('input',function(){
  if (height.value=='') {
    bmiInputEl.value = ''
  }
})
weight.addEventListener('input',function(){
  if (weight.value=='') {
    bmiInputEl.value = ''
  }
})
weight.addEventListener('keyup',function(e){
  if (weight.value.trim()!=''&&e.key === 'Enter'&&height.value.trim()!='') {
    calculateBMI()
  }
})

function calculateBMI() {
  // 2.1 获取输入的身高和体重数值
  const heightValue = height.value / 100
  const weightValue = weight.value

  // 2.2 计算BMI值并保留两位小数
  const bmiValue = (weightValue / (heightValue * heightValue)).toFixed(2)
  // 将计算得到的BMI值显示在输入框中
  bmiInputEl.value = bmiValue

  // 2.3 TODO:根据BMI值设置对应的分类
  // 排他
  /* if (document.querySelector('.active')) {
    document.querySelector('.active').classList.remove('active')
  } */
  document.querySelector('.active')?.classList.remove('active')
  if (bmiValue<18.4) {
    document.querySelector('.under-weight').classList.add('active')
  } else if(bmiValue>=18.4&&bmiValue<24){
    document.querySelector('.normal').classList.add('active')
  } else if(bmiValue>=24&&bmiValue<28){
    document.querySelector('.over-weight').classList.add('active')
  } else{
    document.querySelector('.obesity').classList.add('active')
  }

  // 2.4 TODO:创建当前BMI数据对象,存储当前BMI数据
  localBmiData.unshift({
    "time": +new Date(),
    "height": heightValue,
    "weight": weightValue,
    "bmi": bmiValue
  })
  localStorage.setItem('history',JSON.stringify(localBmiData))

  // 2.5 渲染更新后的BMI历史记录
  renderBMIHistory()
}

css

* {
    margin    : 0;
    padding   : 0;
    box-sizing: border-box;
}

::-webkit-scrollbar {
    width: 8px;
}

::-webkit-scrollbar-track {
    background-color: #e1e3e8;
}

::-webkit-scrollbar-thumb {
    background   : #26262654;
    border-radius: 4px;
}

body {
    /* background     : linear-gradient(to right, #74ebd5, #acb6e5); */
    background     : #ebecf0;
    display        : flex;
    min-height     : 100vh;
    justify-content: center;
    align-items    : center;
    max-height     : 100vh;
}

h1 {
    color      : #BABECC;
    text-shadow: 1px 1px 1px #FFF;
}

button {
    color: #AE1100;
}

.container {
    background    : #ebecf0;
    padding       : 20px;
    display       : flex;
    flex-direction: column;
    border-radius : 8px;
    box-shadow: rgba(0, 0, 0, 0.1) 0px 10px 50px;
    margin        : 5px;
    min-width     : 436px;
    min-height    : 560px;
}

.heading {
    font-size : 30px;
    text-align: center;
}

.label {
    margin-top   : 10px;
    margin-bottom: 4px;
    color        : #757575;
}

.input {
    padding         : 15px 20px;
    font-size       : 18px;
    background      : rgba(255, 255, 255, .4);
    border-color    : rgba(255, 255, 255, .5);
    outline         : none;
    border          : none;
    box-shadow      : inset 2px 2px 5px #BABECC, inset -5px -5px 10px #FFF;
    color           : #757575;
    background-color: #EBECF0;
    text-shadow     : 1px 1px 0 #FFF;
    border-radius   : 25.5px;
    transition      : all 0.2s ease-in-out;
    appearance      : none;
}

.input:focus {
    box-shadow: inset 1px 1px 2px #BABECC, inset -1px -1px 2px #FFF;
}

.btn {
    background-color: #e7eaee;
    border          : none;
    padding         : 10px 20px;
    border-radius   : 24px;
    margin          : 30px 0;
    font-size       : 20px;
    cursor          : pointer;
    box-shadow      : -5px -5px 20px #FFF, 5px 5px 20px #BABECC;
    transition      : all 0.2s ease-in-out;
}

.btn:hover {
    box-shadow: -2px -2px 5px #FFF, 2px 2px 5px #BABECC;
}

.btn:active {
    box-shadow: inset 1px 1px 2px #BABECC, inset -1px -1px 2px #FFF;
}

.info-text {
    font-size  : 16px;
    font-weight: 500;
}

#bmi-result {
    box-shadow: inset 1px 1px 2px #BABECC, inset -1px -1px 2px #FFF;
}

.bmi-result {
    display      : flex;
    margin-top   : 30px;
    margin-bottom: 20px;
}

.bmi-result>.bmi-desc {
    position   : relative;
    flex-basis : 25%;
    flex-shrink: 0;
    text-align : center;
    color      : white;
    font-size  : 14px;
}

.bmi-result .under-weight {
    background-color: #62baf2;
}

.bmi-result .normal {
    background-color: #6ad09d;
}

.bmi-result .over-weight {
    background-color: #f3ce49;
}

.bmi-result .obesity {
    background-color: #e77975;
}

.bmi-desc.active::before {
    content         : "";
    display         : inline-block;
    height          : 1em;
    position        : absolute;
    background-color: inherit;
    clip-path       : polygon(0% 0%, 100% 0%, 50% 100%);
    left            : 50%;
    top             : -1em;
    transform       : translateX(-50%);
    width           : 1.5rem;
}

.history ul {
    max-height : 100px;
    overflow-y : auto;
    font-size  : 12px;
    list-style : none;
    line-height: 16px;
}

.history li {
    display        : flex;
    justify-content: space-between;
    line-height    : 18px;
    height         : 18px;
}

.history li span {
    flex-basis : 25%;
    flex-shrink: 0;
}

.history li span:first-child {
    padding-left: 10px;
}

.history ul li:nth-child(odd) {
    background-color: #e1e3e9;
}

.history h4 {
    margin-bottom  : 10px;
    font-size      : 14px;
    display        : flex;
    justify-content: space-between;
    align-items    : center;
    position       : relative;
    padding-left   : 10px;
    height         : 28px;
    line-height    : 28px;
}

.history h4::before {
    content         : "";
    display         : block;
    width           : 3px;
    height          : 28px;
    background-color: #d97f78;
    position        : absolute;
    top             : 0;
    left            : 0;
}

.history h4 span {
    font-size: 14px;
}

.clear {
    cursor             : pointer;
    outline            : none;
    font-weight        : 500;
    -moz-user-select   : none;
    -webkit-user-select: none;
    -ms-user-select    : none;
    font-size          : 12px;
}

.clear:hover {
    text-decoration: underline;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值