Skip to content

Commit 589c593

Browse files
authored
fix: revise TOC logic (saicaca#252)
fix bugs in fallback, markVisibleSection; refine rAF and connectedCb
1 parent 588a536 commit 589c593

File tree

1 file changed

+40
-36
lines changed

1 file changed

+40
-36
lines changed

src/components/widget/TOC.astro

Lines changed: 40 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -75,30 +75,22 @@ class TableOfContents extends HTMLElement {
7575
this.observer = new IntersectionObserver(
7676
this.markVisibleSection, { threshold: 0 }
7777
);
78-
}
78+
};
7979

8080
markVisibleSection = (entries: IntersectionObserverEntry[]) => {
81-
requestAnimationFrame(() => {
82-
entries.forEach((entry) => {
83-
const id = entry.target.children[0]?.getAttribute("id");
84-
85-
const idx = id ? this.headingIdxMap.get(id) : undefined;
81+
entries.forEach((entry) => {
82+
const id = entry.target.children[0]?.getAttribute("id");
83+
const idx = id ? this.headingIdxMap.get(id) : undefined;
84+
if (idx != undefined)
85+
this.active[idx] = entry.isIntersecting;
8686

87-
if (entry.isIntersecting && this.anchorNavTarget == entry.target)
88-
this.anchorNavTarget = null;
89-
90-
if (idx != undefined)
91-
this.active[idx] = entry.isIntersecting;
92-
});
93-
94-
requestAnimationFrame(() => {
95-
if (!document.querySelector(`#toc .${this.visibleClass}`)) {
96-
this.fallback();
97-
}
98-
this.toggleActiveHeading();
99-
this.scrollToActiveHeading();
100-
});
87+
if (entry.isIntersecting && this.anchorNavTarget == entry.target.firstChild)
88+
this.anchorNavTarget = null;
10189
});
90+
91+
if (!this.active.includes(true))
92+
this.fallback();
93+
this.update();
10294
};
10395

10496
toggleActiveHeading = () => {
@@ -132,7 +124,7 @@ class TableOfContents extends HTMLElement {
132124

133125
if (this.anchorNavTarget || !this.tocEl) return;
134126
const activeHeading =
135-
document.querySelectorAll<HTMLDivElement>("#toc .visible");
127+
document.querySelectorAll<HTMLDivElement>(`#toc .${this.visibleClass}`);
136128
if (!activeHeading.length) return;
137129

138130
const topmost = activeHeading[0];
@@ -153,6 +145,15 @@ class TableOfContents extends HTMLElement {
153145
});
154146
};
155147

148+
update = () => {
149+
requestAnimationFrame(() => {
150+
this.toggleActiveHeading();
151+
// requestAnimationFrame(() => {
152+
this.scrollToActiveHeading();
153+
// });
154+
});
155+
};
156+
156157
fallback = () => {
157158
if (!this.sections.length) return;
158159

@@ -162,20 +163,16 @@ class TableOfContents extends HTMLElement {
162163

163164
if (this.isInRange(offsetTop, 0, window.innerHeight)
164165
|| this.isInRange(offsetBottom, 0, window.innerHeight)
165-
|| (offsetTop < 0 && offsetBottom > window.innerHeight)) {
166+
|| (offsetTop < 0 && offsetBottom > window.innerHeight)) {
166167
this.markActiveHeading(i);
167168
}
168-
else break;
169+
else if (offsetTop > window.innerHeight) break;
169170
}
170-
171-
requestAnimationFrame(() => {
172-
this.toggleActiveHeading();
173-
})
174171
};
175172

176173
markActiveHeading = (idx: number)=> {
177174
this.active[idx] = true;
178-
}
175+
};
179176

180177
handleAnchorClick = (event: Event) => {
181178
const anchor = event
@@ -195,14 +192,19 @@ class TableOfContents extends HTMLElement {
195192

196193
isInRange(value: number, min: number, max: number) {
197194
return min < value && value < max;
198-
}
195+
};
199196

200197
connectedCallback() {
201198
// wait for the onload animation to finish, which makes the `getBoundingClientRect` return correct values
202-
setTimeout(() => {
203-
this.init();
204-
}, 250);
205-
}
199+
const element = document.querySelector('.prose');
200+
if (element) {
201+
element.addEventListener('animationend', () => {
202+
this.init();
203+
}, { once: true });
204+
} else {
205+
console.warn('Animation element not found');
206+
}
207+
};
206208

207209
init() {
208210
this.tocEl = document.getElementById(
@@ -221,6 +223,8 @@ class TableOfContents extends HTMLElement {
221223
document.querySelectorAll<HTMLAnchorElement>("#toc a[href^='#']")
222224
);
223225

226+
if (this.tocEntries.length === 0) return;
227+
224228
this.sections = new Array(this.tocEntries.length);
225229
this.headings = new Array(this.tocEntries.length);
226230
for (let i = 0; i < this.tocEntries.length; i++) {
@@ -240,16 +244,16 @@ class TableOfContents extends HTMLElement {
240244
);
241245

242246
this.fallback();
243-
this.scrollToActiveHeading();
244-
}
247+
this.update();
248+
};
245249

246250
disconnectedCallback() {
247251
this.sections.forEach((section) =>
248252
this.observer.unobserve(section)
249253
);
250254
this.observer.disconnect();
251255
this.tocEl?.removeEventListener("click", this.handleAnchorClick);
252-
}
256+
};
253257
}
254258

255259
customElements.define("table-of-contents", TableOfContents);

0 commit comments

Comments
 (0)