From c9ead12e35624872e3d8b134bdb455c2d52d3c65 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Wed, 21 May 2025 22:55:26 +0800 Subject: [PATCH] feat(theme): sidebar auto scroll into active link (#604) --- theme/src/client/components/VPSidebar.vue | 32 ++++++++++++++++------- theme/src/client/utils/resolveNavLink.ts | 7 ++++- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/theme/src/client/components/VPSidebar.vue b/theme/src/client/components/VPSidebar.vue index 0954e11e..c77c480f 100644 --- a/theme/src/client/components/VPSidebar.vue +++ b/theme/src/client/components/VPSidebar.vue @@ -2,7 +2,7 @@ import VPSidebarGroup from '@theme/VPSidebarGroup.vue' import VPTransitionFadeSlideY from '@theme/VPTransitionFadeSlideY.vue' import { useScrollLock } from '@vueuse/core' -import { onMounted, ref, watch } from 'vue' +import { nextTick, onMounted, ref, watch } from 'vue' import { useRoutePath } from 'vuepress/client' import { useData, useSidebar } from '../composables/index.js' import { inBrowser } from '../utils/index.js' @@ -31,19 +31,31 @@ watch( { immediate: true, flush: 'post' }, ) +/** + * Scroll to active item + */ onMounted(() => { - const activeItem = document.querySelector( - `.vp-sidebar .vp-link[href*="${routePath.value}"]`, - ) - if (!activeItem || !navEl.value) - return + watch(sidebarKey, async () => { + await nextTick() + const activeItem = document.querySelector( + `.vp-sidebar .vp-link[href*="${routePath.value}"]`, + ) + if (!navEl.value) + return - const { top: navTop, height: navHeight } = navEl.value.getBoundingClientRect() - const { top: activeTop, height: activeHeight } + if (!activeItem) { + // 等待动画进入透明状态后再重置滚动位置,避免内容闪烁 + setTimeout(() => navEl.value?.scrollTo(0, 0), 200) + return + } + + const { top: navTop, height: navHeight } = navEl.value.getBoundingClientRect() + const { top: activeTop, height: activeHeight } = activeItem.getBoundingClientRect() - if (activeTop < navTop || activeTop + activeHeight > navTop + navHeight) - activeItem.scrollIntoView({ block: 'center' }) + if (activeTop < navTop || activeTop + activeHeight > navTop + navHeight) + activeItem.scrollIntoView({ block: 'center' }) + }, { immediate: true, flush: 'post' }) }) diff --git a/theme/src/client/utils/resolveNavLink.ts b/theme/src/client/utils/resolveNavLink.ts index fea01739..e57278fd 100644 --- a/theme/src/client/utils/resolveNavLink.ts +++ b/theme/src/client/utils/resolveNavLink.ts @@ -24,13 +24,18 @@ export function resolveNavLink(link: string): ResolvedNavItemWithLink { return notFound ? { text: path, link: path } : { - text: meta.title || path, + text: meta.title || normalizeTitleWithPath(path), link: path, icon: meta.icon, badge: meta.badge, } } +function normalizeTitleWithPath(path: string): string { + path = path.replace(/index\.html?$/i, '').replace(/\.html?$/i, '').replace(/\/$/, '') + return decodeURIComponent(path.slice(path.lastIndexOf('/') + 1)) +} + export function normalizeLink(base = '', link = ''): string { return isLinkAbsolute(link) || isLinkWithProtocol(link) ? link