Vue实现顶部导航跟随页面联动效果

需求背景

最近也是应我一位同学的要求,给他的公司制作一个官网,那也就是拾起了一些基础的知识,搜索了一些其它的文章有实现这种效果的,但大多导航都是在侧边,然后点击导航对应的部分内容滑到最上面,跟我这次设计的交互有些偏差,所以自己也是实现了一把,比较简单,大体逻辑跟其它的比较类似。

要实现的效果预览

  1. 导航固定在顶部,背景色透明,向上滑动时背景色变为白色
  2. 向上滑动时候当相应的内容即将没过头部时,对应的导航变为激活态
  3. 当滑到最底部时,最后的导航设为激活态,因为有的设计稿最后一块儿的区域高度是撑不开一屏的,所以最后一个永远不会高亮
  4. 点击导航滑到对应的区域上,实现两者的联动效果

代码实现

vue 复制代码
<template>
  <div
    class="header"
    :style="{
      backgroundColor: scrollTop || navActive > 1 ? '#fff' : 'transparent'
    }"
  >
    <div class="header-box">
      <div class="logo-container">logo</div>
      <div class="navBox">
        <div
          v-for="item in navList"
          :key="item.value"
          :class="['navItem', navActive == item.value ? 'active' : '']"
          @click="goAnchor(item.id, item.value)"
        >
          {{ item.label }}
        </div>
      </div>
    </div>
  </div>
  <div class="box-col" id="box">
    <div id="part1">核心产品</div>
    <div id="part2">应用场景</div>
    <div id="part3">服务领域</div>
    <div id="part4">联系我们</div>
  </div>
</template>
js 复制代码
<script>
export default {
  name: 'Anchor',
  data() {
    return {
      scrollTop: 0,
      //锚点点击标识, 不然active就会来回抖动
      navClickFlag: false,
      // 滚动元素id
      scrollWrap: 'box',
      navActive: 1,
      navList: [
        {
          label: '核心产品',
          value: 1,
          id: 'part1'
        },
        {
          label: '应用场景',
          value: 2,
          id: 'part2'
        },
        {
          label: '服务领域',
          value: 3,
          id: 'part3'
        },
        {
          label: '联系我们',
          value: 4,
          id: 'part4'
        }
      ],
      // 各锚点的高度
      offTopLs: []
    }
  },
  mounted() {
    // 监听滚动
    window.addEventListener('scroll', this.handleScroll, true)
    this.$nextTick(() => {
      this.getoffTop()
    })
  },
  beforeDestroy() {
    // 销毁监听
    window.removeEventListener('scroll', this.handleScroll)
  },
  methods: {
    // 计算锚点的高度
    getoffTop() {
      let offTop = []
      for (let index = 0; index < this.navList.length; index++) {
        const element = document.getElementById(this.navList[index].id)

        if (element) offTop.push(element.offsetTop)
      }
      this.offTopLs = offTop
    },
    // 监听滚动
    handleScroll(event) {
      console.log(document.querySelector('.header').clientHeight)
      const { scrollTop, scrollHeight, clientHeight } = event.target
      const scrollWrap = document.getElementById(this.scrollWrap)
      if (!this.navClickFlag && scrollWrap) {
        let scTop = scrollWrap.scrollTop
        this.scrollTop = scTop
        for (let index = 0; index < this.offTopLs.length; index++) {
          const element = this.offTopLs[index]
          let offsetTop = scrollWrap.offsetTop + 88
          if (scTop + offsetTop >= element) {
            this.navActive = index + 1
          }
          if (scrollTop + clientHeight >= scrollHeight) {
            // 滚动到底部,高亮最后一个
            this.navActive = this.navList.length
          }
        }
      }
    },
    // 锚点导航
    goAnchor(keyId, val) {
      if (this.navClickFlag || this.navActive === val) return
      if (val === 1) this.scrollTop = 0
      this.navClickFlag = true
      this.navActive = val

      const targetElement = document.getElementById(keyId)
      if (targetElement) {
        const offsetTop = targetElement.offsetTop
        const scrollContainer = document.getElementById(this.scrollWrap)
        if (scrollContainer) {
          scrollContainer.scrollTo({
            top: offsetTop - 88,
            behavior: 'smooth'
          })
        }
      }

      setTimeout(() => {
        this.navClickFlag = false
      }, 1000)
    }
  }
}
</script
css 复制代码
<style scoped lang="scss">
html {
  overflow: hidden;
}
.box-col {
  height: 100vh;
  overflow-x: hidden;
  overflow-y: auto;
  box-sizing: border-box;
  scroll-behavior: smooth;
}

.header {
  width: 100vw;
  background-color: #fff;
  height: 88px;
  position: fixed;
  z-index: 10;
  transition: background-color 0.3s;

  &-box {
    width: 1440px;
    height: 100%;
    display: flex;
    margin: 0 auto;
    justify-content: space-between;
    align-items: center;

    .logo-container {
      width: 199px;
      height: 42px;
      font-size: 24px;
    }

    .navBox {
      margin-left: 10px;
      display: flex;

      .navItem {
        color: #000000;
        padding: 8px 35px;
        cursor: pointer;
        &.active {
          border-radius: 5px;
          color: #fff;
          background: rgb(0, 108, 238);
        }
      }
    }
  }
}

#part1,
#part2,
#part3,
#part4 {
  height: 500px;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 24px;
}

#part1 {
  background-color: #ccc;
}
#part2 {
  background-color: #ccd;
}
#part3 {
  background-color: #cad;
}
#part4 {
  background-color: #cba;
}
</style>

遇到的问题及解决

一开始采用的是scrollIntoView,即:document.getElementById(keyId).scrollIntoView(true),但是这种方式有个弊端,不太灵活,会滚动到最顶部,导致有偏差,所以后面换成scrollTo的方式,灵活控制需要滚动的数值

结语

这里也是记录下,有需求的同学直接代码复制到项目中就能使用,避免了重复造轮子的缺陷,希望能帮助到大家。

相关推荐
京东云开发者2 分钟前
京东Taro Native框架静态布局直渲提速
前端
程序员小羊!3 分钟前
03JavaScript预备知识
前端
前端的阶梯3 分钟前
Cursor 开发 Python 项目完全指南
前端·人工智能·后端
半兽先生4 分钟前
flv.js解决其中一个监控断线导致其他的监控播放阻塞
开发语言·javascript·ecmascript
艾伦野鸽ggg12 分钟前
JavaScript 基础语法速通
前端·javascript
不懂的浪漫13 分钟前
AI 时代还需要买课吗?我用 Skills + Markdown + HTML 搭了一套自学系统
前端·人工智能·html·skill
前端的阶梯16 分钟前
Conda 开发 Python 程序完全指南
前端·人工智能·后端
zhengfei61118 分钟前
第2章 Agent 核心组件深度解析
前端·javascript·react.js
Linsk23 分钟前
前端代码压缩对浏览器兼容性的影响
前端
yingyima27 分钟前
凌晨3点的闹钟:分布式定时任务设计实战
前端