> 文章列表 > vue悬浮导航实现内容滚动时,导航跟随选中,点击导航滚动到相应位置,

vue悬浮导航实现内容滚动时,导航跟随选中,点击导航滚动到相应位置,

vue悬浮导航实现内容滚动时,导航跟随选中,点击导航滚动到相应位置,

在这里插入图片描述

<template><div><div class="goods_navBg" ref="navHeader">模拟悬浮导航</div><div class="goods_box">box</div><!-- 吸顶导航 start --><GoodsInfoNavTab class="goods_sticky" :navHeaderHeight="navHeaderHeight" :tabTitle="tabTitle"></GoodsInfoNavTab><!-- 吸顶导航 end --><ul class="ulMain"><li class="goodsMBox" v-for="(item, index) in tabTitle" :key="index">{{ item }}</li></ul></div>
</template>
<script>
export default {name: 'goodsPage',data() {return {tabTitle: ['Tab1', 'Tab2', 'Tab3', 'Tab4'],//子导航navHeaderHeight: 0,//子导航高度}},mounted() {//获取悬浮导航的高度this.navHeaderHeight = this.$refs.navHeader.offsetHeight;},methods: {}
}
</script>
<style scoped>
.goods_navBg {width: 7.5rem;height: 0.88rem;background: red;z-index: 999;position: fixed;top: 0;left: 0;text-align: center;font-size: 0.4rem;line-height: 0.88rem;color: white;
}.goods_box {width: 100%;height: 5rem;background: green;margin-top: 0.88rem;font-size: 1rem;text-align: center;line-height: 5rem;
}.goods_sticky {position: sticky;top: 0.88rem;z-index: 99;
}.ulMain {width: 100%;
}.goodsMBox {height: 10rem;font-size: 0.5rem;color: white;
}.ulMain li:nth-child(1) {background: blue;
}.ulMain li:nth-child(2) {background: blueviolet;
}.ulMain li:nth-child(3) {background: orange;
}.ulMain li:nth-child(4) {background: pink;
}</style>

导航组件:

<template><div><ul class="goods_ceiling" ref="navTab"><a v-for="(item, index) in tabTitle" :key="index" @click="scrollToPosition(index)":class="{ js_goods_ceilTitle: active == index }"><li class="goods_ceilTitle">{{ item }}</li></a></ul></div>
</template>
<script>
export default {name: 'GoodsInfoNavTab',props: {tabTitle: {type: Array,default: () => []},navHeaderHeight: {type: Number,default: 0}},data() {return {active: 0,// 当前激活的导航索引}},mounted() {//监听页面滚动条事件window.addEventListener("scroll", this.onScroll);},destroy() {// 必须移除监听器,不然当该vue组件被销毁了,监听器还在就会出错window.removeEventListener("scroll", this.onScroll);},methods: {onScroll() {let navTabHeight = this.$refs.navTab.offsetHeight //获取子导航的高度let navHeaderHeight = this.navHeaderHeight //获取悬浮导航的高度// 获取所有锚点元素const navContents = document.querySelectorAll('.goodsMBox')// 所有锚点元素的 offsetTopconst offsetTopArr = []navContents.forEach(item => {offsetTopArr.push(item.offsetTop)})// 获取当前文档流的 scrollTopconst scrollTop = document.documentElement.scrollTop || document.body.scrollTop// 定义当前点亮的导航下标let navIndex = 0for (let n = 0; n < offsetTopArr.length; n++) {// 如果 scrollTop 大于等于第 n 个元素的 offsetTop 则说明 n-1 的内容已经完全不可见// 那么此时导航索引就应该是 n 了if (scrollTop >= offsetTopArr[n] - navTabHeight - navHeaderHeight) {navIndex = n}}// 把下标赋值给 vue 的 datathis.active = navIndex},scrollToPosition(index) {let navTabHeight = this.$refs.navTab.offsetHeight //获取子导航的高度let navHeaderHeight = this.navHeaderHeight //获取悬浮导航的高度// 获取目标的 offsetTop// css选择器是从 1 开始计数,我们是从 0 开始,所以要 +1const targetOffsetTop = document.querySelector(`.goodsMBox:nth-child(${index + 1})`).offsetTop - navTabHeight - navHeaderHeight// 获取当前 offsetToplet 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 = scrollTopdocument.documentElement.scrollTop = scrollTop// 屏幕在绘制下一帧时会回调传给 requestAnimationFrame 的函数// 关于 requestAnimationFrame 可以自己查一下,在这种场景下,相比 setInterval 性价比更高requestAnimationFrame(smoothDown)}}// 定义往上滑函数function smoothUp() {if (scrollTop > targetOffsetTop) {if (scrollTop - targetOffsetTop >= STEP) {scrollTop -= STEP} else {scrollTop = targetOffsetTop}document.body.scrollTop = scrollTopdocument.documentElement.scrollTop = scrollToprequestAnimationFrame(smoothUp)}}},}
}
</script>
<style lang="less" scoped>
.goods_ceiling {width: 7.5rem;height: 1rem;background: white;.flex_item;.f32;justify-content: space-around;
}.goods_ceiling a {.c;
}.goods_ceilTitle {height: 1rem;line-height: 1rem;text-align: center;
}.js_goods_ceilTitle {font-weight: bold;position: relative;
}.js_goods_ceilTitle::after {content: '';position: absolute;bottom: 0;left: 50%;margin-left: -0.3rem;width: 0.6rem;height: 0.08rem;border-radius: 0.18rem;background: @c3b8;
}
</style>