一、scrollBehavior是干啥的?
p标签开头:不少用Vue开发单页应用的同学,肯定碰到过页面切换后滚动位置“不听话”的情况——比如从商品列表页点进详情页,看完返回列表时,页面居然没回到刚才浏览的位置,又得重新往下翻;或者打开新页面时,内容不在顶部,得手动滑上去,这时候,vue - router里的scrollBehavior配置项就能帮咱解决这些头疼的滚动问题!今天咱就好好唠唠,scrollBehavior是干啥的、咋用,还有它能搞定哪些实际开发里的场景~
先理解单页应用(SPA)的特点:路由切换时,页面不会像多页应用那样整页刷新,只是替换<router - view>
里的组件,这种情况下,浏览器默认不会管滚动位置,所以切换路由后,滚动位置可能停在上个页面的位置,体验很糟。
而scrollBehavior就是vue - router专门用来控制路由切换时页面滚动行为的配置项,不管是“返回页面时恢复之前的滚动位置”,还是“打开新页面时自动滚到顶部”,甚至“跳转到页面内指定锚点”,都能靠它实现~
基本用法咋写?
想配置scrollBehavior,得在创建router实例时,加个scrollBehavior函数,这个函数接收三个参数:to
(要进入的目标路由)、from
(当前离开的路由)、savedPosition
(浏览器历史记录里的滚动位置,只有后退/前进时才有值),函数返回一个滚动位置对象,告诉浏览器该怎么滚。
先看最简单的“切换新路由时滚到顶部,后退时恢复位置”场景:
const router = createRouter({ history: createWebHistory(), routes: [...你的路由配置...], scrollBehavior(to, from, savedPosition) { // 如果是浏览器后退/前进(比如点左上角返回按钮),恢复之前的滚动位置 if (savedPosition) { return savedPosition; } else { // 新路由默认滚到顶部(x横轴,y纵轴) return { x: 0, y: 0 }; } } });
解释下参数:savedPosition
只有在用户点浏览器的“后退”“前进”按钮时才会有值(是个包含x、y的对象),所以上面代码逻辑是:有历史位置就恢复,没有就滚到顶部。
进阶玩法:精准控制滚动位置
除了基础的“滚顶部”和“恢复历史位置”,scrollBehavior还能玩出更灵活的花样,比如滚动到页面内指定元素(锚点定位)、设置平滑滚动效果。
锚点定位:滚动到指定DOM元素
假设我们有个“文档页”,路由是/docs#section1
,想让页面加载后自动滚动到id为section1
处,这时候可以用el
选项,配合to.hash
判断:
scrollBehavior(to, from, savedPosition) { // 检查目标路由有没有哈希值(section1) if (to.hash) { return { el: to.hash, // el是选择器,对应DOM元素(section1) behavior: 'smooth' // 可选:开启平滑滚动,浏览器支持的话会有过渡效果 }; } // 没有锚点时,默认滚到顶部 return { x: 0, y: 0 }; }
这里要注意:el
对应的DOM元素必须已经渲染完成,如果页面内容是异步加载的(比如接口请求后渲染),直接用上面的代码可能找不到元素,导致滚动失效,这时候得换个思路——等元素渲染后再滚动,比如用router.afterEach
钩子或者组件的onMounted
生命周期。
处理异步加载的锚点
举个例子,文档页的内容是接口请求后渲染的,这时候在scrollBehavior
里直接用el: to.hash
会失败,因为元素还没生成,可以这样处理:
// 路由实例创建后,加afterEach钩子 router.afterEach((to, from) => { if (to.hash) { // 等DOM更新后,再找元素 nextTick(() => { const el = document.querySelector(to.hash); if (el) { el.scrollIntoView({ behavior: 'smooth' // 平滑滚动 }); } }); } });
nextTick
能确保DOM已经更新,这时候再找锚点元素,滚动就稳了~
实际项目里的常见场景
光讲语法不够,得结合真实开发场景,看看scrollBehavior咋解决问题~
列表页返回,保留滚动位置
比如做电商APP的商品列表页:用户下滑浏览商品,点进某件商品的详情页,返回列表时,希望列表还停在刚才浏览的位置,这时候得结合keep - alive
缓存组件 + scrollBehavior
。
为啥要keep - alive
?因为组件被缓存后,内部的DOM结构和滚动状态(比如滚动容器的scrollTop
)才会保留,然后scrollBehavior
负责在路由切换时恢复window的滚动位置(如果列表是页面级滚动)。
代码思路:
// router/index.js scrollBehavior(to, from, savedPosition) { // 从详情页返回列表页,且是浏览器后退操作 if (from.name === 'GoodsDetail' && to.name === 'GoodsList' && savedPosition) { return savedPosition; } // 其他情况滚到顶部 return { x: 0, y: 0 }; } // GoodsList.vue(列表页组件) <template> <div class="goods - list"> <ul> <li v - for="item in list" :key="item.id" @click="$router.push(`/goods/${item.id}`)"> {{ item.name }} </li> </ul> </div> </template> <script setup> import { onActivated, onDeactivated } from 'vue'; // 假设list是接口请求的数据 // 组件被激活时(从缓存中取出) onActivated(() => { // 如果是页面级滚动,scrollBehavior已经处理;如果是自定义滚动容器,这里恢复scrollTop }); // 组件被停用时(缓存起来) onDeactivated(() => { // 保存自定义滚动容器的scrollTop(如果有的话) }); </script>
这里有个关键点:只有浏览器默认的后退/前进操作,才会触发savedPosition
,如果是用this.$router.push
手动跳转,savedPosition
是undefined
,这时候得自己存滚动位置(比如用sessionStorage
或Vuex)。
新页面强制滚动到顶部(管理系统常用)
做后台管理系统时,每次点击侧边栏进入新页面,都希望内容在顶部,这时候scrollBehavior
的基础用法就够,但要注意嵌套路由或固定导航栏的偏移。
比如页面顶部有个60px高的固定导航栏,新页面要滚动到导航栏下方(避免内容被挡住),可以这样写:
scrollBehavior(to, from, savedPosition) { if (savedPosition) { return savedPosition; } else { // 滚动到y = 60的位置(避开固定导航栏) return { x: 0, y: 60 }; } }
复杂场景:自定义滚动容器的滚动恢复
如果页面的滚动不是整个window,而是某个div
的overflow - scroll
(比如移动端的弹窗滚动、PC端的侧边栏滚动),这时候scrollBehavior
控制不了(它只管window的滚动),这时候得自己在组件内处理滚动位置。
举个移动端列表的例子,滚动容器是div.scroll - wrapper
:
<template> <div class="scroll - wrapper" ref="scrollContainer"> <ul>...</ul> </div> </template> <script setup> import { ref, onBeforeRouteLeave, onBeforeRouteEnter } from 'vue'; const scrollContainer = ref(null); // 离开组件时,保存滚动位置 onBeforeRouteLeave((to, from, next) => { const scrollTop = scrollContainer.value.scrollTop; sessionStorage.setItem('listScrollTop', scrollTop); next(); }); // 进入组件时,恢复滚动位置 onBeforeRouteEnter((to, from, next) => { next(vm => { // vm是组件实例 const scrollTop = sessionStorage.getItem('listScrollTop'); if (scrollTop) { vm.scrollContainer.value.scrollTop = Number(scrollTop); } }); }); </script>
这种场景下,scrollBehavior
管不了容器内的滚动,得靠组件内的路由钩子自己存、自己恢复~
容易踩的坑和解决办法
用scrollBehavior时,有些细节没注意就会掉坑里,这里总结几个常见问题和解法~
滚动位置不生效,因为是自定义滚动容器
前面说过,scrollBehavior
默认控制的是window的滚动(document.scrollingElement
),如果你的滚动是某个div
的overflow - scroll
,得自己在组件内用onBeforeRouteLeave
和onBeforeRouteEnter
存、恢复scrollTop
(参考上面的例子)。
savedPosition只有浏览器导航时才有
如果用户点击的是你自己写的“返回”按钮,用的是this.$router.push('/xxx')
,而不是this.$router.go(-1)
,那savedPosition
是undefined
,滚动位置不会自动恢复。
解决办法:
- 尽量用
router.go
模拟浏览器后退(比如this.$router.go(-1)
); - 自己维护滚动位置(存到
sessionStorage
或Vuex),在scrollBehavior
里读取。
导致锚点滚动失败
如果路由切换后,页面内容是异步加载的(比如接口请求后渲染),直接用el: to.hash
会找不到元素,这时候得等内容渲染完再滚动,
router.afterEach((to, from) => { if (to.hash) { setTimeout(() => { // 或者用nextTick,确保DOM更新 const el = document.querySelector(to.hash); if (el) { el.scrollIntoView({ behavior: 'smooth' }); } }, 0); } });
和keep - alive结合的注意点
keep - alive
能缓存组件实例,让组件的状态(比如数据、滚动位置)保留,和scrollBehavior
结合时,要注意两者的逻辑配合:
- 如果列表页用
keep - alive
缓存,返回时scrollBehavior
恢复的是window的滚动位置,而组件内的滚动容器(如果有的话)得自己处理; - 要确保
scrollBehavior
的触发时机(路由切换后)和keep - alive
的激活/停用钩子(onActivated
/onDeactivated
)不冲突。
举个完整例子:
<!-- App.vue --> <template> <keep - alive> <router - view></router - view> </keep - alive> </template> <!-- router/index.js --> scrollBehavior(to, from, savedPosition) { if (from.name === 'GoodsDetail' && to.name === 'GoodsList' && savedPosition) { return savedPosition; // 恢复window滚动位置 } return { x: 0, y: 0 }; } <!-- GoodsList.vue --> <template> <div class="scroll - wrapper" ref="scrollContainer"> <ul>...</ul> </div> </template> <script setup> import { ref, onActivated } from 'vue'; const scrollContainer = ref(null); onActivated(() => { // 恢复自定义滚动容器的scrollTop(假设之前存在sessionStorage) const scrollTop = sessionStorage.getItem('listScrollTop'); if (scrollTop) { scrollContainer.value.scrollTop = Number(scrollTop); } }); </script>
这样,window的滚动位置靠scrollBehavior
恢复,自定义容器的滚动位置靠onActivated
恢复,体验就顺滑了~
scrollBehavior的价值在哪?
单页应用的路由切换,本质是组件更新,默认没有“多页应用那种整页刷新后滚动到顶部”的行为。scrollBehavior
就是vue - router给咱的“补丁”——让SPA的滚动体验更接近传统多页应用,解决用户最直观的“滚动位置混乱”问题。
不管是简单的“新页面滚顶部”“返回恢复位置”,还是复杂的“锚点定位”“平滑滚动”,甚至结合keep - alive
和自定义滚动容器,scrollBehavior
都提供了灵活的配置方式,但实际开发中,得结合项目的滚动容器类型(window还是自定义)、路由导航方式(浏览器默认还是编程式)、内容加载时机(同步还是异步)这些细节,才能让滚动行为精准可控~
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。