Code前端首页关于Code前端联系我们

一、scrollBehavior是干啥的?

terry 3周前 (09-06) 阅读数 43 #Vue

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手动跳转,savedPositionundefined,这时候得自己存滚动位置(比如用sessionStorage或Vuex)。

新页面强制滚动到顶部(管理系统常用)

做后台管理系统时,每次点击侧边栏进入新页面,都希望内容在顶部,这时候scrollBehavior的基础用法就够,但要注意嵌套路由或固定导航栏的偏移

比如页面顶部有个60px高的固定导航栏,新页面要滚动到导航栏下方(避免内容被挡住),可以这样写:

scrollBehavior(to, from, savedPosition) {
  if (savedPosition) {
    return savedPosition;
  } else {
    // 滚动到y = 60的位置(避开固定导航栏)
    return { x: 0, y: 60 };
  }
}

复杂场景:自定义滚动容器的滚动恢复

如果页面的滚动不是整个window,而是某个divoverflow - 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),如果你的滚动是某个divoverflow - scroll,得自己在组件内用onBeforeRouteLeaveonBeforeRouteEnter存、恢复scrollTop(参考上面的例子)。

savedPosition只有浏览器导航时才有

如果用户点击的是你自己写的“返回”按钮,用的是this.$router.push('/xxx'),而不是this.$router.go(-1),那savedPositionundefined,滚动位置不会自动恢复。

解决办法:

  • 尽量用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前端网发表,如需转载,请注明页面地址。

发表评论:

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

热门