Skip to content

Commit 08123c7

Browse files
tompngkou
andauthored
Stable calculation of active toc (#1456)
Fix this unstable active table-of-contents link calculation shown in this video: Clicking "Plugin types", active toc link will be "Option Parsing" or "File Parsing", depend on previous scroll position. https://github.com/user-attachments/assets/bfd5c92a-9f11-4c9b-9267-86ae55607541 IntersectionObserver only notifies changed intersections. We need to track which heading tag currently intersects with viewport. (I use Set in this pull request) Use the top-most intersecting heading to make user-clicked toc links match with active toc links. --------- Co-authored-by: Sutou Kouhei <kou@cozmixng.org>
1 parent e6f5e8b commit 08123c7

File tree

1 file changed

+44
-33
lines changed
  • lib/rdoc/generator/template/aliki/js

1 file changed

+44
-33
lines changed

lib/rdoc/generator/template/aliki/js/aliki.js

Lines changed: 44 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -209,54 +209,65 @@ function generateToc() {
209209

210210
function hookTocActiveHighlighting() {
211211
var tocLinks = document.querySelectorAll('.toc-link');
212-
if (tocLinks.length === 0) return;
212+
var targetHeadings = [];
213+
tocLinks.forEach(function(link) {
214+
var targetId = link.getAttribute('data-target');
215+
var heading = document.getElementById(targetId);
216+
if (heading) {
217+
targetHeadings.push(heading);
218+
}
219+
});
220+
221+
if (targetHeadings.length === 0) return;
213222

214223
var observerOptions = {
215224
root: null,
216-
rootMargin: '-20% 0px -35% 0px',
225+
rootMargin: '0% 0px -35% 0px',
217226
threshold: 0
218227
};
219228

220-
var activeLink = null;
229+
var intersectingHeadings = new Set();
230+
function update() {
231+
var firstIntersectingHeading = targetHeadings.find(function(heading) {
232+
return intersectingHeadings.has(heading);
233+
});
234+
if (!firstIntersectingHeading) return;
235+
var correspondingLink = document.querySelector('.toc-link[data-target="' + firstIntersectingHeading.id + '"]');
236+
if (!correspondingLink) return;
237+
238+
// Remove active class from all links
239+
tocLinks.forEach(function(link) {
240+
link.classList.remove('active');
241+
});
242+
243+
// Add active class to current link
244+
correspondingLink.classList.add('active');
245+
246+
// Scroll link into view if needed
247+
var tocNav = document.querySelector('#toc-nav');
248+
if (tocNav) {
249+
var linkRect = correspondingLink.getBoundingClientRect();
250+
var navRect = tocNav.getBoundingClientRect();
221251

252+
if (linkRect.top < navRect.top || linkRect.bottom > navRect.bottom) {
253+
correspondingLink.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
254+
}
255+
}
256+
}
222257
var observer = new IntersectionObserver(function(entries) {
223258
entries.forEach(function(entry) {
224259
if (entry.isIntersecting) {
225-
var id = entry.target.id;
226-
var correspondingLink = document.querySelector('.toc-link[data-target="' + id + '"]');
227-
228-
if (correspondingLink) {
229-
// Remove active class from all links
230-
tocLinks.forEach(function(link) {
231-
link.classList.remove('active');
232-
});
233-
234-
// Add active class to current link
235-
correspondingLink.classList.add('active');
236-
activeLink = correspondingLink;
237-
238-
// Scroll link into view if needed
239-
var tocNav = document.querySelector('#toc-nav');
240-
if (tocNav) {
241-
var linkRect = correspondingLink.getBoundingClientRect();
242-
var navRect = tocNav.getBoundingClientRect();
243-
244-
if (linkRect.top < navRect.top || linkRect.bottom > navRect.bottom) {
245-
correspondingLink.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
246-
}
247-
}
248-
}
260+
intersectingHeadings.add(entry.target);
261+
} else {
262+
intersectingHeadings.delete(entry.target);
249263
}
250264
});
265+
update();
251266
}, observerOptions);
252267

253268
// Observe all headings that have corresponding TOC links
254-
tocLinks.forEach(function(link) {
255-
var targetId = link.getAttribute('data-target');
256-
var targetHeading = document.getElementById(targetId);
257-
if (targetHeading) {
258-
observer.observe(targetHeading);
259-
}
269+
targetHeadings.forEach(function(heading) {
270+
observer.observe(heading);
260271
});
261272

262273
// Smooth scroll when clicking TOC links

0 commit comments

Comments
 (0)