Puneet Sharma - Frontend Developer & UI Engineer
Puneet Sharma
Frontend Dev & UI Engineer · 16+ yrs · pixel-perfect HTML, React & WordPress

10 Copy-Paste Animation Snippets for the Scroll, Canvas & GSAP Trends Everyone's Chasing

10 free animation snippets: native CSS scroll timelines, GSAP ScrollTrigger & Flip, canvas particle text, raw WebGL shaders, Motion One. Copy-paste.
10 Copy-Paste Animation Snippets for the Scroll, Canvas & GSAP Trends Everyone's Chasing

Every animation trend on the web right now points in one of three directions: browsers doing more of the work natively so JavaScript can step back, GSAP's plugin ecosystem making genuinely hard motion problems (pinning, layout morphing, draggable inertia) into a few configured options, or raw Canvas/WebGL for the effects no CSS property will ever reach — particle text, cellular automata, hand-written GPU shaders. Most tutorials pick one of those three lanes and stay there. Real products end up needing all three, often on the same page.

Below are ten free, self-contained snippets pulled from that exact spread: a reading-progress bar powered entirely by the brand-new native CSS animation-timeline: scroll() API with zero scroll listeners, a GSAP ScrollTrigger gallery that pins the page and snaps a horizontal track to each panel, particles that assemble themselves into readable text using nothing but the Canvas 2D API, a hand-written WebGL1 fragment shader gradient with no Three.js involved, GSAP's Flip plugin morphing a card grid into a list with one diffed animation, a whole grid of 3D-tilting cards wired up by a single VanillaTilt.init() call, a fully playable Conway's Game of Life on canvas, a card grid animated entirely with spring physics via the lightweight Motion One library, a real microphone-reactive frequency visualizer built on the actual Web Audio API, and dashboard stat tiles that roll like a mechanical odometer. Every one is a live, interactive preview — try them right here in the article — and each exports to React, Vue, Angular or Tailwind in one click from its snippet page.

What these ten get right

  • Every fallback is shape-matched, not a placeholder message. The audio-bars visualizer's simulated frequency data is packed into the exact same Uint8Array shape real microphone data would produce, and the WebGL shader swaps to a plain CSS gradient if getContext('webgl') returns null — so the drawing code never has to know or care which source it's reading from, and nothing ever looks broken.
  • Capture, mutate, animate — never animate blind. GSAP's Flip plugin records real DOM measurements before a layout change and diffs against real measurements after, rather than hand-coding target positions. The particle-text snippet does the equivalent for arbitrary text: it renders the word to an offscreen canvas and samples actual pixel coordinates instead of guessing where letters should be.
  • State updates go into a fresh buffer, not in place. Conway's Game of Life computes every cell's next generation into a brand new typed array from an untouched snapshot of the current one — mutating cells one at a time in the same array they're being read from would let later cells see already-updated neighbors and corrupt the simulation.
  • The compositor and the GPU do the expensive part. The scroll-driven progress bar's fill runs as a native CSS keyframe evaluated off the main thread; the WebGL gradient computes color per pixel on the graphics card via a fragment shader. Neither approach touches JavaScript on every frame the way a hand-rolled scroll listener or a 2D-canvas gradient loop would.
  • A library is worth it exactly when it replaces code you'd otherwise repeat. VanillaTilt's one init() call scales a hand-rolled tilt effect across an entire grid with glare and gyroscope support included; Motion One's spring() helper replaces bezier-curve guesswork with two physical parameters. Neither snippet reaches for a library to do something three lines of vanilla JS already does well.

1. CSS Scroll-Driven Progress Bar

A reading-progress bar whose fill is driven entirely by the native CSS animation-timeline: scroll() API — no scroll event listener, no requestAnimationFrame loop, for the core effect at all.

How it works: the bar has animation: sdp-grow auto linear and, critically, animation-timeline: scroll(root) — a declaration that swaps the animation's normal clock for the document's own scroll position, so 0% scrolled maps directly to the keyframe's 0% and 100% scrolled maps to 100%, with the browser handling the mapping natively in the compositor. The keyframes themselves just scale the bar from scaleX(0) to scaleX(1). A hand-rolled version needs a scroll handler firing potentially hundreds of times a second, computing scrollY / (scrollHeight - innerHeight) and writing a style on the main thread every time — this version does none of that. An @supports not (animation-timeline: scroll()) block swaps in a striped placeholder for browsers that don't yet support it, and the JS layer only calls CSS.supports('animation-timeline: scroll()') to flip a status badge, attaching a minimal scroll-listener fallback solely when the native feature is missing.

Best for: long-form articles, documentation pages, and case studies where "how much is left" is a real, recurring question — currently strongest in Chromium browsers, with Firefox and Safari still rolling out support. Tip: swap scroll(root) for scroll(nearest) to track a scrollable panel instead of the whole page, which is the same technique behind a view-timeline image reveal for individual elements rather than the document.

Grab the code: CSS Scroll-Driven Progress Bar

2. GSAP ScrollTrigger Pinned Gallery Snap

Scroll down and the section locks in place while your scroll input drives a horizontal track of panels sideways, settling on the nearest whole panel the moment you stop.

How it works: the core tween is gsap.to(track, { x: () => -(track.scrollWidth - window.innerWidth), scrollTrigger: { pin: true, scrub: 1, ... } })pin: true fixes the section in the viewport for the whole scroll range, and scrub: 1 ties the horizontal tween's progress directly to scroll position with a one-second catch-up smoothing, so scrolling down moves the track left in lockstep and scrolling up reverses it. Both the x target and the ScrollTrigger's end value are passed as functions rather than fixed numbers, computed from the track's actual scrollWidth, so invalidateOnRefresh: true can recalculate the whole distance correctly after a resize instead of reusing stale measurements. The snap: { snapTo: 1 / (panels - 1) } config divides scroll progress into one increment per panel and animates to the nearest one once scrolling settles, so the gallery never comes to rest mid-panel.

Best for: portfolio and product galleries, case-study sections, and onboarding tours that benefit from a paced, page-like feel rather than a loose scrub. Tip: drop the snap config entirely for a free-scrubbing gallery, or pair the technique with a scroll reveal grid intro before the pinned section begins.

Grab the code: GSAP ScrollTrigger Pinned Gallery Snap

3. Canvas Particle Text Formation

Hundreds of scattered dots animate into forming a readable word — built entirely on the Canvas 2D API by rendering invisible text and sampling exactly where its pixels land.

How it works: sampleTextPoints(word) draws the target word onto a hidden offscreen canvas at a large bold font size, then reads its pixel data with getImageData — any pixel with alpha above a threshold is "inside" a letter, and its coordinates, sampled on a spaced grid rather than every single pixel for performance, become one particle's target. Each particle stores a current position and a target, and every frame nudges its velocity toward the delta (p.vx = (p.vx + dx * 0.02) * 0.85) before applying it — a simple spring approximation that produces an organic ease rather than a straight-line snap. Switching words doesn't destroy and recreate the particle set: formWord() grows or trims the existing array to match the new point count and just reassigns targets, which is why the same swarm visibly reorganizes from one shape into the next instead of popping in and out.

Best for: hero intros, brand reveals, and loading screens where a logo word or headline assembling from chaos reads as more deliberate than a plain fade-in. Tip: a smaller sampling gap produces denser, more detailed text at the cost of per-frame work — pair it with starfield for a layered space-themed hero.

Grab the code: Canvas Particle Text Formation

4. WebGL Gradient Shader Background

A full-bleed animated gradient rendered by a hand-written GLSL fragment shader on a single full-screen triangle — no Three.js, no shader library, just raw WebGL1.

How it works: most full-screen shader tutorials draw two triangles forming a quad; this one uses a single triangle with vertices at (-1,-1), (3,-1), and (-1,3) — coordinates that extend well past the [-1, 1] clip-space boundary, so the GPU clips the oversized triangle down to exactly the viewport, visually identical to a quad but as one draw call with no shared diagonal edge to rasterize twice. The vertex shader does almost nothing; every pixel of color comes from the fragment shader, which combines a handful of offset sin/cos terms driven by a u_time uniform and blends three colors with mix() — deliberately modest, no full noise implementation required. compileShader() explicitly checks gl.getShaderParameter(shader, gl.COMPILE_STATUS) and logs the real compiler error on failure, because WebGL never throws a JS exception when a shader fails — it just silently renders nothing. If getContext('webgl') returns null, the canvas hides and a static CSS radial-gradient takes over instead of leaving the section blank.

Best for: GPU-accelerated hero backgrounds where a full Three.js scene would be overkill, and as a compact, complete reference for what a library like Three.js is actually doing underneath. Tip: compare it against a 2D-canvas approach to the same aesthetic in gradient mesh hero — same visual family, very different cost profile.

Grab the code: WebGL Gradient Shader Background

5. GSAP Flip Layout Transition

A card board that morphs smoothly between a grid and a stacked list, and reflows just as smoothly on shuffle — powered by GSAP's Flip plugin diffing real before/after DOM measurements.

How it works: the whole snippet is one runFlip(mutate) helper used by both triggers. First, Flip.getState('#flBoard .fl-card') records the current position and size of every card. Then mutate() runs — toggling the is-list class, or re-sorting the cards into DOM order — which jumps the layout to its new state instantly with zero animation. Finally Flip.from(state, { duration: 0.55, stagger: 0.03, absolute: true }) compares the recorded state to the new layout and animates every card from where it used to be to where it now is — that's the "First, Last, Invert, Play" the plugin's name stands for. absolute: true temporarily takes cards out of flow so they can cross paths during a shuffle without fighting the CSS grid's own live reflow, and stagger: 0.03 keeps a full-board reflow from moving as one rigid block.

Best for: view switchers, sortable boards, and any UI where a layout genuinely changes shape — filterable galleries, kanban columns, comparison-list toggles. Tip: the same getState → mutate → Flip.from pattern extends to inserting or removing cards via onEnter/onLeave callbacks, not just reordering — pair it with a bento grid or drag sort list for a fuller layout toolkit.

Grab the code: GSAP Flip Layout Transition

6. Vanilla-Tilt 3D Card Grid

A grid of cards that tilt toward the cursor in 3D with a moving glare highlight and gyroscope support on mobile — the entire interaction wired up by a single VanillaTilt.init() call.

How it works: VanillaTilt.init(document.querySelectorAll('.vtg-card'), { max: 14, speed: 400, perspective: 900, scale: 1.04, glare: true, 'max-glare': 0.25, gyroscope: true }) attaches the tilt behavior to every matched card in one call — there's no per-card mousemove handler anywhere in this file. Internally the library does the same getBoundingClientRect()-based offset math a hand-rolled tilt effect would, but manages the shared render loop, easing back to flat on mouse-leave, and cleanup for every card at once, so the same options object scales from six cards to sixty with zero additional code. glare: true layers a highlight that tracks the cursor as if light were reflecting off the surface; gyroscope: true drives the identical rotation from device orientation on supported mobile browsers, so the effect degrades gracefully instead of just not working on touch. Inside each card, the heading and paragraph use translateZ on top of transform-style: preserve-3d, so they visibly float above the card's surface as it tilts rather than reading as a flat rotated rectangle.

Best for: skill or service grids, product catalogs, and portfolio thumbnails where the same tactile hover needs to apply consistently across many cards. Tip: if you only need one distinctive tilt with full control over the glow's behavior, hand-writing it (like 3D Card Tilt) keeps things dependency-free — reach for VanillaTilt when the same effect needs to apply consistently across a whole grid.

Grab the code: Vanilla-Tilt 3D Card Grid

7. Canvas Conway's Game of Life

A fully playable cellular automaton on canvas — play, pause, step one generation at a time, click cells to edit the board, and drag a speed slider, all built on a flat typed-array grid.

How it works: the grid is a single flat Uint8Array(COLS * ROWS) rather than a nested array of arrays — faster to allocate and iterate, and a clean fit since a Life cell only ever has two states. countNeighbors checks all eight surrounding cells but wraps out-of-bounds coordinates with (x + dx + COLS) % COLS, so the grid behaves like a torus where the right edge is a neighbor of the left, avoiding the boundary bias a flat-stopping edge would introduce. The heart of the simulation, step(), builds a brand new array and computes every cell's next state purely from the current grid, applying Conway's four rules — this matters because mutating the same array in place would let cells computed earlier in the loop see already-updated neighbors, corrupting later cells' counts in the same generation. A requestAnimationFrame loop only calls step() once enough real time has passed based on the speed slider, decoupling simulation speed from display refresh rate so the slider takes effect immediately with no restart.

Best for: CS and education demos of emergent behavior, portfolio technical pieces, and screensaver-style ambient backgrounds. Tip: seed a specific classic pattern (a glider, a blinker) programmatically instead of randomizing to demonstrate stable versus oscillating structures — pair it with tic tac toe game for more canvas-and-grid casual builds.

Grab the code: Canvas Conway's Game of Life

8. Motion One Spring Cards

A team card grid whose entrance, hover pop, and drag-release all move with real spring physics — stiffness and damping instead of bezier easing curves — using the sub-5KB Motion One library.

How it works: a cubic-bezier curve is four fixed control points that always take the same shape regardless of travel distance; a spring is defined by physical parameters instead, and its motion emerges from simulating those forces frame by frame — which is why UI built on real springs tends to feel more responsive than one built purely on timed curves. The entrance calls animate(cards, {...}, { delay: stagger(0.08), easing: spring({ stiffness: 220, damping: 18 }) }), staggering each card's start by 0.08 seconds more than the last. Hovering triggers a snappier spring (stiffness: 300, damping: 14) — higher stiffness with lower damping produces a small overshoot, giving the hover a lively "pop" rather than a flat linear lift. Dragging a card via pointer events and releasing it animates it back to center with another spring, so letting go bounces the way a card on a real desk would rather than snapping instantly. The whole library loads as a UMD global from a CDN script tag — no bundler, no import statement.

Best for: team and about pages, product catalog entrances, and any card grid where a plain fade-in feels flatter than the content deserves. Tip: for complex timelines, scroll-triggered sequencing, or SVG morphing, GSAP (as in Scroll Reveal Grid) is still the more capable tool — reach for Motion One when spring easing and simple entrance/hover/drag motion is all you need.

Grab the code: Motion One Spring Cards

9. Canvas Audio Frequency Bars

A real microphone-reactive visualizer built on the actual Web Audio API — with a sine-driven fallback so convincing the drawing code can't tell the difference between it and genuine frequency data.

How it works: clicking "Use microphone" requests getUserMedia({ audio: true }), pipes the resulting stream through audioCtx.createMediaStreamSource into an AnalyserNode with fftSize: 256, and on every frame calls analyser.getByteFrequencyData(freqData) to fill a Uint8Array with each frequency bin's real 0-255 magnitude — that array is what actually drives every bar's height. Microphone access can fail in ways entirely outside the page's control: a denied prompt, no device, or — especially common when this exact snippet renders inside a sandboxed preview iframe — a Permissions-Policy that never grants microphone to that frame at all, so getUserMedia rejects with no prompt shown. simulatedFrame() handles that by blending two sine waves per bar under a slowly breathing envelope and packing the result into the identical Uint8Array shape real data would take — the rendering code has no branch for "is this real," it just draws whatever shape it's handed. A try/catch around the entire microphone request routes every kind of failure into that same simulated state, named with the actual DOMException, so the visualizer is never blank.

Best for: podcast and music app UI, voice-recording tools, and any embed context (including sandboxed previews) where microphone access can't be guaranteed. Tip: pair it with an audio waveform visualizer for a complete player, and note that the fallback pattern here — matching a simulation's data shape exactly to the real API's output — is reusable anywhere a browser permission might silently fail.

Grab the code: Canvas Audio Frequency Bars

10. Odometer Rolling Stat Counters

Dashboard stat tiles whose digits roll like a mechanical odometer — each digit spinning independently on its own reel — the moment the tile grid scrolls into view.

How it works: odometer.js's API is unusual — there's no .play() call. Once an element is wrapped with new Odometer({ el, value: 0 }), the library replaces its content with a structure of individually-positioned digit reels, and calling .update(newValue) is what triggers the roll: each reel animates independently to its new resting digit, so a number like 15,230 looks visually distinct from a linear count-up, since the ones digit might cycle through several values while the ten-thousands digit only moves once. Rather than rolling immediately on page load — where a visitor below the fold could miss the animation entirely — a single IntersectionObserver watches the whole .ost-grid container and fires rollIn() for all four tiles at once, roughly 40% into view, then disconnects so it only ever fires once. The CDN's theme stylesheet supplies the actual sliding-digit visuals; this snippet's own CSS only sets color, size, and weight so the reels match the surrounding dashboard palette.

Best for: admin dashboards, SaaS metrics pages, and investor or annual-report pages where key figures benefit from a memorable, physical-feeling reveal. Tip: odometer.js ships six built-in themes (minimal, car, digital, plaza, slot machine, train station) — swap the theme CDN link and every digit's rendering changes with no JS or HTML edits, or pair the tiles with a dashboard widget grid for a fuller panel.

Grab the code: Odometer Rolling Stat Counters

How to drop these into your project

  1. Open any snippet and hit View / Edit Code to see the HTML, CSS and JS in separate tabs, then paste the HTML into your markup, the CSS into your stylesheet, and the JS before your closing </body> tag — or use the one-click export to React, Vue, Angular or Tailwind right from the snippet page. Where a CDN script is required (GSAP, VanillaTilt, Motion One, odometer.js), the snippet page lists the exact tags to include; the rest need nothing installed at all.
  2. Reach for native CSS first, a library second, and hand-rolled canvas last. If animation-timeline: scroll() or a view() timeline can do the job — a simple linear or eased progress indicator — it removes a dependency entirely and hands the work to the compositor. Save GSAP's ScrollTrigger and Flip for sequencing, pinning, and layout diffing that CSS genuinely can't express, and canvas/WebGL for effects — particle text, cellular automata, per-pixel shaders — no DOM element can produce.
  3. Every permission-gated or asset-dependent effect needs a real fallback, not a blank state. The audio visualizer's simulated data matches its real data's exact shape; the WebGL background swaps to CSS if the context fails to create. Model your own fallbacks the same way — shape-matched, not just an apologetic message.
  4. Capture before you mutate. GSAP's Flip pattern — getState(), mutate the DOM, then diff and animate — and the particle-text snippet's pixel-sampling both avoid hardcoding target positions by measuring the real thing first. Apply the same discipline anywhere you're tempted to guess coordinates instead of reading them.
  5. In React, Vue, or Angular, all of this moves into mount effects with real cleanup. ScrollTrigger instances, WebGL contexts, MediaStream tracks, and requestAnimationFrame loops all need to be created once (scoped to refs, not querySelectorAll) and explicitly torn down on unmount — each snippet's FAQ section spells out exactly what that cleanup should look like for its specific APIs.
  6. Check the CDN version pin before shipping. These snippets pin specific library versions (GSAP 3, odometer.js 0.4.8, VanillaTilt 1.8.1) deliberately, so behavior doesn't drift out from under you — bump them intentionally, and re-test the specific option or plugin API you're relying on when you do.

Final thought

The interesting split in web animation right now isn't "CSS vs. JavaScript" or "library vs. vanilla" — it's about picking the cheapest tool that's actually capable of the effect you need, and being honest about what happens when that tool's assumptions don't hold: no WebGL context, no microphone permission, an older browser without scroll timelines yet. These ten span that whole range, from a progress bar that needs no JavaScript at all to a raw GPU shader that needs to check its own compile status — and every one of them is small enough to read end to end in a few minutes.

Try them, retune the timings, and export to your framework of choice in one click. Browse the full collection at FWD Tools UI Snippets — it's free and runs entirely in your browser.

About the author

Puneet Sharma
Puneet Sharma is a freelance web developer and the creator of FWD Tools and WebDevPuneet. Follow him on X/Twitter

Post a Comment