This commit is contained in:
1 parent
93eac7e146
commit
8dfe2624ab
1 file changed
+94
-3
@@ -1,12 +1,80 @@
|
||||
<script lang="ts">
|
||||
import { tick } from 'svelte';
|
||||
import type { EditorOutline } from '$lib/editor/outline.svelte';
|
||||
|
||||
let { outline, onselect }: { outline: EditorOutline; onselect?: () => void } = $props();
|
||||
let baseLevel = $derived(Math.min(...outline.headings.map((heading) => heading.level), 6));
|
||||
|
||||
let listEl: HTMLUListElement | undefined = $state();
|
||||
let enableAnimation = $state(false);
|
||||
let indicatorStyle = $state({ top: 0, height: 0, visible: false });
|
||||
|
||||
function updateIndicator(withAnimation = true) {
|
||||
if (!listEl || outline.active === undefined || outline.active === null || outline.active < 0) {
|
||||
indicatorStyle = { top: 0, height: 0, visible: false };
|
||||
return;
|
||||
}
|
||||
|
||||
const buttons = listEl.querySelectorAll<HTMLButtonElement>('button');
|
||||
const activeBtn = buttons[outline.active];
|
||||
|
||||
if (!activeBtn) {
|
||||
indicatorStyle = { top: 0, height: 0, visible: false };
|
||||
return;
|
||||
}
|
||||
|
||||
if (!withAnimation) {
|
||||
enableAnimation = false;
|
||||
}
|
||||
|
||||
indicatorStyle = {
|
||||
top: activeBtn.offsetTop,
|
||||
height: activeBtn.offsetHeight,
|
||||
visible: true
|
||||
};
|
||||
|
||||
if (!withAnimation) {
|
||||
// 在下一帧恢复动画能力,避免后续切换时丢失平滑过渡
|
||||
requestAnimationFrame(() => {
|
||||
enableAnimation = true;
|
||||
});
|
||||
} else {
|
||||
enableAnimation = true;
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
// 监听大纲激活索引与标题列表变化
|
||||
void outline.active;
|
||||
void outline.headings;
|
||||
|
||||
tick().then(() => {
|
||||
// 初次挂载静默定位,后续章节变动启用平滑动效
|
||||
updateIndicator(enableAnimation);
|
||||
});
|
||||
});
|
||||
|
||||
$effect(() => {
|
||||
if (!listEl) return;
|
||||
// 监听容器尺寸变化(如侧边栏宽度改变或窗口缩放),静默重校准指示块
|
||||
const ro = new ResizeObserver(() => {
|
||||
updateIndicator(false);
|
||||
});
|
||||
ro.observe(listEl);
|
||||
return () => ro.disconnect();
|
||||
});
|
||||
</script>
|
||||
|
||||
<nav aria-label="正文大纲" class="article-outline">
|
||||
{#if outline.headings.length}
|
||||
<ul>
|
||||
<ul bind:this={listEl}>
|
||||
<div
|
||||
class="active-indicator"
|
||||
class:visible={indicatorStyle.visible}
|
||||
class:animate={enableAnimation}
|
||||
style:transform="translateY({indicatorStyle.top}px)"
|
||||
style:height="{indicatorStyle.height}px"
|
||||
></div>
|
||||
{#each outline.headings as heading, index (heading.from)}
|
||||
<li>
|
||||
<button
|
||||
@@ -41,6 +109,7 @@
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
ul {
|
||||
position: relative;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
@@ -49,6 +118,8 @@
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
button {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: block;
|
||||
width: 100%;
|
||||
border: 0;
|
||||
@@ -60,19 +131,39 @@
|
||||
font-size: 0.85rem;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
button:hover {
|
||||
background: rgba(65, 155, 56, 0.06);
|
||||
}
|
||||
button.active {
|
||||
border-left-color: var(--accent-color, #419b38);
|
||||
color: var(--accent-color, #419b38);
|
||||
background: rgba(65, 155, 56, 0.1);
|
||||
}
|
||||
button:focus-visible {
|
||||
outline: 2px solid var(--accent-color, #419b38);
|
||||
outline-offset: -2px;
|
||||
}
|
||||
.active-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
border-left: 2px solid var(--accent-color, #419b38);
|
||||
background: rgba(65, 155, 56, 0.1);
|
||||
opacity: 0;
|
||||
will-change: transform, height;
|
||||
}
|
||||
.active-indicator.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
.active-indicator.animate {
|
||||
transition:
|
||||
transform 240ms cubic-bezier(0.16, 1, 0.3, 1),
|
||||
height 240ms cubic-bezier(0.16, 1, 0.3, 1),
|
||||
opacity 160ms ease;
|
||||
}
|
||||
span {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
|
||||
Reference in new issue
Block a user