You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 08:23:55