back to home

janpaepke / ScrollMagic

The javascript library for magical scroll interactions.

14,959 stars
2,136 forks
0 issues
TypeScriptJavaScript

AI Architecture Analysis

This repository is indexed by RepoMind. By analyzing janpaepke/ScrollMagic in our AI interface, you can instantly generate complete architecture diagrams, visualize control flows, and perform automated security audits across the entire codebase.

Our Agentic Context Augmented Generation (Agentic CAG) engine loads full source files into context, avoiding the fragmentation of traditional RAG systems. Ask questions about the architecture, dependencies, or specific features to see it in action.

Embed this Badge

Showcase RepoMind's analysis directly in your repository's README.

[![Analyzed by RepoMind](https://img.shields.io/badge/Analyzed%20by-RepoMind-4F46E5?style=for-the-badge)](https://repomind-ai.vercel.app/repo/janpaepke/ScrollMagic)
Preview:Analyzed by RepoMind

Repository Summary (README)

Preview

ScrollMagic 3

npm version license bundle size <!-- TODO: replace with bundlephobia badge once stable release is published --> dependencies TypeScript

The lightweight library for magical scroll interactions

Looking for ScrollMagic v2? The legacy version is on the v2-stable branch.

ScrollMagic tells you where an element is relative to the viewport as the user scrolls — and fires events when that changes.

It's a convenience wrapper around IntersectionObserver and ResizeObserver that handles the performance pitfalls and counter-intuitive edge cases for you.

Donate

Not an animation library – unless you want it to be

By itself, ScrollMagic doesn't animate anything. It provides precise scroll-position data and events — what you do with them is up to you. If you're looking for a ready-made scroll animation solution, check out GSAP ScrollTrigger, Motion, or anime.js.

ScrollMagic is the foundation, tools like these can be built upon: framework-agnostic, zero-dependency, and usable for any scroll-related UX — class toggles, progress-driven animations, lazy loading, parallax, scroll-linked video, behavioural tracking, or anything else.

Why ScrollMagic?

  • Tiny footprint, zero dependencies
  • Free to use (open source)
  • Optimized for performance (shared observers, batched rAF, single-frame updates)
  • Built for modern browsers, mobile compatible
  • Native TypeScript support
  • SSR safe
  • Works with any scroll container (window or custom element)
  • Horizontal and vertical scrolling
  • Plugin system for extensibility
  • Framework agnostic — works with React, Vue, vanilla JS, anything

Installation

npm install scrollmagic@next

Quick Start

import ScrollMagic from 'scrollmagic';

new ScrollMagic({ element: '#my-element' })
	.on('enter', () => console.log('visible!'))
	.on('leave', () => console.log('gone!'))
	.on('progress', e => console.log(`${(e.target.progress * 100).toFixed(0)}%`));

How It Works

ScrollMagic uses two sets of bounds to define when a scene is active:

  • Trigger bounds — a zone on the scroll container, defined by triggerStart and triggerEnd
  • Element bounds — a zone on the tracked element, defined by elementStart and elementEnd

Progress goes from 0 to 1 as the element bounds pass through the trigger bounds. Events fire on enter, leave, and progress change.

<!-- TODO: add diagram illustrating trigger bounds and element bounds -->

Options

All options are optional. They can be passed to the constructor and updated at any time via setters or .modify().

OptionTypeDefaultDescription
elementElement | string | nullfirst child of scrollParentThe tracked element (or CSS selector).
scrollParentWindow | Element | string | nullwindowThe scroll container.
verticalbooleantrueScroll axis. true = vertical, false = horizontal.
triggerStartnumber | string | function | nullinferred (see below)Start inset on the scroll container.
triggerEndnumber | string | function | nullinferred (see below)End inset on the scroll container.
elementStartnumber | string | function0Start inset on the element.
elementEndnumber | string | function0End inset on the element.

Inset values work like CSS top/bottom: positive values offset inward from the respective edge. Accepted value types:

  • Numbers — pixel values (e.g. 50)
  • Strings — percentage or pixel strings (e.g. '50%', '20px'), relative to the parent size (scroll container for trigger options, element for element options)
  • Named positions'here' (0%), 'center' (50%), 'opposite' (100%)
  • Functions(size) => number for dynamic computation

null means infer: For element, scrollParent, triggerStart, or triggerEnd, setting it to null resets them to their inferred default.

For triggerStart/triggerEnd the inferred values depend on the element option value:

  • element is null → the element defaults to the first child of the scroll container (for window this is document.body), which is expected to define the full scrollable height. Triggers default to 'here' (0%), so progress maps to the overall scroll position within the container, going from 0 at the top to 1 at the bottom.
  • element is not null → triggers default to 'opposite' (100%), making the entire scroll container the trigger zone. Progress goes from 0 to 1 as the element scrolls through the container — entering from one edge and leaving through the other.

Events

Subscribe with .on(), .off(), or .subscribe() (returns an unsubscribe function).

EventWhen
enterElement enters the active zone (progress leaves 0 or 1)
leaveElement leaves the active zone (progress reaches 0 or 1)
progressProgress value changes while in the active zone

Every event provides:

event.target; // the ScrollMagic instance (access all properties, e.g. event.target.progress, event.target.element)
event.type; // 'enter' | 'leave' | 'progress'
event.direction; // 'forward' | 'reverse'
event.location; // 'start' | 'inside' | 'end'

Examples

// Default: active from the moment any part of the element
// enters the viewport until it fully leaves it
new ScrollMagic({
	element: '#a',
});

// Active while the element passes through the center line
new ScrollMagic({
	element: '#b',
	triggerStart: 'center',
	triggerEnd: 'center',
});

// Same as above, but with element offsets:
// starts 50px before the element, ends 100px after it
new ScrollMagic({
	element: '#c',
	triggerStart: 'center',
	triggerEnd: 'center',
	elementStart: -50,
	elementEnd: -100,
});

// Active while passing center, but with a fixed scroll
// distance of 150px, regardless of element height.
// elementEnd receives the element's size and offsets from
// the bottom — (size - 150) leaves only 150px of track.
new ScrollMagic({
	element: '#d',
	triggerStart: 'center',
	triggerEnd: 'center',
	elementEnd: size => size - 150,
});

// Active only while the element is fully visible
// (both offsets pushed to the opposite edge = full element height)
new ScrollMagic({
	element: '#e',
	elementStart: 'opposite', // same as '100%'
	elementEnd: 'opposite', // same as '100%'
});

API

const scene = new ScrollMagic(options);

// Event listeners
scene.on(type, callback); // add listener, returns scene (chainable)
scene.off(type, callback); // remove listener, returns scene (chainable)
scene.subscribe(type, callback); // add listener, returns unsubscribe function

// Modify options after creation
scene.modify({ triggerStart: 'center' });

// All options can also be directly read and written
const elem = scene.element; // get the tracked element
scene.triggerStart = 'center'; // set individual options

// Read-only getters
scene.progress; // 0–1, how far through the active zone
scene.scrollOffset; // { start, end } absolute scroll positions
scene.computedOptions; // resolved option values after computation

// Lifecycle
scene.destroy();

// Static
ScrollMagic.defaultOptions({ vertical: false }); // get/set defaults for new instances

Plugins

ScrollMagic has a plugin system for extending instance behaviour.

const myPlugin: ScrollMagicPlugin = {
	name: 'my-plugin',
	onAdd() {
		// `this` is the ScrollMagic instance
		this.on('enter', () => {
			/* ... */
		});
	},
	onRemove() {
		this.off('enter' /* ... */);
	},
	onModify(changedOptions) {
		// react to option changes
	},
};

scene.addPlugin(myPlugin);
scene.removePlugin(myPlugin);

Browser Support

Chrome 73+, Firefox 69+, Safari 13.1+, Edge 79+ (aligned to ResizeObserver support).

License

MIT — Jan Paepke

<!-- TODO: link to extended documentation, demos, migration guide -->