问题:点「产品功能 / 核心能力 / 界面预览 / 官方文档」跳转后图标不变蓝。 根因:VitePress 的 isActive() 只比较 path,不看 hash。nav 里 「首页 /」「产品功能 /#features」「核心能力 /#core」的路径部分都是 `/`, 默认逻辑要么把首页和当前锚点同时点亮,要么只亮首页。 改动 - 新增 .vitepress/theme/nav-anchor.ts 接管首页内锚点项的激活判定: 用 data-anchor-active 属性标记,规则为 在首页且有 hash → 只有 hash 匹配项亮;在首页无 hash → 只有「首页」亮; 不在首页 → 清标记交回 VitePress 默认逻辑 - 样式侧用 html.portal-nav-home 限定压制默认 active 的作用域, 只影响首页,不影响 /projects/ 与 /about 的正常高亮 - 导航「项目」加 activeMatch: '^/projects/', 使 /projects/iotlcm/ 等详情页也保持高亮 - MutationObserver 监听导航 DOM 变化(SPA 切换、主题切换会重建) 踩的坑 - Layout 是函数式组件,onMounted 在其中无效(Vue 报"no active component instance"),改用 nextTick 调度 - 初版用 hashOf(a) 判空来识别「首页」,但 /projects/ 与 /about 也没有 hash, 被误判成首页项一起点亮;改为判断 href 解析后是否为 '/' - 抑制规则最初没加作用域,导致非首页页面的正常 active 也被压成黑字 验证(8 个场景逐个实测) 首页顶部→首页 / 四个锚点→各自高亮 / 项目页→项目 / 关于页→关于 / iotLCM 详情→项目;构建 13 页 274 引用 0 失效
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 导航栏锚点高亮
|
||||
*
|
||||
* 问题:VitePress 的 isActive() 只比较 path,不看 hash。
|
||||
* nav 里「首页 /」「产品功能 /#features」「核心能力 /#core」路径部分都是 `/`,
|
||||
* 于是停在 #features 时,首页和产品功能会同时被标蓝。
|
||||
*
|
||||
* 解决:接管首页内锚点项的激活判定,用 data-anchor-active 标记,
|
||||
* 样式侧压掉 VitePress 默认的 .active。
|
||||
* - 在首页且有 hash:只有 hash 匹配的那项亮,首页项不亮
|
||||
* - 在首页且无 hash:只有「首页」亮
|
||||
* - 不在首页:不干预,清掉标记交回默认逻辑
|
||||
*/
|
||||
|
||||
const SEL = '.VPNavBarMenuLink'
|
||||
|
||||
function hashOf(a: HTMLAnchorElement): string {
|
||||
const href = a.getAttribute('href') ?? ''
|
||||
const i = href.indexOf('#')
|
||||
return i === -1 ? '' : href.slice(i)
|
||||
}
|
||||
|
||||
function apply(): void {
|
||||
const links = Array.from(document.querySelectorAll<HTMLAnchorElement>(SEL))
|
||||
if (!links.length) return
|
||||
|
||||
const path = location.pathname
|
||||
const onHome = path === '/' || path === '' || /^\/index\.html?$/.test(path)
|
||||
|
||||
// 标记首页状态:样式侧据此压掉 VitePress 对「项目 / 关于」的前缀匹配高亮
|
||||
document.documentElement.classList.toggle('portal-nav-home', onHome)
|
||||
|
||||
if (!onHome) {
|
||||
links.forEach((a) => a.removeAttribute('data-anchor-active'))
|
||||
return
|
||||
}
|
||||
|
||||
// 兼容 VitePress 的 cleanUrls:路径可能是 / 或 /index.html
|
||||
const h = location.hash
|
||||
|
||||
links.forEach((a) => {
|
||||
const href = a.getAttribute('href') ?? ''
|
||||
const itemHash = hashOf(a)
|
||||
|
||||
if (itemHash) {
|
||||
// 锚点项:只有 hash 完全匹配时才亮
|
||||
a.toggleAttribute('data-anchor-active', h === itemHash)
|
||||
return
|
||||
}
|
||||
|
||||
// 没有 hash 的项:只有「指向首页本身」的才算首页项
|
||||
// (/projects/ 与 /about 也没有 hash,不能一并当作首页)
|
||||
const bare = href.split('#')[0].replace(/\/index\.html?$/, '/')
|
||||
const isHomeItem = bare === '/' || bare === ''
|
||||
|
||||
if (isHomeItem) {
|
||||
a.toggleAttribute('data-anchor-active', h === '')
|
||||
} else {
|
||||
a.removeAttribute('data-anchor-active')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
let raf = 0
|
||||
function schedule(): void {
|
||||
if (raf) return
|
||||
raf = requestAnimationFrame(() => {
|
||||
raf = 0
|
||||
apply()
|
||||
})
|
||||
}
|
||||
|
||||
/** 首屏导航是异步渲染的,用 MutationObserver 等它出现再判定 */
|
||||
export function setupNavAnchorHighlight(): void {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const run = () => {
|
||||
apply()
|
||||
// 导航可能是分块渲染的,多补两次
|
||||
requestAnimationFrame(apply)
|
||||
setTimeout(apply, 80)
|
||||
setTimeout(apply, 300)
|
||||
}
|
||||
|
||||
run()
|
||||
|
||||
window.addEventListener('hashchange', schedule)
|
||||
window.addEventListener('popstate', schedule)
|
||||
window.addEventListener('load', schedule)
|
||||
|
||||
// 导航栏内容变化(SPA 切换、暗色切换重建)时重新判定
|
||||
const nav = document.querySelector('.VPNavBar') ?? document.body
|
||||
const mo = new MutationObserver(() => schedule())
|
||||
mo.observe(nav, { childList: true, subtree: true })
|
||||
}
|
||||
|
||||
export { apply as applyNavAnchorHighlight }
|
||||
Reference in New Issue
Block a user