irpas技术客

vue实现导航栏吸顶效果 + 与内容联动_sfyjknvcx_vue悬浮吸顶导航

大大的周 8463

大家好,我是南宫。今天写一篇博客来整理下最近刚解决的一个问题,那就是导航栏跟内容联动的问题。

?

简单说一下我想要的效果:写了一个宽度为屏幕100%的div,居中的部分是一个导航栏,水平排列,默认位于banner下,如果页面滚到了banner下面,要让导航栏固定顶部。如果页面滚到了下方对应的内容,那就高亮对应的tab标记。如果点击了tab,那就要让页面滚到对应的内容,并且让该tab高亮。

(效果是动态的,我随便截取一个场景来展示吧,比如我点击“应用场景”的时候,页面滚动到了对应的内容区域,并且对应的tab高亮了,也能看到导航栏固定顶部)

拆解一下,可以分为这么几个部分:①吸顶、②选择tab,可以让页面滚动到对应内容的位置、③页面滚动到了对应的内容的位置,可以设置tab的选择。

吸顶的实现:

这个是最容易的,我先简单说一下原理:默认导航栏是位于banner下方的,也就是普通的标准流元素,没有浮动没有定位,是正常占位的;而固定顶部的状态下,导航栏被固定定位(position: fixed)到了顶部,这个时候可以设置一下top。我们可以判断当前滚动的位置是否需要固定定位,如果是,那就做一下样式的切换。

显然导航栏有两种状态,所以我们可以写两个class来分别控制。(我这里使用SCSS,可以参考一下我的代码,nav-bar是默认状态,再加上fixed的是固定顶部的状态)

// 导航条 .nav-bar { height: 61px; background: url("/img/download/rect_bg.png") repeat-x; background-size: 5px 61px; &.fixed { position: fixed; top: 56px; left: 0; right: 0; z-index: 10; } .nav-bar-item { position: relative; margin: 0 47px; line-height: 55px; cursor: pointer; &:hover::after, &.active::after { content: ""; position: absolute; bottom: 0; left: 0; right: 0; height: 4px; background: $dark-blue; } .nav-text { font-size: 16px; color: #333; } } }

那么怎么控制导航栏是否固定顶部呢?可以在data里放一个变量(如isFixed)来控制,默认为false。监听一下页面的滚动,如果滚动超过了某个值,就让isFixed改为true,否则改为false。然后在导航栏这边,动态绑定class,“fixed”在isFixed为true才设置上去。

data() { return { // 是否固定 isFixed: false, //...其他data } } // 处理滚动 handleScroll() { var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop // console.log(scrollTop) // 滑动的长度 var offsetTop = document.querySelector('.banner').offsetHeight // 吸顶效果 if (scrollTop > offsetTop) { this.isFixed = true } else { this.isFixed = false } }, <div class="nav-bar" :class="{ fixed: isFixed }">...</div>

Vue项目中如何监听页面的滚动呢?把刚才的handleScroll定义到组件的methods里面,在mounted的时候绑定window的滚动时间,注意是在mounted的时候才可以使用DOM。退出页面的时候,也要记得销毁监听。

mounted() { window.addEventListener('scroll', this.handleScroll) // 监听滑动事件 }, destroyed() { window.removeEventListener('scroll', this.handleScroll) // 销毁监听滑动事件 }

点击选择tab,让页面滚动到对应内容的实现:

首先,tab会有普通状态和高亮状态,所以我们也需要用一个变量来控制当前选中的、处于高亮状态的tab是第几个,默认为0,让第一个tab高亮。

data() { return { // ...其他data // 当前选择的tab currentNav: 0, } } <!-- 这是导航条里的每一个tab,给它动态绑定active类,高亮才显示,并且绑定了点击事件 --> <div v-for="(item, index) in obj.contentList" :key="index" class="nav-bar-item" :class="{ active: currentNav == index }" @click="toBox(index)" > <span class="nav-text">{{ item }}</span> </div>

然后,页面上有好几块内容,每一块对应一个tab,我们给这几块内容的最外层加上一个class,作为标记,便于选择这些锚点元素,比如我给它们加上“j-content”这个类名。这样就实现了导航栏和每块内容的绑定。

<!-- 这是其中的一块内容 --> <div class="scene j-content"> <h3 class="production-title">应用场景</h3> <!-- 其他内容 --> </div>

点击tab的时候,我们可以获取到当前点击的tab的索引(比如第1个的索引是0),在带有j-content的div里,我们找到下标相同的div(如索引为0的时候找到第一个这样的div),获取它顶部的坐标,让页面滚动到这里。

(看下面的代码,从所有的带有j-content的class里找到对应当前索引的div,获取它的offsetTop,然后用window.scrollTo平滑滚动到指定地方,这里用behavior设置了平滑滚动)

// 滚动到哪一块 toBox(index) { this.currentNav = index const DOM = document.querySelectorAll('.j-content')[index] const offsetTop = DOM.offsetTop - 25 // console.log('滚动到哪里', DOM) window.scrollTo({ top: offsetTop, behavior: 'smooth' }) },

页面滚动到对应内容,设置tab高亮的实现:

首页,要判断页面滚到到了哪一块内容,就得监听页面的滚动事件,所以判断的代码需要写到handleScroll方法里。

然后滚动的时候,滚动的位置跟哪些值比较呢?这就需要我们记录每一块内容的offsetTop的位置。根据之前定好的class,找到每一块内容的div,获取一下它们各自的offsetTop,并保存。

// 获取所有锚点元素 const divs = [...document.querySelectorAll('.j-content')] // 将所有锚点元素offsetTop push到数组内 divs.forEach((item, index) => { this.contentTopList[index] = item.offsetTop - 25 })

接着,在滚动的过程中,怎么确定当前要高亮的tab是哪个呢?我们可以把当前的scrollTop值与每一个div的offsetTop比较,找到“小于但又最接近”的那个值,把这个下标作为要高亮的tab的下标。

(我补充了一个判断,假如刚开始滚,还没有滚到第一个内容区域的话,navIndex会算出来undefined,为了让这个时候也有tab被高亮,我认为当前高亮的是0.)

let navIndex // 滚动定位tab高亮的状态 for (let i = 0; i < this.contentTopList.length; i++) { // 如果当前滚动的top坐标大于第i个的top坐标,就记录下i。 // 记录到最后,i就会是最后一个满足条件的i,也就是刚刚好的那个值 if (scrollTop >= this.contentTopList[i]) { navIndex = i } } // 把下标赋值给 vue 的 data this.currentNav = navIndex if (typeof navIndex !== 'number') { this.currentNav = 0 }

到这一步,就已经做到了滚动吸顶、点击tab滚动到相应的锚点、滚动高亮对应的tab。但是在调试的时候我又发现了一个问题——点击tab,页面滚动的过程中,经过其他区域的时候,也会点亮对应的tab,这就显得效果有些拖泥带水,不像是被直接定位过去的。

于是我觉得,需要区分这两种情况:“因为点击而直接滚动到这里”? 和 “页面自己滚动的时候路过这里”。我在data里加了一个新的变量,叫isClick,默认为false,表示不是点击定位的。在点击tab后,瞬间把isClick赋值为true。然后在“根据滚动位置,判断高亮tab”之前,先判断isClick,确定不是点击定位的才判断。在点击动作完成、滚动完毕后,过一小段时间,把isClick还原成true,以便恢复后续的滚动高亮效果。

完整的handleScroll和toBox代码在这里:

// 处理滚动 handleScroll() { var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop // console.log(scrollTop) // 滑动的长度 var offsetTop = document.querySelector('.banner').offsetHeight // 判断是否已经记录了每个内容的top,如果不是,就记录一下。如果是,就直接使用 // 获取所有锚点元素 const divs = [...document.querySelectorAll('.j-content')] // 将所有锚点元素offsetTop push到数组内 divs.forEach((item, index) => { this.contentTopList[index] = item.offsetTop - 25 }) // if (this.contentTopList.length === 0) { // } // 判断当前是否是点击定位的,如果不是,才有滚动定位的效果 if (!this.isClick) { let navIndex // 滚动定位tab高亮的状态 for (let i = 0; i < this.contentTopList.length; i++) { // 如果当前滚动的top坐标大于第i个的top坐标,就记录下i。记录到最后,i就会是最后一个满足条件的i,也就是刚刚好的那个值 if (scrollTop >= this.contentTopList[i]) { navIndex = i } } // 把下标赋值给 vue 的 data this.currentNav = navIndex if (typeof navIndex !== 'number') { this.currentNav = 0 } } // 吸顶效果 if (scrollTop > offsetTop) { this.isFixed = true } else { this.isFixed = false } }, // 滚动到哪一块 toBox(index) { // 点击滚动到指定的位置,要去掉滚动的过程中因为位置变化带来的效果 this.isClick = true this.currentNav = index const DOM = document.querySelectorAll('.j-content')[index] const offsetTop = DOM.offsetTop - 25 // console.log('滚动到哪里', DOM) window.scrollTo({ top: offsetTop, behavior: 'smooth' }) // 过一段时间,把isClick还原 setTimeout(() => { this.isClick = false }, 800) },

?还有20分钟就是新年了,我总算赶在2021年写完了这一篇博客,欢迎有类似需要的小伙伴来探讨哦,谢谢大家!


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #vue悬浮吸顶导航 #大家好我是南宫