Scrollytelling Chart — Free HTML CSS JS Snippet

Scrollytelling Chart · Scroll · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Scrollama pattern
Steps narrate a sticky, morphing graphic.
Data-driven steps
One array holds titles, values, and leads.
CSS-tweened bars
Height transitions with an overshoot settle.
Middle-band trigger
Steps fire when centered under the eyes.
CSS-only pinning
position: sticky, no pin library.
Narrative highlight
The lead bar swaps color per dataset.
Bidirectional
Scrolling up rewinds the story.
Zero libraries
No chart, scroll, or animation dependency.

About this UI Snippet

Scrollytelling Chart — A Sticky Chart Narrated by Scroll Position

Screenshot of the Scrollytelling Chart snippet rendered live

Scrollytelling is the data-journalism pattern popularized by the NYT and The Pudding: narrative steps scroll up one column while a sticky chart beside them morphs to illustrate each step. The reader controls the pace; the chart always shows the data for the paragraph they're reading. This snippet builds the full scrollama-style setup — sticky graphic, step detection, dataset morphing — in vanilla HTML, CSS, and an IntersectionObserver, with no charting or animation library.

The chart is data-driven from a steps array

All content lives in a STEPS array: each entry holds a title, five values, and which bar leads. The bars are built once from the first row; advancing a step only rewrites heights, labels, and a lead class. That separation — build once, restyle per step — is what makes the morph feel like *one chart changing* rather than charts being swapped, and it means adding a step is adding a data row, not markup.

CSS transitions are the animation engine

applyStep just writes height: NN% inline; the 0.8s cubic-bezier(.22,1,.36,1) transition on .ssc-bar animates every bar to its new value with a slight overshoot settle. Letting CSS interpolate means the JavaScript never runs per-frame — the observer fires four times across the whole section, and the browser tweens the rest on the compositor-adjacent fast path for height-in-a-flex-row.

Step detection uses a middle-band observer

Each narrative step is watched with rootMargin: '-45% 0px -45% 0px', collapsing the detection zone to a 10%-tall band across the viewport's center. A step triggers exactly when its text is where the reader's eyes are, and only one step can occupy the band at a time — the scrollama library's core trick, in six lines. Scrolling upward re-fires earlier steps automatically, so the story is fully bidirectional.

position: sticky keeps the graphic in view

The chart panel is position: sticky; top: 0; height: 100vh inside a column as tall as all four steps, so it pins while steps pass and releases at the section's edges with no JavaScript pinning and none of the jump artifacts pinning libraries can introduce.

The lead-bar highlight carries the narrative

Each dataset marks a lead index; that bar swaps to a cyan gradient via the is-lead class. Color is doing narrative work here — in step four, the highlight physically jumps from West to East at the same moment the text announces the overtake, which is the kind of text/graphic synchronization scrollytelling exists for. Value labels use tabular-nums so they don't jitter as digits change.

Steps dim rather than hide

Inactive steps sit at 30% opacity, keeping the story's shape visible while focusing the active paragraph — and on mobile the sticky column hides entirely, letting steps read as a plain article (add per-step inline charts there if needed).

Customizing it

Add datasets (bars rebuild from the first row's length), restyle bars into columns, dots, or lines, or drive any DOM-based graphic — maps, diagrams, counters — from applyStep. Related: scroll story steps with a pinned panel, the bar chart base component, sticky scroll features for the marketing cousin, and a scroll year timeline for date-driven stories.

Build with AI

Build, Understand, Optimize, and Extend It With AI

You don't have to work out the scrollama-style step detection by hand. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why the bars are built once from the first dataset row and only ever have their height rewritten afterward, or why the rootMargin band collapsed to the vertical middle guarantees exactly one narrative step is ever active. The same assistant can help optimize it — asking whether the 0.8s cubic-bezier bar transition should be shortened for datasets with many more bars, or whether the STEPS array's lead index could instead be derived automatically from whichever value is highest each step. It's also useful for extending the effect: ask it to add a second chart type (a line chart) driven by the same applyStep function, annotate bars with change indicators like up/down arrows between steps, or sync a map visualization alongside the bar chart from the same observer. Treat the code less like a finished artifact and more like a starting point for a conversation.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a "scrollytelling chart" in plain HTML, CSS, and JavaScript using only CSS position: sticky, native IntersectionObserver, and CSS transitions — no charting library, no animation library, no scroll event listener.

Requirements:
- A two-column layout: a left column of narrative step blocks (each carrying a data-step index and containing a heading and paragraph describing that moment in the story), and a right column containing a sticky panel (position: sticky, top: 0, height: 100vh) with a title, a bar chart container, and axis labels.
- Store all the narrative data in a single JavaScript array, one entry per step, each with a title, an array of numeric values (one per bar, on a shared 0-100 scale), and an index marking which bar should be visually highlighted as the "lead."
- Build the bar elements only once, using the first step's values to determine how many bars to create — do not rebuild or replace bar elements when the step changes.
- Write an applyStep(index) function that updates the panel title text, sets each bar's height as an inline percentage style from that step's values, updates each bar's numeric label, and toggles a "lead" class on whichever bar matches that step's lead index while removing it from all others.
- Give the bar elements a CSS transition on the height property (a slower duration like 0.8s with an eased overshoot curve) so that whenever applyStep changes a height value, the browser animates smoothly between the old and new height with no JavaScript-driven animation loop.
- Create a single IntersectionObserver watching every narrative step block, using a rootMargin with large negative top and bottom percentages so its effective detection area is a thin horizontal band across the vertical middle of the viewport, and call applyStep with the intersecting step's index inside the callback.
- Confirm scrolling back up through the steps re-triggers earlier steps' applyStep calls and animates the bars back to their earlier values, so the story is fully reversible with no separate rewind logic.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSNo CDNs — sticky CSS and an observer do everything.
  2. 2
    Scroll into the storyThe chart pins while step one's text is centered.
  3. 3
    Keep scrollingEach step morphs the bars to its dataset.
  4. 4
    Watch the lead barThe cyan highlight jumps as the story turns.
  5. 5
    Scroll back upEarlier steps re-fire — the story rewinds.
  6. 6
    Edit the STEPS arrayTitles, values, and lead index drive everything.

Real-world uses

Common Use Cases

Data journalism
Narrate market shifts step by step; a static bar chart can summarize at the end.
Annual reports
Walk stakeholders through the year, then a metric card grid of totals.
Product analytics stories
Show before/after adoption curves; pair with a scroll before after visual.
Pitch pages
Let the traction chart grow as investors read; follow with sticky scroll features.
Research explainers
Step through an experiment's phases like a scroll pin story.
History timelines
Combine with a scroll year timeline counter for date context.

Got questions?

Frequently Asked Questions

An IntersectionObserver watches every step with rootMargin: '-45% 0px -45% 0px', shrinking detection to a thin band across the viewport's middle. A step intersects only while its text occupies that band — where the reader is actually looking — and applyStep fires with its index. It's the core trick of the scrollama library, implemented in a few lines.

applyStep writes each bar's height as an inline percentage, and a CSS transition — 0.8s with a cubic-bezier(.22,1,.36,1) overshoot — interpolates from the old value. JavaScript runs only at step boundaries (four times total), not per frame; the browser does all tweening. The bars are plain flex-column divs, so no canvas or SVG library is involved.

Because the morph is the message: seeing East's bar physically grow past West communicates "overtake" in a way a hard swap can't. Building bars once from the first dataset and only rewriting heights, labels, and the lead class guarantees visual continuity and keeps DOM churn at zero during the story.

Add rows to the STEPS array and matching .ssc-step blocks with incremented data-step indexes — bars, labels, and highlights all derive from the data. Values are percentages of the chart height, so normalize your real numbers to 0–100 (or compute the max and scale). applyStep can drive any DOM graphic, not just bars: maps, counters, or diagrams.

Keep the sticky layout in CSS (Tailwind: sticky top-0 h-screen) and hold the active step index in state, set from an IntersectionObserver registered in a mount effect — useEffect, onMounted, or ngAfterViewInit — and disconnected in the cleanup. Render bars from the active dataset and let the same CSS transitions animate between renders; heights as inline styles diff cleanly in all three frameworks.