超详细Vue实现导航栏绑定内容锚点+滚动动画+vue

您所在的位置:网站首页 html侧边导航栏跟随页面滚动 超详细Vue实现导航栏绑定内容锚点+滚动动画+vue

超详细Vue实现导航栏绑定内容锚点+滚动动画+vue

2024-06-04 07:06| 来源: 网络整理| 查看: 265

最终效果

首先是一些说明

动画效果是原生 js 实现,不可能为了这么一个动画引用 Jquery 这么大一个库 因为是在 vue-router 的 hash 模式下,所以就不能用herf="#contentx"这样的方式来跳转了,这里用 js 来滚动到指定元素位置

文章整体思路是先布局,然后把内容和导航联动起来,最后来实现点击导航滚动到指定内容

布局

话不多说,上代码

navs.vue

content-0 content-1 content-2 content-3 content-4 content-0 content-1 content-2 content-3 content-4 export default { props: {}, data() { return { active: 0 // 当前激活的导航索引 } }, methods: {} } /* 内容区的样式 */ .content { background-color: white; width: 500px; } .content div { width: 100%; height: 600px; font-size: 36px; padding: 20px; background-color: #7ec384; } .content div:nth-child(2n) { background-color: #847ec3; } /* 导航栏的样式 */ .navs { position: fixed; top: 80px; left: 700px; background-color: #efefef; } .navs li { padding: 0 20px; line-height: 1.6; font-size: 24px; } /* 当导航被点亮后改变颜色 */ .navs .active{ color: #847ec3; background-color: #e2e2e2; }

布局很简单,就左边内容,右边导航。导航和内容本来可以用 v-for 来生成的,但是为了看起来更简单明了,我就没那么写。先来看看布局效果吧!

布局效果图 内容导航联动

这一块开始变得有意思了起来,首先我们要搞懂元素的两个属性:

scrollTop offsetTop scrollTop

先看看一句话定义吧

一个元素的 scrollTop 值是这个元素的顶部到视口可见内容(的顶部)的距离的度量。当一个元素的内容没有产生垂直方向的滚动条,那么它的 scrollTop 值为0。

引用自 https://developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollTop

如果还不理解,那么我画张图,大概就懂了,相信读者也知道文档流是个啥,我们先来个文档流

元素文档流

好了,现在这个文档流有点长,我们的视口不够高,于是只能显示一部分,其余的要滚动查看,看起来像这样

视口可以看见的内容

这个时候我们看见的是最顶部的内容,这个文档流的 scrollTop 此时等于 0,让我们把视口往下移一点,也就是滚动一下窗口,查看下面的内容。

scrollTop值

OK,到这里我相信你一定已经理解了 scrollTop

offsetTop

还是先一句话定义吧

HTMLElement.offsetTop 为只读属性,它返回当前元素相对于其 offsetParent 元素的顶部内边距的距离。

引用自 https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement/offsetTop

关于 offsetParent 网上有的定义是

与当前元素最近的经过定位( position 不等于 static )的父级元素

具体情况分为以下几种:

position为fixed时,offsetParent为null,offsettop的值和top相等。此时元素是以视口来定位的。

position非fixed,父级元素无定位(static)时,offsetParent为body。

position非fixed,父级元素有定位时,offsetParent为最近的有定位的父级元素。

body元素,offsetParent为null,offsettop为0(似乎是废话)。

引用自 https://www.jianshu.com/p/135731ec13f1

我们这里是属于第二种情况。

可能到这里你有点蒙,那还是拿刚才那张图来举例

image

好了,关于 offsetTop 你也了解了,我们就可以开始写代码了。

监听滚动

当元素发生滚动时,会触发 scroll事件,我们就在 vue 的 mounted 钩子中添加监听好了,修改 vans.vue 文件

export default { props: {}, data() { return { active: 0 // 当前激活的导航索引 } }, mounted() { // 监听滚动事件 window.addEventListener('scroll', this.onScroll) }, destroy() { // 必须移除监听器,不然当该vue组件被销毁了,监听器还在就会出错 window.removeEventListener('scroll', this.onScroll) }, methods: { onScroll() { // 滚动监听器 } } }

现在我们开始写监听回调

// 滚动监听器 onScroll() { // 获取所有锚点元素 const navContents = document.querySelectorAll('.content div') // 所有锚点元素的 offsetTop const offsetTopArr = [] navContents.forEach(item => { offsetTopArr.push(item.offsetTop) }) // 获取当前文档流的 scrollTop const scrollTop = document.documentElement.scrollTop || document.body.scrollTop // 定义当前点亮的导航下标 let navIndex = 0 for (let n = 0; n < offsetTopArr.length; n++) { // 如果 scrollTop 大于等于第 n 个元素的 offsetTop 则说明 n-1 的内容已经完全不可见 // 那么此时导航索引就应该是 n 了 if (scrollTop >= offsetTopArr[n]) { navIndex = n } } // 把下标赋值给 vue 的 data this.active = navIndex }

到了这里,我们的导航已经可以跟着滚动来变化了,看效果图

监听滚动效果图 用JS来滚动到指定元素

scrollTop 是可读可写的,我们只需要把目标元素的 offsetTop 赋值给 scrollTop 就可以让视口自动跳过去,但是直接跳过去是很生硬的,所以我们要一点一点的跳过去,只要速度够快,人眼看上去就是动画效果。好了,开始码代码。

给导航添加点击点击事件

content-0 content-1 content-2 content-3 content-4

新增滚动函数

methods: { ... // 跳转到指定索引的元素 scrollTo(index) { // 获取目标的 offsetTop // css选择器是从 1 开始计数,我们是从 0 开始,所以要 +1 const targetOffsetTop = document.querySelector(`.content div:nth-child(${index + 1})`).offsetTop // 获取当前 offsetTop let 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 = scrollTop document.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 = scrollTop document.documentElement.scrollTop = scrollTop requestAnimationFrame(smoothUp) } } } } } 最终局

好了,我们看看最终效果

最终效果

最终代码

content-0 content-1 content-2 content-3 content-4 content-0 content-1 content-2 content-3 content-4 export default { props: {}, data() { return { active: 0 // 当前激活的导航索引 } }, mounted() { // 监听滚动事件 window.addEventListener('scroll', this.onScroll, false) }, destroy() { // 必须移除监听器,不然当该vue组件被销毁了,监听器还在就会出错 window.removeEventListener('scroll', this.onScroll) }, methods: { // 滚动监听器 onScroll() { // 获取所有锚点元素 const navContents = document.querySelectorAll('.content div') // 所有锚点元素的 offsetTop const offsetTopArr = [] navContents.forEach(item => { offsetTopArr.push(item.offsetTop) }) // 获取当前文档流的 scrollTop const scrollTop = document.documentElement.scrollTop || document.body.scrollTop // 定义当前点亮的导航下标 let navIndex = 0 for (let n = 0; n < offsetTopArr.length; n++) { // 如果 scrollTop 大于等于第n个元素的 offsetTop 则说明 n-1 的内容已经完全不可见 // 那么此时导航索引就应该是n了 if (scrollTop >= offsetTopArr[n]) { navIndex = n } } this.active = navIndex }, // 跳转到指定索引的元素 scrollTo(index) { // 获取目标的 offsetTop // css选择器是从 1 开始计数,我们是从 0 开始,所以要 +1 const targetOffsetTop = document.querySelector(`.content div:nth-child(${index + 1})`).offsetTop // 获取当前 offsetTop let 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 = scrollTop document.documentElement.scrollTop = scrollTop // 关于 requestAnimationFrame 可以自己查一下,在这种场景下,相比 setInterval 性价比更高 requestAnimationFrame(smoothDown) } } // 定义往上滑函数 function smoothUp() { if (scrollTop > targetOffsetTop) { if (scrollTop - targetOffsetTop >= STEP) { scrollTop -= STEP } else { scrollTop = targetOffsetTop } document.body.scrollTop = scrollTop document.documentElement.scrollTop = scrollTop requestAnimationFrame(smoothUp) } } } } } /* 内容区的样式 */ .content { background-color: white; width: 500px; } .content div { width: 100%; height: 600px; font-size: 36px; padding: 20px; background-color: #7ec384; } .content div:nth-child(2n) { background-color: #847ec3; } /* 导航栏的样式 */ .navs { position: fixed; top: 80px; left: 700px; background-color: #efefef; } .navs li { padding: 0 20px; line-height: 1.6; font-size: 24px; } /* 当导航被点亮后改变颜色 */ .navs .active{ color: #847ec3; background-color: #e2e2e2; }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3