From c3fc76a32f429c0ea13c9de85188ae9b2634fab8 Mon Sep 17 00:00:00 2001 From: sanbuphy Date: Sat, 7 Feb 2026 09:34:09 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20outline=20=E4=BE=A7=E8=BE=B9=E6=A0=8F?= =?UTF-8?q?=E6=8C=87=E7=A4=BA=E6=A0=87=E8=87=AA=E5=8A=A8=E6=BB=9A=E5=8A=A8?= =?UTF-8?q?=E8=B7=9F=E9=9A=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/.vitepress/theme/Layout.vue | 85 ++++++++++++++++++++++++++++++++ docs/.vitepress/theme/style.css | 49 ++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/docs/.vitepress/theme/Layout.vue b/docs/.vitepress/theme/Layout.vue index 2dd6032..a9c6c0f 100644 --- a/docs/.vitepress/theme/Layout.vue +++ b/docs/.vitepress/theme/Layout.vue @@ -85,8 +85,93 @@ onMounted(() => { applyFontSize(saved) applyLineHeight(savedLineHeight) isHydrated.value = true + + // 初始化 outline 自动滚动功能 + initOutlineAutoScroll() }) +// ============================================ +// Outline 侧边栏自动滚动跟随功能 +// 当页面滚动时,自动滚动 outline 让当前激活项保持在可视区域 +// ============================================ +function initOutlineAutoScroll() { + // 使用 MutationObserver 监听 outline 的变化 + const observer = new MutationObserver((mutations) => { + for (const mutation of mutations) { + if (mutation.type === 'attributes' && mutation.attributeName === 'class') { + const target = mutation.target + if (target.classList.contains('active') && target.tagName === 'A') { + scrollOutlineToActiveItem(target) + } + } + } + }) + + // 开始监听 + const startObserving = () => { + const outlineContainer = document.querySelector('.VPDocAsideOutline') + if (outlineContainer) { + observer.observe(outlineContainer, { + attributes: true, + subtree: true, + attributeFilter: ['class'] + }) + } + } + + // 页面加载完成后开始监听 + 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) + } + + history.replaceState = function (...args) { + originalReplaceState.apply(this, args) + setTimeout(startObserving, 100) + } + + window.addEventListener('popstate', () => { + setTimeout(startObserving, 100) + }) +} + +// 滚动 outline 让当前激活项保持在可视区域中心 +function scrollOutlineToActiveItem(activeLink) { + const outlineContainer = document.querySelector('.VPDocAsideOutline') + if (!outlineContainer || !activeLink) return + + const containerRect = outlineContainer.getBoundingClientRect() + const linkRect = activeLink.getBoundingClientRect() + + // 计算链接相对于容器的位置 + const linkTop = linkRect.top - containerRect.top + outlineContainer.scrollTop + const linkHeight = linkRect.height + const containerHeight = containerRect.height + + // 判断链接是否在可视区域外 + const isAbove = linkRect.top < containerRect.top + 20 + const isBelow = linkRect.bottom > containerRect.bottom - 20 + + if (isAbove || isBelow) { + // 将激活项滚动到容器中间位置 + const targetScrollTop = linkTop - containerHeight / 2 + linkHeight / 2 + outlineContainer.scrollTo({ + top: targetScrollTop, + behavior: 'smooth' + }) + } +} + watch(fontSize, (next) => { if (!isHydrated.value) return const normalized = clampFontSize(next) diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css index 2c3b24d..47e67b3 100644 --- a/docs/.vitepress/theme/style.css +++ b/docs/.vitepress/theme/style.css @@ -345,3 +345,52 @@ padding-bottom: 18vh !important; box-sizing: border-box; } + +/* ============================================ + Outline 侧边栏指示标滚动修复 + 问题:当页面内容很长时,outline 中的指示标(marker) + 会移动到可见区域之外,用户看不到当前阅读位置 + ============================================ */ + +/* 让 outline 容器可以独立滚动 */ +.VPDocAsideOutline { + max-height: calc(100vh - var(--vp-nav-height, 64px) - 32px); + overflow-y: auto; + overflow-x: hidden; +} + +/* 隐藏滚动条但保持滚动功能 */ +.VPDocAsideOutline::-webkit-scrollbar { + width: 4px; +} + +.VPDocAsideOutline::-webkit-scrollbar-track { + background: transparent; +} + +.VPDocAsideOutline::-webkit-scrollbar-thumb { + background: var(--vp-c-divider); + border-radius: 2px; +} + +.VPDocAsideOutline::-webkit-scrollbar-thumb:hover { + background: var(--vp-c-text-3); +} + +/* Firefox 滚动条样式 */ +.VPDocAsideOutline { + scrollbar-width: thin; + scrollbar-color: var(--vp-c-divider) transparent; +} + +/* 确保 outline 内容区域正确显示 */ +.VPDocAsideOutline .content { + position: relative; + min-height: min-content; +} + +/* 确保 marker 指示标始终在可见区域内 */ +.VPDocAsideOutline.has-outline .outline-marker { + position: absolute; + will-change: top; +}