美文网首页
Vue2实现电梯导航(内容随锚点滚动+动画)

Vue2实现电梯导航(内容随锚点滚动+动画)

作者: 哈士奇的猫 | 来源:发表于2023-08-21 10:21 被阅读0次
最终实现效果

实现效果说明

整体布局,内容和导航联动起来,内容滚动时电梯导航栏跟随内容指定对应楼层,点击导航,滚动到指定内容。

代码实现比较简单,就那么几行,就不多废话了,上代码。

<template>
  <div class="main">
    <!-- 内容区域  每个模块的id要与menu的id一一对应-->
    <section class="content" id="gonglu">
      <div class="header">
        公路运输
      </div>
    </section>
    <section class="content" id="tielu">
      <div class="header">
        铁路运输
      </div>
    </section>
    <section class="content" id="shuilu">
      <div class="header">
        水路运输
      </div>
    </section>
    <section class="content" id="cangchu">
      <div class="header">
        仓储统配
      </div>
    </section>
    <!-- 侧边栏 -->
    <ul class="sidebar">
      <li v-for="item in menu" :key="item.name" :class="{ active: 1 === item.active }">
        <a href="javascript:;" @click="scrollTo(item)">{{ item.name }}</a>
      </li>
    </ul>

  </div>
</template>

<script>
export default {
  props: {},
  data() {
    return {
      menu: [
        {
          name: '公路运输',
          id: 'gonglu',
          active: 0, // 用来判断侧边栏的激活状态,为1则代表激活,class的active为true。
        },
        {
          name: '铁路运输',
          id: 'tielu',
          active: 0,
        },
        {
          name: '水路运输',
          id: 'shuilu',
          active: 0,
        },
        {
          name: '仓储统配',
          id: 'cangchu',
          active: 0,
        }
      ]
    }
  },
  mounted() {
    //获取每个模块的参数,因为模块在整页渲染完毕后,offsetTop和scrollHeight值已经是确立的,所以在滚动监听之前可以先获取,暂存在menu中。
    this.menu.forEach(item => {
      item.offsetTop = document.getElementById(item.id).offsetTop;
      item.scrollHeight=document.getElementById(item.id).scrollHeight;
    })
    // 监听滚动事件
    window.addEventListener('scroll', this.onScroll)
  },
  destroy() {
    // 必须移除监听器,不然当该vue组件被销毁了,监听器还在就会出错
    window.removeEventListener('scroll', this.onScroll)
  },
  methods: {
    // 滚动监听器
    onScroll() {
     // 获取浏览器窗口高度
      let clientHeight=window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
      // 获取当前文档流的 scrollTop
      let scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      // 屏幕垂直中线
      let screenMiddle=scrollTop+clientHeight/2;
      this.menu.forEach(item=>{
        //以浏览器窗口垂直中线为准,中线在内容模块的上边界至下边界之间,导航栏都认为在该楼层
        if(screenMiddle-item.offsetTop>=0&&screenMiddle-item.offsetTop<item.scrollHeight){
          item.active=1;
        }else{
          item.active=0;
        }
      })
    },
    // 跳转到指定索引的元素
    scrollTo(item) {
      // 获取目标的 offsetTop
      // css选择器是从 1 开始计数,我们是从 0 开始,所以要 +1
      const targetOffsetTop = document.getElementById(item.id).offsetTop
      // 获取当前 offsetTop
      let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
      // 定义一次跳 50 个像素,数字越大跳得越快,但是会有掉帧得感觉,步子迈大了会扯到蛋
      const STEP = 50
      // 判断是往下滑还是往上滑
      if (scrollTop > targetOffsetTop) {
        // 往上滑
        smoothUp()
      } else {
        // 往下滑
        smoothDown()
      }
      // 定义往下滑函数
      function smoothDown() {
        // 如果当前 scrollTop 小于 targetOffsetTop 说明视口还没滑到指定位置
        if (scrollTop < targetOffsetTop) {
          // 如果和目标相差距离大于等于 STEP 就跳 STEP
          // 否则直接跳到目标点,目标是为了防止跳过了。
          if (targetOffsetTop - scrollTop >= STEP) {
            scrollTop += STEP
          } else {
            scrollTop = targetOffsetTop
          }
          document.body.scrollTop = scrollTop
          document.documentElement.scrollTop = scrollTop
          // 关于 requestAnimationFrame 可以自己查一下,在这种场景下,相比 setInterval 性价比更高
          requestAnimationFrame(smoothDown)
        }
      }
      // 定义往上滑函数
      function smoothUp() {
        if (scrollTop > targetOffsetTop) {
          if (scrollTop - targetOffsetTop >= STEP) {
            scrollTop -= STEP
          } else {
            scrollTop = targetOffsetTop
          }
          document.body.scrollTop = scrollTop
          document.documentElement.scrollTop = scrollTop
          requestAnimationFrame(smoothUp)
        }
      }
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style lang="scss" scoped>

.main {
  padding: 30px;
  padding-right: 240px;

  .content {
    height: 70vh;
    background: #eee;
    border-radius: 8px;
    margin-bottom: 15px;
  }

  .header {
    padding: 15px;
    font-size: 20px;
    font-weight:600;
    color:black
  }
}

.sidebar {
  position: fixed;
  top: 50%;
  right: 50px;
  width: 168px;
  overflow: hidden;
  background-color: #fff;
  border-radius: 8px;
  padding: 0;
  transform: translateY(-50%);
  box-shadow: 0px 1px 10px 0px rgba(0, 0, 0, 0.05), 0px 3px 5px 0px rgba(0, 0, 0, 0.06), 0px 2px 4px -1px rgba(0, 0, 0, 0.04);

  li {
    line-height: 40px;
    text-align: center;
    list-style: none;

    &:not(:last-child) {
      border-bottom: #f5f5f5 solid 1px;
    }

    &.active {
      color: #fff;
      background-color: #ff8000;
    }

    a {
      display: block;
      width: 100%;
      height: 100%;
      color: #333;
      text-decoration: none;
    }
  }
}
</style>

相关文章

网友评论

      本文标题:Vue2实现电梯导航(内容随锚点滚动+动画)

      本文链接:https://www.haomeiwen.com/subject/zcgemdtx.html