scrollmirror

A vanilla JavaScript micro-library that syncs the scroll position of multiple elements on a page.

Library
npm
v1.2.4
24stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
29/100Needs Attention
Development Activity8
Maintenance32
Community16
Maturity48
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
76/100Good
Architecture78
Code Quality72
Innovation80
Learning Curve75

ScrollMirror is a lightweight, dependency-free JavaScript library that keeps the scroll position of multiple elements in sync. Point it at any NodeList or array of scrollable elements — including the page root — and it mirrors vertical and horizontal scroll as a percentage of each element’s own scrollable range, so panes of different lengths still line up proportionally instead of drifting apart.

Written in TypeScript and built with microbundle, it ships ESM, CommonJS, and UMD bundles alongside type declarations, so it drops into a bundler-based project or a plain <script> tag from a CDN with the same API. A small imperative surface — pause(), resume(), destroy(), and a settable progress property — covers the cases where mirrored panes need to be temporarily decoupled or driven manually.

What You Get

  • A single dependency-free class (ScrollMirror) with zero runtime dependencies
  • Proportional scroll mirroring driven by scroll-progress ratios rather than raw pixel offsets, so mismatched content lengths still line up
  • Support for mirroring to/from the page root by including :root, html, or body in the element selector
  • Prebuilt ESM, CommonJS, and UMD bundles plus bundled TypeScript type declarations
  • An imperative API — pause(), resume(), destroy(), and a gettable/settable progress property — for manual control

Common Use Cases

  • Side-by-side code diff or translation viewers where two panes must scroll together
  • Synced comparison sliders (before/after images, pricing tables) that scroll in lockstep
  • Custom scrollbars or minimap-style navigation that mirrors the main content’s scroll position
  • Multi-column layouts (e.g. parallel document translations) that need proportional scroll sync despite differing content lengths

Under The Hood

Architecture The library centers on a single ScrollMirror class (src/ScrollMirror.ts) that normalizes the provided elements to their nearest scroll container via getScrollContainer, deduplicates them, and attaches a shared handleScroll listener to each through addScrollHandler/removeScrollHandler in src/support/functions.ts. On scroll, handleScroll waits a tick (nextTick, using requestAnimationFrame) then computes the scrolled element’s progress with the pure helper getScrollProgress (src/support/helpers.ts) and calls mirrorScrollPositions, which applies that ratio to every other element via setScrollPosition while temporarily detaching and re-attaching each element’s own listener to prevent feedback loops. Pure scroll-math and validation logic is factored out of the class into helpers.ts and functions.ts, keeping the stateful class thin and the math independently testable.

Tech Stack The package is authored entirely in TypeScript with no runtime dependencies, built via microbundle into modern ESM, legacy CJS, and a UMD bundle (targeted separately for <script>/CDN use) plus generated .d.ts declarations. Release automation runs on Changesets (@changesets/cli) with a dedicated version-or-publish GitHub Actions workflow, formatting is handled by Prettier, and the project ships its own documentation/demo site built with Astro (plus astro-expressive-code and astro-feather) deployed to Netlify.

Code Quality The project has two separate, CI-enforced test suites: unit tests with Vitest (tests/unit, run in a jsdom environment, covering exports and internal helpers like the logger) and end-to-end tests with Playwright (tests/e2e) that drive the real demo page to assert actual scroll mirroring behavior across vertical, horizontal, both-axis, and root-mirroring scenarios. Both suites run in dedicated GitHub Actions workflows on every push. TypeScript provides type safety throughout, and JSDoc-style comments annotate public class members, though there is no dedicated ESLint config — Prettier is the only enforced style tool.

API Design The public surface is intentionally small: construct with new ScrollMirror(elements, options), then call pause(), resume(), or destroy(), or read/write the progress property. The progress setter is notably ergonomic — it accepts a single number to set both axes at once, or a partial {x, y} object to set just one, falling back to the current value for the other. Debug mode adds console warnings (missing overflow, missing CSS overflow value) to catch common setup mistakes early, keeping the API approachable with minimal boilerplate to get a working mirror running.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search