ScrollSpy Benchmark

Bootstrap 5 ScrollSpy JS (config parser + event system + .active class manager + 44 KB infra) vs Native IntersectionObserver (~15 lines)

Bootstrap JS

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).

Section 1

Scroll down to see the nav link highlight change. Bootstrap ScrollSpy manages .active classes on the nav links above.

Section 2

Each scroll position triggers the internal IntersectionObserver, which then goes through Bootstrap’s event system, config parser, and class management layer.

Section 3

All of this requires 44 KB of JavaScript infrastructure: scrollspy.js + base-component + event-handler + selector-engine + data + manipulator + util + config.

Native Browser API

Uses IntersectionObserver directly — one callback toggles .active on nav links. Same result, ~15 lines of JS, zero dependencies, zero custom events.

Section 1

Scroll down — the nav links highlight via a single IntersectionObserver callback. No framework, no config parser, no event system.

Section 2

The observer fires when sections enter the viewport. The callback is just: remove .active from all links, add it to the matching one.

Section 3

Total code: ~15 lines. The browser’s built-in IntersectionObserver handles all the visibility detection.

Initializing... 0%

Results

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
_nav.scss (5.1 KB)
ScrollSpy has no dedicated SCSS — it toggles .active on nav links
- -
Bootstrap JS 20 containers × 10 sections — bootstrap.ScrollSpy
Native Browser API 20 containers × 10 sections — IntersectionObserver
Key difference: Bootstrap ScrollSpy wraps 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.
Native IntersectionObserver scrollspy (entire implementation)
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.