详细讲解js实现电梯导航

场景

对于某一个页面内容繁多,
如果我们滚动的时间较长,为了增加用户体验。
我们需要实现点击某一个按钮,然后滚动到对应的区域。
滚动的时候,右侧对应的分类实现高亮
其实,这个功能就2个步骤:
1.点击元素高亮,滚动到对应区域
2.滚动的时候,右侧导航的分类高亮

点击当前元素高亮的实现

css 复制代码
1.我们利用事件委托的原理:给被点击子元素的父元素绑定点击事件
2.然后移除 li 元素的激活类
3.给当前被点击的子元素添加上激活类

事件委托也称为事件代理:就是利用事件冒泡,把子元素的事件都绑定到父元素上。
xml 复制代码
<style>
  :root {
    --h:931px;
  }
  *{
    padding: 0;
    margin: 0;
  }
  .demo1{
    height:var(--h);
    background-color: antiquewhite;
  }
  .demo2{
    height:var(--h);
    background-color: aqua;
  }
  .demo3{
    height:var(--h);
    background-color: blue;
  }
  .demo4{
    height:var(--h);
    background-color:chartreuse;
  }
  .fix-post{
    position: fixed;
    right: 50px;
    bottom: 100px;
    width: 200px;
  }
  li{
    height: 40px;
    line-height: 40px;
    text-align: center;
    list-style: none;
    border-top: 1px solid #fff;
    border-left:  1px solid #fff;
    border-right:  1px solid #fff;
  }
  li:last-child{
    border-bottom: 1px solid #fff;
  }
  .liactive{
    background-color: cornsilk;
  }
</style>

<body>
  <div>
    <div class="demo1">1</div>
    <div class="demo2">2</div>
    <div class="demo3">3</div>
    <div class="demo4">4</div>
  </div>
  <ul class="fix-post" id="nav">
    <li class="liactive">1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
  </ul>
</body>
<script>
  let domNav= document.getElementById('nav')
  let liList=document.querySelectorAll("#nav li")
  console.log('liList', liList)
  // 给父级元素注册点击事件(利用事件委托)
  domNav.addEventListener('click',(event)=>{
    for(let i=0;i<liList.length;i++){
      // 移除所有元素的类名liactive
      liList[i].classList.remove('liactive')
    }
    console.log('event.target', event.target)
    // 给当前元素添加激活这个类名
    event.target.classList.add('liactive'); 
  })
</script>

点击右侧按钮,滚动到对应区域

javascript 复制代码
1.给右侧的按钮添加上索引。
2.点击索引的时候 * 每个区域的高度
3.使用window.scrollTo({ })进行滚动
xml 复制代码
<script>
  let domNav= document.getElementById('nav')
  let liList=document.querySelectorAll("#nav li")
  console.log('liList', liList)
  // 给父级元素注册点击事件(利用事件委托)
  domNav.addEventListener('click',(event)=>{
    for(let i=0;i<liList.length;i++){
      // 移除所有元素的类名liactive
      liList[i].classList.remove('liactive')
      // 给右侧的按钮添加上索引
      liList[i]['index']=i
    }
    console.log('event.target', event.target.index)
    // 给当前元素添加激活这个类名
    event.target.classList.add('liactive'); 

    // 点击按钮的时候,滚动到相应的区域
    window.scrollTo({
      top:event.target.index * 931,
      behavior:"smooth" // 平滑的滚动
    })
  })
</script>

滑动到对应区域右侧按钮自动高亮

javascript 复制代码
// 实现滚动的时候,右侧区域自动高亮
window.addEventListener('scroll',()=>{
  // 兼容
  let top = document.documentElement.scrollTop || document.body.scrollTop
  // 获取当前区域的下标
  let index = Math.floor(top/931)
  console.log('top,index',top,index)
  for(let i=0;i<liList.length;i++){
    // 移除所有元素的类名liactive
    liList[i].classList.remove('liactive')
  }
  // 给当前元素添加激活这个类名
  liList[index].classList.add('liactive');
},false)

发现2个问题

makefile 复制代码
问题1:出现这个问题的原因是:距离顶部的高度仅仅超过第一层区域的一点,
这个时候页面显示绝大部分区域是第二层的,
但是右侧按钮显示的是当前是第1层。
怎么解决这个问题?我们给当前区域手动新增一个高度。
这个高度一般为 3/10

问题2:我们每次滚动的时候,都在移除元素激活类,然后新增。
这样不太好,没有必须要。
我们需要判断一下

优化代码[每次滚动的时候都在移除元素的激活类]

javascript 复制代码
// 实现滚动的时候,右侧区域自动高亮
let index=0
window.addEventListener('scroll',()=>{
  console.log(222)
  // 兼容
  let top = (document.documentElement.scrollTop || document.body.scrollTop) + 279 //手动新增一个值
  // 如果索引不变,则不取新增或者移除类名
  if( Math.floor(top/931) != index){
    // 获取当前区域的下标
    let index = Math.floor(top/931)
    for(let i=0;i<liList.length;i++){
      // 移除所有元素的类名liactive
      liList[i].classList.remove('liactive')
    }
    // 给当前元素添加激活这个类名
    liList[index].classList.add('liactive');
  }
},false)

scroll 事件不滚动也会触发

less 复制代码
我们每次刷新页面的时候,滚动事件都会被触发。
因为:刷新的时候可视区域距离顶部有距离。所以滚动事件会被触发;【现象】
这样就会导致初始化(可视区域距离顶部有距离)刷新页面的时候。
右侧的指示灯会切换2次(第一次html上写激活类,第二次是由于有距离触发了滚动事件)。
这样不太好。我们需要优化一下:
删除html上的激活类。如果距离为0

处理右侧的指示灯在距离顶部有距离的时候,快速切换了2次

ini 复制代码
let index
let topValue = document.documentElement.scrollTop || document.body.scrollTop
// 距离为0.显示第一个指示灯
if(topValue==0){
  liList[0].classList.add('liactive');
}

scroll 事件特别说明

makefile 复制代码
在 iOS UIWebViews 中,
滚动进行时不会触发 scroll 事件;
只有当滚动结束后事件才会被触发。
参见 Bootstrap issue #16202。Safari 和 WKWebViews 则没有这个问题。
ps:刷新的时候可视区域距离顶部有距离。滚动事件也会被触发;不一定滚动才会触发

每个区域固定高度实现导航【全部代码】

xml 复制代码
<!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>
    :root {
      --h:931px;
    }
    *{
      padding: 0;
      margin: 0;
    }
    .demo1{
      height:var(--h);
      background-color: antiquewhite;
    }
    .demo2{
      height:var(--h);
      background-color: aqua;
    }
    .demo3{
      height:var(--h);
      background-color: blue;
    }
    .demo4{
      height:var(--h);
      background-color:chartreuse;
    }
    .fix-post{
      position: fixed;
      right: 50px;
      bottom: 100px;
      width: 200px;
    }
    li{
      height: 40px;
      line-height: 40px;
      text-align: center;
      list-style: none;
      border-top: 1px solid #fff;
      border-left:  1px solid #fff;
      border-right:  1px solid #fff;
    }
    li:last-child{
      border-bottom: 1px solid #fff;
    }
    .liactive{
      background-color: cornsilk;
    }
  </style>
</head>
<body>
  <div>
    <div class="demo1">1111</div>
    <div class="demo2">2</div>
    <div class="demo3">3</div>
    <div class="demo4">4</div>
  </div>
  <ul class="fix-post" id="nav">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
  </ul>
</body>
<script>
  let domNav= document.getElementById('nav')
  let liList=document.querySelectorAll("#nav li")
  console.log('liList', liList)
  // 给父级元素注册点击事件(利用事件委托)
  domNav.addEventListener('click',(event)=>{
    for(let i=0;i<liList.length;i++){
      // 移除所有元素的类名liactive
      liList[i].classList.remove('liactive')
      // 给右侧的按钮添加上索引
      liList[i]['index']=i
    }
    console.log('event.target', event.target.index)
    // 给当前元素添加激活这个类名
    event.target.classList.add('liactive'); 

    // 点击按钮的时候,滚动到相应的区域
    window.scrollTo({
      top:event.target.index * 931,
      behavior:"smooth" // 平滑的滚动
    })
  })
 
  let index
  let topValue = document.documentElement.scrollTop || document.body.scrollTop
  // 离为0.显示第一个指示灯
  if(topValue==0){
    liList[0].classList.add('liactive');
  }
  // 实现滚动的时候,右侧区域自动高亮
  window.addEventListener('scroll',()=>{
    console.log('scroll-触发')
    // 兼容
    let top = (document.documentElement.scrollTop || document.body.scrollTop) + 279 //手动新增一个值
    // 如果索引不变,则不取新增或者移除类名
    if( Math.floor(top/931) != index){
      // 获取当前区域的下标
      index = Math.floor(top/931)
      for(let i=0;i<liList.length;i++){
        // 移除所有元素的类名liactive
        liList[i].classList.remove('liactive')
      }
      // 给当前元素添加激活这个类名
      liList[index].classList.add('liactive');
    }
  },false)
</script>
</html>

每个区域高度不一致怎么滚动到对应的区域

虽然我们的滚动可以正确显示右侧的高亮。
点击右侧区域也可以显示到对应的区域。
但是我们每个区域的高度是一致的。
在有些情况,每个区域的高度不一致,
怎么滚动到对应的区域,这个问题怎么处理呢?
我们可以判断当前区域在哪个区间。
xml 复制代码
<body>
  <div id="cont">
    <div class="demo1">1111</div>
    <div class="demo2">2</div>
    <div class="demo3">3</div>
    <div class="demo4">4</div>
  </div>
  <ul class="fix-post" id="nav">
    <li id="demo1">1</li>
    <li id="demo2">2</li>
    <li id="demo3">3</li>
    <li id="demo4">4</li>
  </ul>
</body>
xml 复制代码
<script>
  let contDivList= document.querySelectorAll('#cont div')
  let liList=document.querySelectorAll("#nav li")
  liList.forEach(link =>{
    // 给每个元素注册点击事件
    link.addEventListener('click',(event)=>{
      // 获取被点击元素的类名
      let currentClickElement= event.target.getAttribute('id')
      // 获取对应的区域元素dom
      let currentTargetTop= document.querySelector('.' + currentClickElement)
      // 获取当前这个点击元素的距离顶部的距离
      let eleTop=  currentTargetTop.offsetTop
       // 点击按钮的时候,滚动到相应的区域
      window.scrollTo({
        top:eleTop,
        behavior:"smooth" // 平滑的滚动
      })
    })
  })
  // 实现滚动的时候,右侧区域自动高亮
  window.addEventListener('scroll',()=>{
    let top = window.scrollTop || document.documentElement.scrollTop || document.body.scrollTop;
    console.log('top', top)
    contDivList.forEach(element => {
      // 获取每个元素距离顶部的距离
      const offsetTop = element.offsetTop;
      // 获取每个元素的高度
      const offsetHeight = element.offsetHeight;
      // 判断当前内容区块是否在可视范围内
      if (top >= offsetTop && top < offsetTop + offsetHeight) {
        liList.forEach(function (link) {
          if (link.getAttribute('id') === element.getAttribute('class')) {
            link.classList.add('liactive');
          } else {
            link.classList.remove('liactive');
          }
        });
      }
    });
  },false)
</script>

咋们这种判断方式有没有问题?

ini 复制代码
有的。
const offsetTop = element.offsetTop;
console.log('offsetTop', offsetTop)
// 获取每个元素的高度
const offsetHeight = element.offsetHeight;
// 判断当前内容区块是否在可视范围内
if (top >= offsetTop && top < offsetTop + offsetHeight) {
  
}
这个判断是不准确的。容易出问题。
比如说:某一个区域的高度大于屏幕的可用区域并且下一个区域小于上一个区域的高度。
就可能出现问题。
下一楼层无法正确高亮(在滚动的时候)区域与高亮区域不匹配

全部代码:每个区域高度不确定导航

xml 复制代码
<!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>
    :root {
      --h:931px;
    }
    *{
      padding: 0;
      margin: 0;
    }
    .demo1{
      height: 800px;
      background-color: antiquewhite;
    }
    .demo2{
      height: 450px;
      background-color: aqua;
    }
    .demo3{
      height: 1200px;
      background-color: blue;
    }
    .demo4{
      height: 660px;
      background-color:chartreuse;
    }
    .demo5{
      height: 1000px;
      background-color:rgb(33, 58, 7);
    }
    .fix-post{
      position: fixed;
      right: 50px;
      bottom: 100px;
      width: 200px;
    }
    li{
      height: 40px;
      line-height: 40px;
      text-align: center;
      list-style: none;
      border-top: 1px solid #fff;
      border-left:  1px solid #fff;
      border-right:  1px solid #fff;
    }
    li:last-child{
      border-bottom: 1px solid #fff;
    }
    .liactive{
      background-color: cornsilk;
    }
  </style>
</head>
<body>
  <div id="cont">
    <div class="demo1">1111</div>
    <div class="demo2">2</div>
    <div class="demo3">3</div>
    <div class="demo4">4</div>
    <div class="demo5">5</div>
  </div>
  <ul class="fix-post" id="nav">
    <li id="demo1">1</li>
    <li id="demo2">2</li>
    <li id="demo3">3</li>
    <li id="demo4">4</li>
    <li id="demo5">5</li>
  </ul>
</body>
<script>
  let contDivList= document.querySelectorAll('#cont div')
  let liList=document.querySelectorAll("#nav li")
  liList.forEach(link =>{
    // 给每个元素注册点击事件
    link.addEventListener('click',(event)=>{
      // 获取被点击元素的类名
      let currentClickElement= event.target.getAttribute('id')
      // 获取对应的区域元素dom
      let currentTargetTop= document.querySelector('.' + currentClickElement)
      // 获取当前这个点击元素的距离顶部的距离
      let eleTop=  currentTargetTop.offsetTop
       // 点击按钮的时候,滚动到相应的区域
      window.scrollTo({
        top:eleTop,
        behavior:"smooth" // 平滑的滚动
      })
    })
  })

  // 实现滚动的时候,右侧区域自动高亮
  window.addEventListener('scroll',()=>{
    let top = window.scrollTop || document.documentElement.scrollTop || document.body.scrollTop;
    console.log('top', top)
    contDivList.forEach(element => {
      // 获取每个元素距离顶部的距离
      const offsetTop = element.offsetTop;
      console.log('offsetTop', offsetTop)
      // 获取每个元素的高度
      const offsetHeight = element.offsetHeight;
      // 判断当前内容区块是否在可视范围内
      if (top >= offsetTop && top < offsetTop + offsetHeight) {
        liList.forEach(function (link) {
          if (link.getAttribute('id') === element.getAttribute('class')) {
            link.classList.add('liactive');
          } else {
            link.classList.remove('liactive');
          }
        });
      }
    });
  },false)
</script>
</html>
相关推荐
Cwhat几秒前
前端性能优化2
前端
熊的猫1 小时前
JS 中的类型 & 类型判断 & 类型转换
前端·javascript·vue.js·chrome·react.js·前端框架·node.js
瑶琴AI前端1 小时前
uniapp组件实现省市区三级联动选择
java·前端·uni-app
会发光的猪。1 小时前
如何在vscode中安装git详细新手教程
前端·ide·git·vscode
mosen8682 小时前
Uniapp去除顶部导航栏-小程序、H5、APP适用
vue.js·微信小程序·小程序·uni-app·uniapp
别拿曾经看以后~2 小时前
【el-form】记一例好用的el-input输入框回车调接口和el-button按钮防重点击
javascript·vue.js·elementui
我要洋人死3 小时前
导航栏及下拉菜单的实现
前端·css·css3
川石课堂软件测试3 小时前
性能测试|docker容器下搭建JMeter+Grafana+Influxdb监控可视化平台
运维·javascript·深度学习·jmeter·docker·容器·grafana
科技探秘人3 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人3 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome