webdevpuneet.com

Scrollytelling Chart — Free HTML CSS JS Snippet

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

Copy & paste

Source Code

<section class="ssc-intro"><p>Scroll ↓</p></section>
<section class="ssc-wrap">
  <div class="ssc-steps">
    <div class="ssc-step" data-step="0"><h3>2021 — The baseline</h3><p>Five regions, one flat market. Nobody stands out yet, and the spread between best and worst is barely 20 points.</p></div>
    <div class="ssc-step" data-step="1"><h3>2022 — The west wakes up</h3><p>A single product launch doubles the West region while everyone else treads water.</p></div>
    <div class="ssc-step" data-step="2"><h3>2023 — The chasing pack</h3><p>Playbooks get copied. North and South close the gap, and the market's center of gravity shifts.</p></div>
    <div class="ssc-step" data-step="3"><h3>2024 — A new leader</h3><p>East compounds quietly for two years, then overtakes everyone in a single quarter.</p></div>
  </div>
  <div class="ssc-sticky">
    <div class="ssc-panel">
      <div class="ssc-title" id="sscTitle">The baseline</div>
      <div class="ssc-chart" id="sscChart"></div>
      <div class="ssc-axis"><span>North</span><span>South</span><span>East</span><span>West</span><span>Central</span></div>
    </div>
  </div>
</section>
<section class="ssc-outro"><p>One chart, four datasets — narrated by scroll position.</p></section>

Extend with AI

Take This Snippet Further With AI

The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.

· Become a supporter

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.

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.
Related: Three.js Scroll Crystal Bloom
See the Three.js Scroll Crystal Bloom for a related scroll pattern worth pairing with this one.

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.