一、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前端网发表,如需转载,请注明页面地址。
code前端网



发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。