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

结语

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

相关推荐
混血哲谈1 小时前
如何使用webpack预加载 CSS 中定义的资源和预加载 CSS 文件
前端·css·webpack
浪遏3 小时前
我的远程实习(二) | git 持续更新版
前端
智商不在服务器3 小时前
XSS 绕过分析:一次循环与两次循环的区别
前端·xss
_Matthew3 小时前
JavaScript |(四)正则表达式 | 尚硅谷JavaScript基础&实战
开发语言·javascript·正则表达式
MonkeyKing_sunyuhua3 小时前
npm WARN EBADENGINE required: { node: ‘>=14‘ }
前端·npm·node.js
Hi-Jimmy4 小时前
【VolView】纯前端实现CT三维重建-CBCT
前端·架构·volview·cbct
janthinasnail4 小时前
编写一个简单的chrome截图扩展
前端·chrome
拉不动的猪4 小时前
刷刷题40(vue中计算属性不能异步,如何实现异步)
前端·javascript·vue.js
冴羽yayujs5 小时前
SvelteKit 最新中文文档教程(6)—— 状态管理
前端·javascript·vue.js·前端框架·react·svelte·sveltekit
烛阴5 小时前
前端进阶必学:JavaScript Class 的正确打开方式,让你代码更清晰!
前端·javascript