Skip to content
This repository has been archived by the owner on Apr 18, 2023. It is now read-only.



This is a tiny JavaScript library for creating animations that synchronize with scrolling. It uses requestAnimationFrame to check the scroll status and trigger scrollytelling events.

Check out the mobile-friendly examples that can be used as templates.

Before going over the API, let's establish a vocabulary.

An area within the container designated for data visualization that uses sticky or fixed positioning. Typically this is an <svg> or <canvas> and has a low z-order to allow explanatory text to flow over.

Not all scrollytell stories have charts. For example, the visualization area might be external to the container, although this could be hard to manage for small screens.

The outermost DOM element of concern for a single scrollytell story. Style must include overflow-y: scroll and position: relative.
developer HUD
Heads-up-display for development purposes. This is an overlay over the container that displays the guideline, active panel index, and progress value. It also draws semitransparent boxes over each panel's bounding box.
fullsize chart
Special configuration where the chart has the same height as the browser window.
An invisible line stretched horizontally over the middle of the container that does not scroll with its content. As panels cross the guideline they trigger scrollytell events.
One of several block-level DOM elements that form a scrollytelling sequence. Panels trigger scrollytell events as they cross the guideline. The active panel is the panel that currently overlaps the guideline. Long panels may contain segments that vertically divide the panel into multiple pieces, but do not trigger scrollytell events.
progress value
Percentage in [0,1] that represents the vertical position of the active panel relative to the guideline. When the top of the panel aligns with the guideline, the progress value is 0. When the bottom of the panel aligns with the guideline, the progress value is 1.
The context for a scrollytell animation. Corresponds to a specific container element.


To use scrollytell, create a Story object and pass in a configuration object. The config contains event handlers and querySelector strings. The only two required fields are containerSelector (which should select a single DOM element) and panelSelector (which should select several DOM elements). Here's an example:

import { Story } from "./scrollytell";

new Story({
    containerSelector: ".container",
    panelSelector: ".panels p",
    developerHud: true, // <-- disable this in production!
    enterHandler: (story, panel) => {`Entered panel ${panel}`);
    exitHandler: (story, panel) => {`Exited panel ${panel}`);
    progressHandler: (story, progress) => {
        const percentage = (100 * progress).toFixed(2);`Progress is now ${percentage}%`);

To make a useful story, you'll probably want to do something useful in enterHandler and exitHandler, which are triggered during requestAnimationFrame when a panel crosses in or out of the guideline.

For continuous-style scrollytelling, you can provide a progressHandler which is triggered every time the progress value changes or the active panel changes. Instead of writing your own render loop, you can put your drawing code in the progress handler. This saves power on mobile devices because it only does work when the scroll position changes.

Additionally, the Story object exposes a few methods:

 * Returns the zero-based index of the panel that is currently overlapping
 * the guideline. Returns -1 if no such panel exists.
getActivePanelIndex(): number;

 * Returns a percentage in the range [0, 1] that represents the position of
 * the active panel relative to the guideline. Returns 0 when the top of the
 * panel aligns with the guideline, +1 when the bottom of the panel aligns
 * with the guideline, and -1 if no panel is overlapping the guideline.
getProgressValue(): number;

 * Toggles the heads-up-display for development purposes. Do not enable
 * when your site is in production.
showDeveloperHud(enable: boolean): void;

The above methods provide a way of using the library if you'd like to avoid the event handlers and instead create your own render loop that polls the status of the story.

For more information check out the examples and the TypeScript source.

Fullsize mode

If you want the chart to encompass the background of the container, you can enable fullsizeChart in the config and provide a chartSelector field. This resizes the chart to match the height of the initial window.

In fullsize mode you can also set segmentSelector in the config to select an additional set of elements that get resized to the initial window height. This is useful for creating fixed-sized panels or panel segments. Go to the segmented example to see this in action.


Because of its focus on mobile platforms, currently the scrollytell library does not gracefully handle situations where the container is resized by the user (e.g. if the container expands to fill a browser window). This may be fixed in the future.

Other resources

For step-by-step storytelling, take a look at AMP Stories. See also Mike Bostock's post How to Scroll.


This is not an officially supported Google product.


tiny mobile-friendly scrollytelling library




Code of conduct

Security policy





No releases published


No packages published