Vue Router锚点向上滚动异常问题求助
问题描述
我参考了一些帖子实现锚点滚动,使用Vue Router的scrollBehavior配置了平滑滚动,代码如下:
import { route } from 'quasar/wrappers' import { createRouter, createMemoryHistory, createWebHistory, createWebHashHistory } from 'vue-router' import routes from './routes' export default route(function (/* { store, ssrContext } */) { const createHistory = process.env.SERVER ? createMemoryHistory : (process.env.VUE_ROUTER_MODE === 'history' ? createWebHistory : createWebHashHistory) const Router = createRouter({ scrollBehavior(to, from, savedPosition) { if (to && to.hash) { return { el: to.hash, top: 75, // 避开固定组件遮挡 behavior: 'smooth' }; } else if (savedPosition) { return savedPosition; } else { return { top: 75 }; } }, routes, history: createWebHistory(process.env.VUE_ROUTER_BASE) }) return Router })
当前代码在锚点上方时可以平滑滚动到锚点,但当处于锚点下方时,点击侧边栏的导航按钮(基于router-link),页面会先重置到顶部再向下滚动,体验很差。侧边栏菜单代码如下:
<template> <div class="navigation-links q-pa-lg"> <q-list padding> <q-item clickable v-ripple class="q-pa-md q-ma-md menu-item" :to="{name: 'Home', hash: '#Home'}" :active="(route.name === 'Home' && route.hash === '#Home') || (route.name === 'Home' && route.hash === '')" active-class="active-menu-item"> <q-item-section avatar> <q-icon name="fa-solid fa-house" style="font-size:1.25rem"/> </q-item-section> <q-item-section> HOME </q-item-section> </q-item> <q-item clickable v-ripple class="q-pa-md q-ma-md menu-item" :to="{name: 'Home', hash: '#Services'}" :active="route.name === 'Home' && route.hash === '#Services'" active-class="active-menu-item"> <q-item-section avatar> <q-icon name="fa-solid fa-shirt" style="font-size:1.25rem"/> </q-item-section> <q-item-section> SERVICES </q-item-section> </q-item> <q-item clickable v-ripple class="q-pa-md q-ma-md menu-item" :to="{name: 'Home', hash: '#OurTeam'}" :active="route.name === 'Home' && route.hash === '#OurTeam'" active-class="active-menu-item"> <q-item-section avatar> <q-icon name="fa-solid fa-users" style="font-size:1.25rem"/> </q-item-section> <q-item-section> OUR TEAM </q-item-section> </q-item> <q-item clickable v-ripple class="q-pa-md q-ma-md menu-item" @click="layoutStore.toggleContactUsDrawer"> <q-item-section avatar> <q-icon name="fa-solid fa-envelope" style="font-size:1.25rem"/> </q-item-section> <q-item-section> CONTACT US </q-item-section> </q-item> </q-list> </div> <div class="socialmedia-links q-pa-lg text-center column no-wrap flex-center self" style=""> <div class="row"> <q-btn flat size="sm" color="primary" round icon="fa-brands fa-google" aria-label="google" class="float-right q-ma-sm no-shadow" @click="openInNewTab(companyGoogleLink)"/> <q-btn flat size="sm" color="primary" round icon="fa-brands fa-facebook-f" aria-label="facebook" class="float-right q-ma-sm no-shadow" @click="openInNewTab(companyFacebookLink)"/> <q-btn flat size="sm" color="primary" round icon="fa-brands fa-instagram" aria-label="instagram" class="float-right q-ma-sm no-shadow" @click="openInNewTab(companyInstagramLink)"/> </div> <h2 class="text-h5 text-weight-bolder q-mb-xs">CONNECT WITH US</h2> <h6 class="text-subtitle1 text-accent text-weight-normal q-my-none">If you like what you have seen so far we can guarentee you will appreciate our print quality.</h6> </div> </template> <script setup> import { ref } from 'vue' import {storeToRefs} from 'pinia' import {useLayoutStore} from '../stores/layout-store' import {useCompanyInfoStore} from '../stores/company-info-store' import {useRoute, useRouter } from 'vue-router' //store const layoutStore = useLayoutStore() const {contactUsDrawer, navigationDrawer} = storeToRefs(layoutStore) const companyInfoStore = useCompanyInfoStore() const {companyFacebookLink, companyGoogleLink, companyInstagramLink} = storeToRefs(companyInfoStore) const searchText = ref('') const route = useRoute() const router = useRouter() //methods function openInNewTab(url) { window.open(url, '_blank').focus(); } </script> <style lang="scss" scoped> .block-wrapper{ max-width: 1024px; margin-left: auto; margin-right: auto; } .active-menu-item{ background-color: $background-accent; color: $primary !important; } .menu-item{ letter-spacing: .15rem; font-weight:500; border-radius: 7px; color:$dark } </style>
请问Vue Router无法实现直接向上滚动到锚点吗?
解决方案
这个问题不是Vue Router做不到,而是同路由下的hash跳转触发了默认的页面重置行为。当你在同一个Home路由下点击不同hash的链接时,Vue Router默认会先把页面滚动到顶部,再执行scrollBehavior里的逻辑,导致了先顶再滚的奇怪效果。
可以通过以下几种方式解决:
1. 修改scrollBehavior,处理同路由hash跳转
在scrollBehavior里判断是否是同路由跳转,如果是,直接手动获取元素并执行平滑滚动,跳过默认的顶部重置:
scrollBehavior(to, from, savedPosition) { // 处理同路由下的hash跳转 if (to.name === from.name && to.hash) { const el = document.querySelector(to.hash); if (el) { // 手动执行平滑滚动,加上偏移量 el.scrollIntoView({ behavior: 'smooth', block: 'start' }); // 调整偏移(适配固定头部的75px高度) setTimeout(() => { window.scrollBy({ top: -75, behavior: 'smooth' }); }, 10); return false; // 告诉Vue Router不要执行默认滚动行为 } } // 原有逻辑 if (to && to.hash) { return { el: to.hash, top: 75, behavior: 'smooth' }; } else if (savedPosition) { return savedPosition; } else { return { top: 75 }; } }
2. 给router-link添加replace属性
在侧边栏的q-item(本质是router-link)上添加replace属性,这样同路由hash跳转不会添加新的历史记录,同时能避免默认的顶部滚动:
<q-item clickable v-ripple class="q-pa-md q-ma-md menu-item" :to="{name: 'Home', hash: '#Home'}" replace :active="(route.name === 'Home' && route.hash === '#Home') || (route.name === 'Home' && route.hash === '')" active-class="active-menu-item"> <!-- 图标和文字内容 --> </q-item>
3. 全局守卫中手动处理滚动(兜底方案)
如果上述方法无效,可以在Router创建后添加全局前置守卫,直接接管同路由hash跳转的滚动逻辑:
// 在Router创建后添加 Router.beforeEach((to, from, next) => { if (to.name === from.name && to.hash) { next(); // 直接执行滚动,跳过Vue Router默认处理 const el = document.querySelector(to.hash); if (el) { const offsetTop = el.offsetTop - 75; window.scrollTo({ top: offsetTop, behavior: 'smooth' }); } } else { next(); } });
内容的提问来源于stack exchange,提问作者Qiuzman
相关产品推荐
相关产品推荐

