Files
bitvid/benchmarks/docs_scroll_spy.html
T
google-labs-jules[bot]andPR0M3TH3AN ea301d1230 Refactor Docs ScrollSpy to use IntersectionObserver and fix dist/src mismatch
Replaces legacy scroll listener with `IntersectionObserver` to eliminate layout thrashing.
- Updates `js/docsView.js` with `rootMargin: "-96px 0px -50% 0px"` and correct scroll-up logic.
- Synchronizes `dist/js/docsView.js` to match the source logic exactly.
- Updates benchmarks to use the same logic for verification.
- Updates lint scripts to exclude benchmarks.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-05 21:21:40 +00:00

167 lines
5.4 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Scroll Spy Benchmark</title>
<style>
body { font-family: sans-serif; line-height: 1.6; margin: 0; padding: 0; }
nav { position: fixed; top: 0; left: 0; width: 200px; height: 100vh; overflow-y: auto; background: #f0f0f0; padding: 20px; box-sizing: border-box; }
main { margin-left: 220px; padding: 20px; }
section { height: 800px; border-bottom: 1px solid #ccc; padding-top: 100px; }
.active { color: red; font-weight: bold; }
h2 { margin: 0; }
.heavy {
display: flex;
flex-wrap: wrap;
}
.heavy div {
width: 10px;
height: 10px;
margin: 1px;
background: #ddd;
}
</style>
</head>
<body>
<nav id="toc"></nav>
<main id="content"></main>
<script>
const NUM_SECTIONS = 200;
const OFFSET = 96;
const headings = [];
const toc = document.getElementById('toc');
const content = document.getElementById('content');
for (let i = 0; i < NUM_SECTIONS; i++) {
const id = `section-${i}`;
const link = document.createElement('a');
link.href = `#${id}`;
link.textContent = `Section ${i}`;
link.style.display = 'block';
link.id = `link-${id}`;
toc.appendChild(link);
const section = document.createElement('section');
const h2 = document.createElement('h2');
h2.id = id;
h2.textContent = `Section ${i}`;
section.appendChild(h2);
const heavy = document.createElement('div');
heavy.className = 'heavy';
for (let j = 0; j < 500; j++) {
const d = document.createElement('div');
heavy.appendChild(d);
}
section.appendChild(heavy);
content.appendChild(section);
headings.push(h2);
}
let activeId = "";
function setActive(id) {
if (activeId === id) return;
if (activeId) {
const prev = document.getElementById(`link-${activeId}`);
if (prev) prev.classList.remove('active');
}
activeId = id;
if (id) {
const next = document.getElementById(`link-${id}`);
if (next) next.classList.add('active');
}
}
let totalTime = 0;
let count = 0;
function updateLegacy() {
const start = performance.now();
let nextId = headings[0].id;
for (const heading of headings) {
const top = heading.getBoundingClientRect().top - OFFSET;
if (top <= 0) {
nextId = heading.id;
} else {
break;
}
}
setActive(nextId);
totalTime += (performance.now() - start);
count++;
}
let observer;
function setupOptimized() {
if (observer) observer.disconnect();
observer = new IntersectionObserver((entries) => {
const start = performance.now();
entries.forEach(entry => {
if (entry.isIntersecting) {
setActive(entry.target.id);
} else if (entry.boundingClientRect.top > 96) {
// Scrolling up simulation
const index = headings.findIndex((h) => h.id === entry.target.id);
if (index > 0) {
setActive(headings[index - 1].id);
}
}
});
totalTime += (performance.now() - start);
count++;
}, {
rootMargin: "-96px 0px -50% 0px",
threshold: 0
});
headings.forEach(h => observer.observe(h));
}
let rafId;
window.startLegacy = () => {
totalTime = 0;
count = 0;
window.onScrollLegacy = () => {
// To measure pure layout thrashing cost, we should NOT wrap in RAF in the benchmark logic
// if we want to ensure it runs every scroll event.
// But realistically, it IS wrapped in RAF in production.
// So we stick to RAF, but we measure the execution time of the *inner* function.
if (rafId) return;
rafId = requestAnimationFrame(() => {
updateLegacy();
rafId = null;
});
};
window.addEventListener('scroll', window.onScrollLegacy);
};
window.stopLegacy = () => {
window.removeEventListener('scroll', window.onScrollLegacy);
if (rafId) cancelAnimationFrame(rafId);
rafId = null;
};
window.startOptimized = () => {
totalTime = 0;
count = 0;
setupOptimized();
};
window.stopOptimized = () => {
if (observer) observer.disconnect();
}
window.getMetrics = () => ({
avgTime: count ? totalTime / count : 0,
totalTime,
count
});
</script>
</body>
</html>