From 3fe68cd57e4fb2f517716344011d8f7b15a7f26b Mon Sep 17 00:00:00 2001 From: seaHi Date: Mon, 28 Sep 2026 11:35:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=B8=BA=E6=96=87=E7=AB=A0=E7=9B=AE?= =?UTF-8?q?=E5=BD=95=E6=B7=BB=E5=8A=A0=E5=8A=A8=E6=80=81=E6=BB=91=E5=8A=A8?= =?UTF-8?q?=E9=AB=98=E4=BA=AE=E6=8C=87=E7=A4=BA=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.css | 20 +++++++++++++---- src/routes/posts/[slug]/+page.svelte | 33 +++++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/app.css b/src/app.css index 2cbfd31..82dbfbf 100644 --- a/src/app.css +++ b/src/app.css @@ -1405,6 +1405,7 @@ footer section p:first-child { margin-left: -1rem; padding-left: 1rem; position: relative; + z-index: 1; /* 保证在指示器上方 */ color: #b0b0b0; /* VOID style dark text */ transition: color 0.2s, @@ -1421,16 +1422,27 @@ footer section p:first-child { .TOC li a.is-active-link { font-weight: 700; +} + +/* 动态滑动指示器 */ +.toc-active-indicator { + position: absolute; + left: 0; + width: 100%; background-color: rgba(0, 0, 0, 0.3); /* VOID style dark highlight */ border-left: 2px solid #419b38; - padding-left: calc(1rem - 2px); /* adjust padding for the border */ + pointer-events: none; + z-index: 0; + transition: + transform 0.24s cubic-bezier(0.16, 1, 0.3, 1), + height 0.24s cubic-bezier(0.16, 1, 0.3, 1), + opacity 0.2s; + opacity: 0; } @media screen and (min-width: 1199.5px) { .TOC li a:hover { - background-color: rgba(0, 0, 0, 0.3); - border-left: 2px solid #419b38; - padding-left: calc(1rem - 2px); + /* hover 时不修改背景和左边框了,保持透明给指示器让路 */ } } diff --git a/src/routes/posts/[slug]/+page.svelte b/src/routes/posts/[slug]/+page.svelte index 720b34e..7d4e0fd 100644 --- a/src/routes/posts/[slug]/+page.svelte +++ b/src/routes/posts/[slug]/+page.svelte @@ -8,6 +8,10 @@ let sidebarShow = $state(true); let hasToc = $state(false); + let indicatorTop = $state(0); + let indicatorHeight = $state(0); + let indicatorOpacity = $state(0); + let tocContainer = $state(); let { data }: { data: PageData } = $props(); @@ -84,6 +88,29 @@ }; }); + $effect(() => { + if (!hasToc || !tocContainer) return; + + const updateIndicator = () => { + const activeLink = tocContainer!.querySelector('a.is-active-link') as HTMLElement; + if (activeLink) { + indicatorTop = activeLink.offsetTop; + indicatorHeight = activeLink.offsetHeight; + indicatorOpacity = 1; + } else { + indicatorOpacity = 0; + } + }; + + const observer = new MutationObserver(updateIndicator); + observer.observe(tocContainer, { attributes: true, subtree: true, attributeFilter: ['class'] }); + + setTimeout(updateIndicator, 100); + + return () => { + observer.disconnect(); + }; + }); $effect(() => { if (hasToc && sidebarShow) { document.body.classList.add('sidebar-show'); @@ -176,7 +203,11 @@
(sidebarShow = false)}>
-
+
+