实现一个匀速滚动的页内侧边栏导航

您所在的位置:网站首页 iqoo3侧边 实现一个匀速滚动的页内侧边栏导航

实现一个匀速滚动的页内侧边栏导航

2023-07-17 04:18| 来源: 网络整理| 查看: 265

页面布局

搭建一个简单的页面案例,左侧是滚动浏览内容,右侧是侧边导航栏。

滚动内容需要和侧边栏菜单进行联动,给主题内容块绑定一个id,以便可以获取对应的元素,这里不用vue的ref获取元素主要是因为:在正式开发中,通过会拆分组件进行开发,侧边栏通常不会和主体内容放到同一个文件里,所以ref在功能实现上不太合适。

{{item.name}} {{item.name}} 复制代码 import { defineComponent, ref } from 'vue'; export default defineComponent({ setup() { const menu = ref([ { name: '前言', id: 'main1', intersectionRatio: 0, // 用来判断侧边栏的激活状态,后续有用。 }, { name: '简介', id: 'main2', intersectionRatio: 0, }, { name: '主要功能', id: 'main3', intersectionRatio: 0, }, { name: '阅读提示', id: 'main4', intersectionRatio: 0, }, { name: '开始编写', id: 'main5', intersectionRatio: 0, }, { name: '结语', id: 'main6', intersectionRatio: 0, }, ]) return { menu }; }, }); 复制代码 .main { padding: 30px; padding-right: 240px; .content { height: 70vh; background: #eee; border-radius: 8px; margin-bottom: 15px; } .header { padding: 15px; font-size: 20px; font-weigth: bold; } } .sidebar { position: fixed; top: 50%; right: 50px; width: 168px; overflow: hidden; background-color: #fff; border-radius: 8px; padding: 0; transform: translateY(-50%); box-shadow: 0px 1px 10px 0px rgba(0,0,0,0.05), 0px 3px 5px 0px rgba(0,0,0,0.06), 0px 2px 4px -1px rgba(0,0,0,0.04); li { line-height: 40px; text-align: center; list-style: none; &:not(:last-child) { border-bottom: #f5f5f5 solid 1px; } &.active { color: #fff; background-color: #ff8000; } a { display: block; width: 100%; height: 100%; color: #333; text-decoration: none; } } } 复制代码

得到页面结构如下:

image

点击侧边栏滚动到页面指定目录

这一步骤最简单的方法是通过 a标签 的锚点特性实现,改变路由的hash直接定位到页面具体的id元素位置。

但这种方式的缺点是太简单,没法实现页面滚动的动画效果,且想要实现定位时和顶部保持的一定距离很难,所以还是推荐使用window的滚动方法实现。

给侧边栏绑定点击事件,每当侧边栏点击时,获取对应页面元素的 offsetTop,并通过 window.scrollTo 滚动对应位置即可。

{{item.name}} 复制代码 function clickSideBar(item) { const dom = document.getElementById(item.id) if (dom) { window.scrollTo(0, dom.offsetTop - 50) // -50是为了让顶部留出距离,在页面存在顶部固定导航栏时可以使用 } } 复制代码

这样很简单,但是缺少动画,没有灵性,我们可以改造一下:

只要滚动的间隔和每次滚动的距离是固定的,动画就是匀速的,间隔越短,动画越流畅。

function clickSideBar(item) { const dom = document.getElementById(item.id) const prevDom = document.getElementById(active.value.id) // 当前激活的元素 if (dom) { const isDown = dom?.offsetTop - (prevDom?.offsetTop || 0) >= 0 // 目标元素距离当前激活目标的距离 > 0,表示向下滚动 let prevScrollY = window.scrollY const timer = setInterval(() => { window.scrollBy(0, isDown ? 20 : -20) // 滚动的距离固定20(向下滚动为正,反之负)。 // 如果滚动后距离 = 上次滚动的距离,说明滚动失败了,停止滚动. // 加这个是因为,如果页面主体的最后一块内容没有撑满屏幕时,window.scrollY的值永远都不符合与元素的offsetTop的判断,会一直死循环。 if (prevScrollY === window.scrollY) { clearInterval(timer) } else { prevScrollY = window.scrollY // 记录当前滚动后的滚动条距离 } if (isDown) { // 向下滾動時,頁面滾動距離大於目標元素距離時,停止滾動,反之同理 if (prevScrollY >= dom.offsetTop - 20) { // -20用来和顶部拉开距离 clearInterval(timer) } } else { if (prevScrollY { menu.value.forEach((el) => { const observer = new IntersectionObserver(([{ intersectionRatio }]) => { el.intersectionRatio = intersectionRatio // 在回调中记录元素和屏幕相交的比例 }, { rootMargin: '-100px', // 缩小屏幕相交的尺寸,这在有fixed定位的header和footer时非常有用 threshold: [0.1, 0.5, 0.8, 1], // 多定义几个相交触发的时机,以便得出当前屏幕中占据最大面积的元素 }) observer.observe(document.getElementById(el.id)) observers.push(observer) }) }) // 页面销毁时,注销观察者,避免不必要的开销 onBeforeUnmount(() => { observers.forEach((observer) => { observer.disconnect() }) }) 复制代码

这段代码还可以进行优化,比如一个观察者对象可以观察多个元素,理论上只要构建一个观察者即可,而不需要new一整个数组那么多的观察者。



【本文地址】


今日新闻


推荐新闻


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