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的方式,灵活控制需要滚动的数值

结语

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

相关推荐
橙子家21 分钟前
浏览器缓存之【结构化数据库与缓存】: IndexedDB、Cache storage 和 Storage buckets
前端
user205855615181327 分钟前
X6 中边悬浮置顶,规避 `mouseleave` 事件丢失问题
前端
李明卫杭州28 分钟前
CSS aspect-ratio 属性完全指南
前端
Pedantic2 小时前
SwiftUI 手势层级(Gesture Hierarchy)详解
前端
飘尘3 小时前
前端转型全栈(Java后端)的快速上手指引
前端·后端·全栈
一颗烂土豆3 小时前
Meshopt 压缩深度解析,为什么它比 Draco 更快
前端·javascript·webgl
浏览器工程师4 小时前
AI Agent 接浏览器任务,先别让它一路点到底
前端·后端
雨季mo浅忆4 小时前
VSCode自动格式化三要素
前端
爱勇宝5 小时前
深扒 Anthropic 1680 位工程师简历:应届生几乎没机会,AI 公司最缺的不是博士
前端·后端·程序员