{T}

实现新手指引

仿照 driver.js 实现 新手指引的功能

实现思路是:

  • 点击“开始指引”:找到id为mask的元素,为该元素添加蒙层样式(setMask)
  • 添加提示信息:找到id为tip的元素,将提示信息添加为该元素的子元素(setTip)
  • 高亮当前步骤元素:找到当前目标元素,克隆目标元素,然后将克隆后的目标元素添加为curStepMask的子元素(setTip)
  • 定位tip和curStepMask的元素:curStepMask元素在当前目标元素的正上方,tip元素根据情况而定
  • 每次添加当前提示信息时要移除上一次添加的提示信息和覆盖元素(removeTip,removeStepMask)
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;
        }
 
        body {
            box-sizing: border-box;
        }
 
        .stepBlock {
            background-color: burlywood;
            margin-right: 20px;
        }
 
        .positionStyle {
            position: absolute;
            z-index: 200;
        }
 
        .overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(0, 0, 0, .5);
            z-index: 100;
        }
    </style>
</head>
 
<body>
<section id="mask">
    <!-- 存储提示语 -->
    <section class="positionStyle" id="tip"></section>
    <!-- 存储 高亮的第一步、第二步、第三步-->
    <section class="positionStyle" id="curStepMask"></section>
</section>
 
<section style="margin:200px;">
    <span id="first" class="stepBlock">第一步</span>
    <span id="second" class="stepBlock">第二步</span>
    <span id="third" class="stepBlock">第三步</span>
</section>
 
<section style="margin-top:30px">
    <button onclick="setMask()">开始指引</button>
</section>
 
<script>
  const tipDict = [
    {id: 'first', content: '这里是第1步哦'},
    {id: 'second', content: '这里是第2步哦'},
    {id: 'third', content: '这里是最后一步哦,点击完成按钮结束新手指引'},
  ]
  let flag = 0;
 
  // 添加蒙层
  function setMask() {
    let mask = document.getElementById('mask')
    mask.setAttribute('class', 'overlay')
    setTip()
  }
 
  // 移除蒙层
  function removeMask() {
    let mask = document.getElementById('mask')
    mask.setAttribute('class', '')
 
    // 移除tip提示的子元素
    removeTip()
    removeStepMask()
  }
 
  function setTip() {
    debugger
    if (flag < tipDict.length) {
      // 获取当前步骤的元素,以及元素的位置信息,供后续定位提示信息和覆盖信息使用
      const curStepEle = document.getElementById(tipDict[flag].id)
      const bound = curStepEle.getBoundingClientRect()
 
      // 找到 id 为 tip 的元素
      let ele = document.getElementById("tip")
 
      // 如果存在子元素,先移除
      removeTip()
      removeStepMask()
 
      // 创建提示信息和下一步的统一父元素,方便后续移除元素
      let node = document.createElement('div')
      // 创建提示信息
      let tipText = document.createTextNode(tipDict[flag].content)
      // 将提示信息插入到父元素
      node.appendChild(tipText)
      // 创建“下一步”按钮
      let nextBtn = document.createElement('button')
      nextBtn.innerHTML = flag === tipDict.length - 1 ? '完成' : '下一步';
      nextBtn.onclick = setTip;
      // 将按钮插入到父元素
      node.appendChild(nextBtn)
      // 设置统一父元素的位置
 
 
      ele.style.left = bound.x + 'px'
      ele.style.top = bound.y + 20 + 'px'
      // 将创建的提示语元素插入到id为tip的元素
      ele.appendChild(node)
 
      // 将当前步骤高亮显示
      let tag = flag - 1
      if (tag >= 0) {
        document.getElementById(tipDict[tag].id).style = ''
      }
      // const curStepEle = document.getElementById(tipDict[flag].id)
      // const bound = curStepEle.getBoundingClientRect()
 
      const curStepMask = document.getElementById('curStepMask')
      curStepMask.style.left = bound.x + 'px'
      curStepMask.style.top = bound.y + 'px'
 
      const curStepEleClone = curStepEle.cloneNode(true)
      curStepMask.appendChild(curStepEleClone)
 
      flag++
    } else {
      flag = 0;
      removeMask()
    }
  }
 
  function removeStepMask() {
    let ele = document.getElementById('curStepMask')
    let child = ele.lastElementChild
    if (child) {
      ele.removeChild(child)
    }
  }
 
  function removeTip() {
    let ele = document.getElementById("tip")
    let child = ele.lastElementChild
    if (child) {
      ele.removeChild(child)
    }
  }
</script>
</body>
 
</html>

getBoundingClientRect

返回值是一个 DOMRect 对象,是包含整个元素的最小矩形(包括 paddingborder-width)。该对象使用 lefttoprightbottomxywidthheight 这几个以像素为单位的只读属性描述整个矩形的位置和大小。除了 widthheight 以外的属性是相对于视图窗口的左上角来计算的。 --MDN

image-20231006022016858

场景题:个公告栏,每天都可以展示,当用户点击关闭后,过了今天零点还会显示

在 localStorage 中存储用户关闭公告栏的时间戳,等再次进入页面的时候判断是不是存在 localStorage:

  • 若不存在则证明从来没有关闭过公告栏,那就显示;
  • 若存在,就判断时间戳和当前时间是否是同一天,不是同一天就显示
js
getTime(data, type){  //data 时间戳,type 返回的类型默认 Y,可传参 Y 和 H
  let time = new Date(data);    
  let Y = time.getFullYear();//获得年
  let Mon = time.getMonth() + 1; //月
  let Day = time.getDate();//日
  let H = time.getHours();
  let Min = time.getMinutes();
  let S = time.getSeconds();
  //自定义选择想要返回的类型
  if(type === "Y") {   //返回年月日2020-10-10
    return `${Y}-${Mon}-${Day}`
  } else if(type === "H") {  //返回时分秒20:10:10
    return `${H}:${Min}:${S}`
  } else {  //返回年月日时分秒2020-10-10 10:26:38
    return `${Y}-${Mon}-${Day} ${H}:${Min}:${S}`
  }
}

命名方式中划线与驼峰转换

命名方式中划线改小驼峰

js
function transName(arr) {
    let res = arr.map(e => {
        let items = e.split('-').map((item, index) => {
            if (index) {
                let first = item.substring(0,1)
                let rest = item.substring(1)
                return first.toUpperCase()+rest
            }else{
                return item.toLowerCase()
            }
        })
        return items.join('')
    })
    return res
}
console.log(transName(['A-b-cee', 'ca-de-ea', 'e-fe-eaa','f-g','mn']))
// ['aBCee', 'caDeEa', 'eFeEaa', 'fG', 'mn']
js
function turnName(str){
    return str.replace(/-[a-zA-Z]/g,match=>match.replace('-','').toUpperCase())
}

命名方式小驼峰改中划线

js
let s1 = 'aBBcdE';
 
let t = s1.replace( /[A-Z]/g, match=>'-'+match.toLowerCase());
console.log(t);

前端的缓存机制

分为强缓存和协商缓存

强缓存不需要客户端向服务端发送请求,有两种响应头实现方案:

  • Expires:值是一个绝时间,在这个时间前缓存有效,但是如果本地时间被修改,会导致缓存失效
  • Cache-control:值是一个相对时间,单位为秒,资源在这个时间内有效

强缓存过期之后会使用协商缓存,协商缓存需要客户端向服务端发送请求,资源未过期则服务端返回304否则返回新的资源。 协商缓存也有两种实现方案:

  • Last-Modified 和 If-Modified-Since:Last-Modified 表示本地文件最后修改日期,If-Modified-Since 会将 Last-Modified 的值发送给服务器,询问服务器在该日期后资源是否有更新,有更新的话就会将新的资源发送回来。但是如果本地文件被打开,会导致Last-Modified 被修改。
  • ETag 和 If-None-Match:ETag 类似于文件指纹,If-None-Match 会将当前 ETag 发送给服务器,询问该资源 ETag 是否变动,有变动的话就将新的资源发送回来。并且 ETag 优先级比 Last-Modified

判断图片即将进入可视区域

方案1:clientHeight + scroolTop > offsetTop

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>
  </head>
 
  <body>
    <div style="background-color: green;width:100vw;height:8000px">
    </div>
    <div id="yellow" style="background-color: yellow;width:100vw;height:800px">
    </div>
 
    <script>
      document.addEventListener('scroll', () => {
        // 获取屏幕可视区域的高度
        const clientH = document.documentElement.clientHeight
        // 获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离
        const scrollT = document.documentElement.scrollTop
        // 获取元素相对于文档顶部的高度
        const offsetTop = document.getElementById('yellow').offsetTop
        
        if (clientH + scrollT > offsetTop) {
          console.log('进入可视区域啦!!')
        }
      })
    </script>
  </body>
 
</html>

方案2:下滑过程中 bound.top 会越来越小

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>
</head>
 
<body>
    <div style="background-color: green;width:100vw;height:8000px">
    </div>
    <div id="yellow" style="background-color: yellow;width:100vw;height:800px">
    </div>
 
    <script>
        document.addEventListener('scroll', () => {
            var bound = document.getElementById('yellow').getBoundingClientRect(); ////获取元素的大小及位置
            var clientHeight = window.innerHeight;
            if (bound.top <= clientHeight) {
                console.log('进入可视区域啦')
            }
        })
    </script>
</body>
 
</html>