From 004496f1d5af1ce3fc3e3508d213570d5f5014eb Mon Sep 17 00:00:00 2001 From: sanbuphy Date: Sun, 15 Feb 2026 01:56:48 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E4=BE=A7=E8=BE=B9?= =?UTF-8?q?=E6=A0=8F=E7=82=B9=E5=87=BB=E5=90=8E=E4=B8=8D=E8=87=AA=E5=8A=A8?= =?UTF-8?q?=E6=BB=9A=E5=8A=A8=E5=AE=9A=E4=BD=8D=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 onMounted 中调用 initOutlineAutoScroll 初始化函数 - 添加 VPSidebar 选择器支持,修正 VitePress 侧边栏类名 - 修正激活状态类名为 is-active(VitePress 使用) - 新增 scrollSidebarToActiveItem 函数实现侧边栏自动滚动 - 点击侧边栏链接后自动将激活项平滑滚动到可视区域中心 --- docs/.vitepress/theme/Layout.vue | 94 +++++++++++++++++++++++++++++--- 1 file changed, 86 insertions(+), 8 deletions(-) diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/Layout.vue index a9c6c0f..006483f 100644 --- a/docs/.vitepress/theme/Layout.vue +++ b/docs/.vitepress/theme/Layout.vue @@ -86,7 +86,6 @@ onMounted(() => { applyLineHeight(savedLineHeight) isHydrated.value = true - // 初始化 outline 自动滚动功能 initOutlineAutoScroll() }) @@ -95,7 +94,34 @@ onMounted(() => { // 当页面滚动时,自动滚动 outline 让当前激活项保持在可视区域 // ============================================ function initOutlineAutoScroll() { - // 使用 MutationObserver 监听 outline 的变化 + const outlineSelectors = [ + '.VPDocAsideOutline', + '.VPTableOfContents', + '.vitepress-doc-sidebar', + '.sidebar-outline', + 'aside' + ] + + const sidebarSelectors = [ + '.VPSidebar', + '.VPDocSidebar', + '.vitepress-doc-sidebar' + ] + + let outlineContainer = null + for (const selector of outlineSelectors) { + outlineContainer = document.querySelector(selector) + if (outlineContainer) break + } + + if (!outlineContainer) return + + let sidebarContainer = null + for (const selector of sidebarSelectors) { + sidebarContainer = document.querySelector(selector) + if (sidebarContainer) break + } + const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'attributes' && mutation.attributeName === 'class') { @@ -107,7 +133,17 @@ function initOutlineAutoScroll() { } }) - // 开始监听 + const sidebarObserver = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + const target = mutation.target + if (target.classList.contains('is-active')) { + scrollSidebarToActiveItem(target) + } + } + } + }) + const startObserving = () => { const outlineContainer = document.querySelector('.VPDocAsideOutline') if (outlineContainer) { @@ -116,32 +152,48 @@ function initOutlineAutoScroll() { subtree: true, attributeFilter: ['class'] }) + + const existingActive = outlineContainer.querySelector('.active') + if (existingActive) { + scrollOutlineToActiveItem(existingActive) + } + } + + if (sidebarContainer) { + sidebarObserver.observe(sidebarContainer, { + attributes: true, + subtree: true, + attributeFilter: ['class'] + }) + + const existingSidebarActive = sidebarContainer.querySelector('.is-active') + if (existingSidebarActive) { + scrollSidebarToActiveItem(existingSidebarActive) + } } } - // 页面加载完成后开始监听 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', startObserving) } else { startObserving() } - // 同时监听路由变化(VitePress 是 SPA) const originalPushState = history.pushState const originalReplaceState = history.replaceState history.pushState = function (...args) { originalPushState.apply(this, args) - setTimeout(startObserving, 100) + setTimeout(startObserving, 300) } history.replaceState = function (...args) { originalReplaceState.apply(this, args) - setTimeout(startObserving, 100) + setTimeout(startObserving, 300) } window.addEventListener('popstate', () => { - setTimeout(startObserving, 100) + setTimeout(startObserving, 300) }) } @@ -172,6 +224,32 @@ function scrollOutlineToActiveItem(activeLink) { } } +// 滚动侧边栏让当前激活项保持在可视区域中心 +function scrollSidebarToActiveItem(activeItem) { + const sidebarContainer = document.querySelector('.VPSidebar') || document.querySelector('.VPDocSidebar') + if (!sidebarContainer || !activeItem) return + + const targetElement = activeItem.querySelector('.item') || activeItem.querySelector('a') || activeItem + + const containerRect = sidebarContainer.getBoundingClientRect() + const targetRect = targetElement.getBoundingClientRect() + + const targetTop = targetRect.top - containerRect.top + sidebarContainer.scrollTop + const targetHeight = targetRect.height + const targetCenterY = targetTop + targetHeight / 2 + + const isInside = targetRect.top >= containerRect.top - 20 && + targetRect.bottom <= containerRect.bottom + 20 + + if (!isInside) { + const targetScrollTop = targetCenterY - containerRect.height / 2 + sidebarContainer.scrollTo({ + top: targetScrollTop, + behavior: 'smooth' + }) + } +} + watch(fontSize, (next) => { if (!isHydrated.value) return const normalized = clampFontSize(next)