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

vue-router页面内手动滚动时router-link未自动添加active-class的解决办法

问题根因

Vue Router 仅会在路由显式变更时触发链接激活类的匹配逻辑,手动滚动页面不会自动更新当前路由的hash值,因此对应视口区块的router-link无法自动添加exact-active-class。

修复方案

可根据项目场景二选一:

方案1:监听滚动位置同步路由hash(保持原有router-link激活逻辑不变)

第一步:配置路由scrollBehavior,保证点击跳转逻辑正常

在路由初始化配置中添加滚动行为规则:

const router = createRouter({
  history: createWebHistory(),
  routes: [], // 业务路由配置
  scrollBehavior(to) {
    if (to.hash) {
      return {
        el: to.hash,
        behavior: 'smooth'
      }
    }
    return { top: 0 }
  }
})

第二步:注册区块可见性监听,自动同步当前hash

使用IntersectionObserver监听锚点区块的可见状态,当区块进入视口指定区域时,替换当前路由的hash值,触发router-link的自动激活类匹配。推荐把逻辑封装为可复用的组合式函数:

import { onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'

// 入参为所有锚点区块的id数组
export function useAnchorActive(sectionIds) {
  const router = useRouter()
  let observer = null

  onMounted(() => {
    observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          // 替换hash,不新增浏览历史、不触发强制跳转
          router.replace({
            hash: `#${entry.target.id}`,
            force: true
          })
        }
      })
    }, {
      // 视口上下各裁剪50%,仅当区块进入视口中间位置时判定为激活
      rootMargin: '-50% 0px -50% 0px',
      threshold: 0
    })

    sectionIds.forEach(id => {
      const section = document.getElementById(id)
      if (section) observer.observe(section)
    })
  })

  onUnmounted(() => {
    observer?.disconnect()
  })
}

在导航栏组件中直接引入调用即可:

useAnchorActive(['features', 'pricing', 'about', 'contact'])

方案2:自行维护激活状态,手动绑定类名(更灵活,无路由依赖)

如果不想改动路由逻辑,可以直接监听滚动事件,自行计算当前处于视口的区块,给对应链接手动绑定激活类,示例代码如下:

<template>
  <nav class="landing-nav">
    <router-link
      v-for="nav in navList"
      :key="nav.target"
      :to="nav.target"
      target="_self"
      class="nav-item"
      :class="{ 'exact-active-class': activeTarget === nav.target }"
    >
      {{ nav.label }}
    </router-link>
  </nav>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'

const navList = [
  { label: 'features', target: '#features' },
  { label: 'pricing', target: '#pricing' },
  { label: 'about', target: '#about' }
]
const activeTarget = ref(navList[0].target)
let scrollListener = null

onMounted(() => {
  scrollListener = () => {
    // 偏移量适配固定导航栏高度,比如导航栏高80px就设为80
    const offsetTop = 80
    for (const nav of navList) {
      const section = document.querySelector(nav.target)
      if (!section) continue
      const rect = section.getBoundingClientRect()
      if (rect.top <= offsetTop && rect.bottom >= offsetTop) {
        activeTarget.value = nav.target
        break
      }
    }
  }
  // 滚动时监听,passive标记优化滚动性能
  window.addEventListener('scroll', scrollListener, { passive: true })
  // 页面初始化时执行一次,匹配首屏状态
  scrollListener()
})

onUnmounted(() => {
  window.removeEventListener('scroll', scrollListener)
})
</script>

注意:如果页面存在固定在顶部的导航栏,请根据导航栏实际高度调整rootMargin或者offsetTop的数值,避免激活状态和实际视口内容错位。


内容的提问来源于stack exchange,提问作者morteza mortezaie

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:33:07