Bootstrap 5 ScrollSpy JS (config parser + event system + .active class manager + 44 KB infra) vs Native IntersectionObserver (~15 lines)
MutationObserver. CSS transitions are disabled during the run so we measure raw JS overhead.
Uses bootstrap.ScrollSpy — wraps IntersectionObserver with config parsing, custom event system (activate.bs.scrollspy), .active class management across nested navs, data store, and 8 dependency files (44 KB).
Scroll down to see the nav link highlight change. Bootstrap ScrollSpy manages .active classes on the nav links above.
Each scroll position triggers the internal IntersectionObserver, which then goes through Bootstrap’s event system, config parser, and class management layer.
All of this requires 44 KB of JavaScript infrastructure: scrollspy.js + base-component + event-handler + selector-engine + data + manipulator + util + config.
Uses IntersectionObserver directly — one callback toggles .active on nav links. Same result, ~15 lines of JS, zero dependencies, zero custom events.
Scroll down — the nav links highlight via a single IntersectionObserver callback. No framework, no config parser, no event system.
The observer fires when sections enter the viewport. The callback is just: remove .active from all links, add it to the matching one.
Total code: ~15 lines. The browser’s built-in IntersectionObserver handles all the visibility detection.
| Metric | Bootstrap ScrollSpy | Native IntersectionObserver |
|---|---|---|
| Initialization time (20 containers × 10 sections) | - | - |
| Scroll time (50 cycles × 20 containers × 10 sections) | - | - |
| DOM mutations (50 cycles × 20 containers × 10 sections) | - | - |
| Instance memory cost (20 ScrollSpy instances) | - | - |
|
JS code size
scrollspy.js (8.4 KB) +
base-component (2.8 KB) +
event-handler (9.2 KB) +
selector-engine (4.1 KB) +
data (2.1 KB) +
manipulator (2.3 KB) +
util (9.1 KB) +
config (2.5 KB)
|
- | - |
| CSS output size | - | - |
bootstrap.ScrollSpy
IntersectionObserver
IntersectionObserver with 44 KB of
JavaScript infrastructure: a config parser, custom event system (activate.bs.scrollspy), data
store for instance management, .active class propagation through nested nav structures,
manipulator utils, and selector engine. Native IntersectionObserver does the same visibility
detection — the callback just toggles .active directly in ~15 lines of code. Both use the
same browser-level intersection detection, but Bootstrap adds significant overhead on top of it.
const nav = document.querySelector('#my-nav');
const container = document.querySelector('#scroll-area');
const sections = container.querySelectorAll('[id]');
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
nav.querySelectorAll('.active').forEach(a => a.classList.remove('active'));
const link = nav.querySelector(`a[href="#${entry.target.id}"]`);
if (link) link.classList.add('active');
}
}
}, { root: container, rootMargin: '0px 0px -40% 0px', threshold: 0 });
sections.forEach(section => observer.observe(section));
// That's it. ~15 lines. Same result as Bootstrap ScrollSpy.