feat: 为文章目录添加动态滑动高亮指示器
continuous-integration/drone/push Build is passing

This commit is contained in:
seahi committed 2026-09-28 11:35:14 +08:00
1 parent 8dfe2624ab
commit 3fe68cd57e
2 files changed
+48 -5

No files matched your search

+16 -4
View File
@@ -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 时不修改背景和左边框了,保持透明给指示器让路 */
}
}
+32 -1
View File
@@ -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<HTMLElement>();
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 @@
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_static_element_interactions -->
<div class="toc-mask" onclick={() => (sidebarShow = false)}></div>
<div class="TOC" aria-label="{$page.data.aliases?.post || '文章'}目录">
<div class="TOC" bind:this={tocContainer} aria-label="{$page.data.aliases?.post || '文章'}目录">
<div
class="toc-active-indicator"
style="transform: translateY({indicatorTop}px); height: {indicatorHeight}px; opacity: {indicatorOpacity}"
></div>
<div class="js-toc"></div>
</div>