More Scroll Snippets
Lottie Scroll Bar Chart — Bars Grow on Scroll
Lottie Scroll Bar Chart · Scroll · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
How to Build a Scroll-Driven Lottie Bar Chart That Grows Column by Column

The Lottie Scroll Bar Chart snippet grows a row of coloured bars up from a baseline as the visitor scrolls, one column staggered a beat behind the next, and shrinks them back down on the way up. Nothing plays on a timer — the scrollbar drives the animation frame by frame — and the whole chart is generated in JavaScript rather than exported from After Effects, so the growth mechanism is fully visible in the code.
Lottie is JSON, and here it is assembled in code
A Lottie animation is a JSON document of vector layers and keyframes. Instead of fetching one, this snippet builds the animationData object directly and passes it to lottie.loadAnimation, so there is nothing to host and nothing that can 404. A small layer helper stamps out each bar, and a keyframe helper assembles the growth animation with the easing tangents lottie-web requires.
Growing a bar with scale, not height
The natural instinct is to animate a rectangle's height, but that grows it from the centre in both directions. The trick used here is to grow the bar with a transform scale instead. Each bar is a group whose origin sits exactly on the baseline; inside it, the rectangle is positioned so its whole body is above that origin. Animating only the group's vertical scale from 0 to 100 then stretches the bar upward from the base, exactly like a real column filling in — no repositioning maths required. At scale 0 the bar collapses onto the baseline and is invisible; at 100 it stands at full height.
The one keyframe detail that matters
When you author a Lottie by hand, every animated keyframe must carry its i (in) and o (out) bezier tangents. Omit them and lottie-web treats the keyframe as a hold keyframe: it never interpolates, so a scale animated 0 to 100 stays stuck at 0 and the bar never grows. The keyframe helper here attaches linear tangents to every keyframe, which is what makes the bars actually rise.
The column stagger
The bars do not all grow at once. Each bar's scale animation is offset by its column index — column two starts a beat after column one, and so on — so the chart fills in as a left-to-right sweep rather than a flat simultaneous pop. That single per-column delay is what gives the reveal its rhythm; without it the chart would feel like a snap rather than a build.
Autoplay off, scroll on
The animation is loaded with autoplay: false so its internal clock never runs. A GSAP ScrollTrigger pins the stage and scrubs a single plain value from 0 to 1 over a range several viewport-heights tall, and a requestAnimationFrame loop reads that value every frame and calls anim.goToAndStop(p * (totalFrames - 1), true). The true tells lottie-web the value is a frame number, and goToAndStop renders a single still frame without starting playback, so the chart's growth is welded to scroll position. Because lottie-web interpolates between keyframes, fractional frames render smoothly.
Reversible for free
Deriving the frame from one scrubbed value means scrolling up produces smaller values, so the bars shrink back down in reverse order with no extra code. A smoothed scrub: 0.5 lets the growth glide toward the scroll position rather than snapping to every wheel tick, and the caption fades out the instant the chart begins to build. Pair it with the Lottie scroll line draw for a trend line, or the Lottie scroll scrub ring for a summary stat.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not have to reverse-engineer how a JSON file becomes a scroll-grown chart. Paste this snippet's HTML, CSS, and JS into an AI assistant like Claude and ask it to walk through why the bars grow with a scale transform instead of an animated height, or why every keyframe needs i/o tangents. The same assistant can help you extend it — ask it to add value labels above each bar, animate a horizontal grid line, feed the HEIGHTS array from real data, or turn the bars into a grouped or stacked chart. Treat the code as a conversation starter, not a finished artifact.
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:
Build a "scroll-driven Lottie bar chart" in plain HTML, CSS, and JavaScript using lottie-web and GSAP's ScrollTrigger plugin, both from a CDN (no bundler).
Requirements:
- A pinned full-height section with a centered container div for the Lottie, an intro caption overlay, and a live "percent grown" read-out.
- Build the Lottie as an inline animationData JSON object (leave a comment showing how to swap it for path: 'your-animation.json'). Include a keyframe helper that ALWAYS attaches i/o easing tangents (linear) — without them lottie-web holds the value and nothing animates.
- The animation is a row of coloured bars that grow up from a baseline. Grow each bar with a transform scale, not by animating height: place the bar's rectangle above a base-aligned group origin and animate the group's vertical scale from 0 to 100. Colours interpolate across the bars by index.
- Stagger each bar's growth by its column index so the chart fills in left to right rather than all at once.
- Load with lottie.loadAnimation using renderer 'svg', loop false, autoplay FALSE.
- Register a GSAP tween on a ScrollTrigger targeting the pinned section, with pin true, start at top top, scrub around 0.5, and an end several hundred percent tall, animating one value p from 0 to 1.
- In a requestAnimationFrame loop, read p and call anim.goToAndStop(p * (anim.totalFrames - 1), true), update the read-out, and fade the caption out once p passes a small threshold.
- Confirm scrolling up shrinks the bars back down.Step by step
How to Use
- 1Load the three CDN scriptsAdd lottie.min.js, gsap.min.js, and ScrollTrigger.min.js from the CDN panel, in that order.
- 2Paste the HTML, CSS, and JSA baseline and a "% grown" read-out appear centred in a pinned stage, with the bars starting at zero height.
- 3Scroll downThe bars grow up from the baseline, column by column in a left-to-right stagger, tied directly to scroll position.
- 4Scroll back upThe bars shrink back down exactly, because goToAndStop seeks a single frame rather than playing on a timer.
- 5Set your own dataEdit the HEIGHTS array (values 0 to 1) and the bar count to plot your own figures; colours interpolate automatically.
- 6Tune the revealChange the per-column stagger, the growth duration, or the ScrollTrigger end value to speed up or slow the build.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Animating a rectangle height grows it from its centre in both directions, which looks wrong for a chart. Placing each bar so its body sits above a base-aligned group origin and animating only the vertical scale from 0 to 100 stretches the bar upward from the baseline, exactly like a real column filling. It also keeps the animation to a single property per bar, which is simpler and cheaper than animating both height and position.
Because lottie-web treats an animated keyframe with no i/o easing tangents as a hold keyframe — it never interpolates, so a scale keyframed 0 to 100 stays stuck at 0 and the bar never grows. The keyframe helper in this snippet attaches linear tangents to every keyframe. If you hand-edit a keyframe and a bar stops growing, missing tangents are the first thing to check.
Edit the HEIGHTS array — each value from 0 to 1 is a bar height as a fraction of the maximum — and change N to match the number of bars. The x positions, colours, and stagger all derive from the index, so they adapt automatically. For labels or axis ticks, add more shape layers positioned along the baseline, or overlay HTML text on top of the pinned stage.
play runs the animation on its internal clock, which is exactly what you do not want when scroll should control it. goToAndStop(frame, true) renders one specific still frame and never starts the timer — the second argument tells lottie-web the value is a frame number, not milliseconds. Multiplying scroll progress by totalFrames gives the frame to show, and fractional frames interpolate smoothly so the growth is continuous.
Yes. Click JSX for a React component, Vue for a Vue 3 SFC, Angular for a standalone component, or Tailwind for a React + Tailwind version. Call lottie.loadAnimation against a ref inside a mount effect, register the ScrollTrigger there, and on cleanup call anim.destroy() and kill the ScrollTrigger instance (or revert a gsap.context) so the SVG and scroll listener are released on unmount.