avatar

麦兜的小站

MDO.INK

  • 首页
  • 随笔
  • 知识库
  • 归档
  • 动态
  • 标签
  • 关于
Home Vue实现顶部导航跟随页面联动效果
文章

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

Posted 2025-06-25 Updated 2025-06- 25
By power 已删除用户
12~15 min read

需求背景

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

要实现的效果预览

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

代码实现

<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>
<script>
export default {
  name: 'Anchor',
  data() {
    return {
      scrollTop: 0,

      navClickFlag: false,

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

结语

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

前端
License:  CC BY 4.0
Share

Further Reading

Jun 25, 2025

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

需求背景 最近也是应我一位同学的要求,给他的公司制作一个官网,那也就是拾起了一些基础的知识,搜索了一些其它的文章有实现这种效果的,但大多导航都是在侧边,然后点击导航对应的部分内容滑到最上面,跟我这次设...

Jun 20, 2025

Share.js - 一键分享到微博、QQ空间、QQ好友、微信、腾讯微博、豆瓣、Facebook、Twitter、Linkedin、Google+、点点

一键分享到微博、QQ空间、QQ好友、微信、腾讯微博、豆瓣、Facebook、Twitter、Linkedin、Google+、点点等社交网站,使用字体图标。 有两种安装方式: 使用npm npmi...

Jun 19, 2025

node express实现热更新

在使用node进行开发的时候,每次修改文件,都需要重启express服务很麻烦 我们可以使用nodemon这个库,修改文件后可以自动重启express服务。 安装: npminstall--save-...

OLDER

nodejs实现的定时爬取微博热搜

NEWER

几款好用的免费 SSH 工具推荐

Recently Updated

  • 如何实现接口幂等性
  • 10个npm工具包
  • How to set up PHP7.4 on MacOS.
  • Automa:一键自动化,网页数据采集与工作流程优化专家Automa:解锁自动化
  • Mac 下用 brew 搭建 LNMP

Trending Tags

thinkphp clippings

Contents

©2025 麦兜的小站. Some rights reserved.

Using the Halo theme Chirpy