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

10 More Copy-Paste Snippets: Real Physics on Canvas, Real 3D Models, and Scroll That Tells a Story

Explore 10 free interactive UI snippets featuring real Canvas physics, Three.js 3D models, GSAP scroll effects, diff tools, and more.
10 More Copy-Paste Snippets: Real Physics on Canvas, Real 3D Models, and Scroll That Tells a Story

The easiest way to make a demo look impressive and be worthless is to fake the hard part. A "metaball" effect that's just overlapping blurred circles. A "3D configurator" that recolors a sphere instead of a real product. A scroll story where the text changes but nothing underneath is actually being measured. All ten snippets below refuse that shortcut on purpose: the metaballs are a genuine scalar field summed per sample point, the water ripples solve an actual discretized wave equation, the 3D pieces load real Khronos-licensed .glb models and compute their explode directions from real geometry, and the scroll stories measure a real SVG path length or a real clip-path percentage rather than eyeballing a number that happens to look right.

Below are ten free, self-contained snippets spanning three lanes: hand-rolled Canvas 2D simulations built on real algorithms (flocking, metaballs, finite-difference wave propagation), Three.js scenes that load and manipulate genuine .glb product models, and GSAP ScrollTrigger stories that tie a real measured value — not a guessed one — to scroll position. Every one is a live, interactive preview you can try 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

  • Simulate the real thing, don't fake its silhouette. The water ripple snippet steps forward an actual 2D wave equation on a height-field grid, so two overlapping ripples genuinely interfere the way real water does — a typical "expanding fading circle" ripple effect can't do that because there's no shared physical state for two ripples to interact through.
  • Measure geometry instead of guessing numbers. The GLB exploded-view snippet computes each part's outward direction from its actual position relative to the assembly's bounding-box center — no hand-authored offset per part — and the map journey snippet positions its marker with getPointAtLength instead of interpolating between two fixed coordinates, so it never cuts corners on a curve.
  • Clone before you mutate shared state. The 3D configurator clones every mesh's material the moment the model loads, specifically because a glTF file often reuses one material instance across several parts — skip the clone and one paint-swatch click would recolor geometry that was never meant to change.
  • One shared number, several synchronized outputs. The transformation-story snippet drives its clip-path wipe, its divider line, and its progress bar from the exact same self.progress value inside one onUpdate callback — nothing can drift out of sync because nothing has its own independent clock.
  • A permission or a bad build shouldn't mean a blank scene. Every GLTFLoader.load() call across these snippets has a real error callback that logs the actual failure and substitutes a working, honestly-labeled placeholder — the exploded-view snippet's fallback is even a genuine three-piece assembly, so the explode slider stays functional even when the real model can't load.

1. Canvas Metaball Blobs

Soft, gooey blobs that merge and split with a wet, organic pull as they drift and respond to the cursor — a true scalar field sampled and rasterized directly into pixel data, no WebGL, no shader.

How it works: for every point on a coarse sampling grid, render() sums each ball's contribution as radius² / distance² — an inverse-square falloff, the same shape used for gravitational fields — into a shared scalar field. Where that summed field crosses a threshold of 1.0, the pixel is "inside" the merged shape; nothing special-cases the merge itself, it's a direct, automatic consequence of two nearby balls' fields overlapping and pushing the combined value over threshold across a wider connected region. Sampling every device pixel at 60fps would be far too slow, so the field is computed on a coarse grid instead and each cell filled as a small flat block, trading a little edge smoothness for real-time performance. The cursor doesn't repel the blobs — within 220px, updateBalls() nudges each ball's velocity toward the pointer by a small constant amount, a deliberately non-physical "wet pull" rather than an accurate force.

Best for: hero backgrounds, loading screens, and creative-agency sites where an organic, liquid ambient layer reads as more alive than a static gradient. Tip: reduce the sampling cell size for finer, smoother edges at the cost of per-frame work, and compare it with Canvas Water Ripple Simulation below for a very different flavor of grid-based canvas simulation.

Grab the code: Canvas Metaball Blobs

2. Canvas Boids Flocking Simulation

A real-time flock of a hundred-plus boids that align, cluster, and avoid collisions with no leader and no central coordination — pure emergent behavior from Craig Reynolds' three classic 1986 steering rules, with the cursor acting as a predator.

How it works: every frame, flock() looks only at neighbors within a fixed 55px PERCEPTION radius and computes three steering vectors — separation (steer away, weighted more strongly the closer a neighbor is), alignment (steer toward the average heading of nearby boids), and cohesion (steer toward their average position). No boid has any awareness of the flock as a whole; it only ever reacts to whoever happens to be within its own radius. The three vectors aren't averaged equally — separation is weighted roughly 1.6x, alignment 1.0x, cohesion 0.9x — which is the single biggest lever over whether the result reads as a tight flock, a loose swarm, or a scattered mess. Both the individual steering forces and each boid's final velocity pass through a limit() helper that caps magnitude while preserving direction, keeping turns smooth instead of jittery. The cursor predator is just a fourth force summed in on top of the other three: any boid within 120px steers directly away from the pointer.

Best for: AI/simulation teaching demos, data or nature-themed landing pages, and interactive portfolio pieces that want to demonstrate real algorithmic depth rather than a looping video. Tip: the neighbor search is a naive O(n²) scan, intentionally — fine up to a few hundred boids, but worth bucketing into a spatial grid before pushing into the thousands.

Grab the code: Canvas Boids Flocking Simulation

3. Canvas Water Ripple Simulation

Click, drag, or touch the surface and real waves propagate outward and interfere with each other — an actual discretized 2D wave equation stepped forward on a height-field grid every frame, not a decorative expanding circle.

How it works: the water surface is two Float32Array grids, current and previous. Every frame, stepWave() takes the sum of its four neighbors' previous heights, halves it, and subtracts the cell's own existing value — (neighborSum / 2 - current[i]), the discrete form of 2 × average − old height — then damps the result by a constant just under 1. That's the standard finite-difference discretization of the wave equation, the same family of numerical method used in real fluid and acoustic simulation. Because every disturbance feeds into that same shared grid, overlapping ripples genuinely add together and interfere rather than just drawing on top of each other. Damping is what makes the surface visibly settle back to stillness instead of oscillating forever, compounding a small energy loss every single step. The clever part is the shading: instead of mapping height directly to color, render() computes the local height gradient between neighboring cells and uses that gradient — not the raw height — to brighten or darken the base water color, approximating how a real surface refracts light depending on its slope, which is what gives flat ripples a convincing sense of depth.

Best for: physics teaching demos, relaxation/wellness apps, and interactive hero sections that want a genuinely tactile, physically-grounded background rather than a loop. Tip: pair the technique conceptually with Canvas Metaball Blobs above — both rasterize a computed scalar field directly into ImageData, just from a very different underlying simulation.

Grab the code: Canvas Water Ripple Simulation

4. GLB Product Color Configurator

A real glTF product model, loaded and recolored live by clicking paint swatches and finish presets — not a primitive sphere standing in for "a product," and not a shared material silently repainting parts it shouldn't touch.

How it works: a glTF scene graph frequently reuses one material instance across several mesh nodes to keep the file small — set .color directly on that shared material and every mesh using it recolors, not just the one you meant to change. The moment the model loads, this snippet walks its full node tree with car.traverse() and clones every mesh's material with node.material.clone() before collecting the clones into a paintMaterials array; every swatch and finish click afterward only ever touches those per-instance clones. Scale is never guessed: the loaded model's bounding box is measured with THREE.Box3, its largest dimension found, and the whole model scaled to a known target height — a real fix for a real bug this exact snippet family hit earlier, where different .glb exports decode at wildly different raw sizes. Color and finish are two genuinely independent controls: swatches set .color, finish presets set .metalness/.roughness together, so any of six colors combines correctly with any of three finishes with zero extra state to track.

Best for: e-commerce product configurators, automotive and vehicle showcases, and any "preview it in your color" flow that currently fakes 3D with a flat image swap. Tip: notice the zoom hint in the corner — OrbitControls' own wheel-zoom is deliberately disabled here (it silently hijacks every plain scroll otherwise) and reimplemented as a slider plus Ctrl/Cmd + scroll, the same fix applied across every GLB snippet in this batch.

Grab the code: GLB Product Color Configurator

5. GLB Exploded View Assembly Toggle

Drag a slider and a real multi-part glTF model pulls itself apart into a technical exploded diagram — with every part's outward direction computed automatically from the assembly's own geometry, not hand-authored one offset at a time.

How it works: most exploded diagrams get built by hand, one offset per part. This one doesn't: model.traverse() flattens every mesh node in the loaded hierarchy — at any nesting depth, with no assumption about a specific model's structure — into a flat list. For each part, its world position is compared against the entire assembly's bounding-box center, and the normalized difference becomes that part's permanent outward direction: parts near the center barely move, parts near the edges explode outward furthest, exactly like a real technical diagram, computed purely from geometry. Positions never accumulate — applyExplode(amount01) always resets each part to its captured original position plus direction × amount01 × MAX_OFFSET, so dragging the slider back and forth ten thousand times can never drift a part out of place. The honest fallback goes a step further than most: if the real model fails to load, the placeholder isn't one mesh, it's a genuine three-piece group, each registered into the same parts array so the explode slider stays fully functional.

Best for: product manuals, engineering/CAD-adjacent portfolios, and "what's inside" e-commerce sections that want a real interactive diagram instead of a static illustration. Tip: because the direction computation makes no assumption about node names, the exact same code works on any multi-part .glb — swap MODEL_URL and it just works.

Grab the code: GLB Exploded View Assembly Toggle

6. Scroll Map Journey Story

An SVG trail draws itself as you scroll while a marker travels along its exact curved geometry and a waypoint story panel updates the moment it passes each stop — the whole route measured, not approximated.

How it works: the route's real length is measured once with route.getTotalLength(), then set as both stroke-dasharray and the starting stroke-dashoffset — the classic SVG line-draw technique. Every scroll update sets the offset to routeLength × (1 − progress), so the drawn portion is always a geometrically exact fraction of the real path, not a rough guess that would draw unevenly across a winding route. The marker doesn't interpolate between two fixed coordinates either, which would visibly cut every corner — route.getPointAtLength(routeLength × progress) asks the path itself for the exact x/y at that fraction of its real length, so it follows every bend precisely. Each waypoint's data-progress value is set to roughly match where that stop actually sits along the path's real length, so a stop lighting up green and the story panel's text changing both happen right as the marker visually arrives — one shared progress value drives the draw, the marker, every stop's state, and the text, so nothing can drift apart.

Best for: expedition and travel recap pages, delivery/logistics journey explainers, and city walking-tour microsites where the map itself should feel traveled, not just illustrated. Tip: swap the abstract path for coordinates traced from a real route, and pair it with Scroll Company Timeline for a date-based rather than geography-based journey.

Grab the code: Scroll Map Journey Story

7. Scroll Transformation Story

Scroll down and a legacy code block visibly overtakes itself with a clean rewrite through a hard-edged clip-path wipe — no opacity blend, no ambiguity about how much has actually changed at any given scroll position.

How it works: the "after" panel sits stacked exactly on top of the "before" panel, starting with clip-path: inset(0 100% 0 0) — fully clipped from its right edge. A single scrubbed ScrollTrigger writes a new inset(0 N% 0 0) on every update, where N shrinks from 100 to 0 as scroll progress climbs. Unlike an opacity crossfade, a clip-path wipe never blends both versions together mid-transition — at any scroll position the frame shows a literal, honest split between exactly how much "before" and "after" is visible. The same self.progress value that drives the wipe also positions a glowing divider line at the identical edge and fills a progress bar, so all three pieces stay permanently in step with each other by construction, not by careful timing. Captions layer a discrete narrative on top of the continuous scrub: a small array of { at, text } pairs is checked each update for the highest threshold progress has already passed — a lightweight step function needing no second ScrollTrigger.

Best for: code refactor case studies, design/UI redesign showcases, and brand-refresh microsites — anywhere a before/after genuinely needs to read as one thing overtaking another rather than fading between two states. Tip: the wipe mechanic only needs two equal-size stacked panels, so screenshots or a paragraph of edited copy work exactly the same as the code blocks shown here.

Grab the code: Scroll Transformation Story

8. GitHub-Style Contribution Heatmap

A 53-week activity calendar with five color intensity levels, aligned month labels, and a cursor-following tooltip — the exact grid pattern GitHub popularized, built from CSS Grid and vanilla JavaScript with zero charting library.

How it works: the script computes a 53-week window ending today, then rewinds the start date to the previous Sunday — start = new Date(start.getTime() - start.getDay() * DAY) — so every week lands cleanly into 7 rows with no partial first column. .chm-grid then does the real layout work: grid-template-rows: repeat(7, 11px) with grid-auto-flow: column means pushing 371 day cells in chronological order automatically wraps them into weekly columns, with zero manual row/column math in JavaScript. Rather than mapping counts to color through a continuous gradient, levelFor() buckets each count into five discrete tiers matching GitHub's own convention — five states read faster at a glance than infinite shades. Month labels get positioned with style.gridColumn = weekIndex + 1 the moment a Sunday starts a new month, keeping them aligned to the exact column even though months never divide evenly into 53 weeks.

Best for: developer profile pages, habit and streak trackers, and any internal dashboard that wants a scannable year of daily activity at a glance. Tip: the random data generator is a clean drop-in point — swap it for a real fetch() call and every other piece (grid, levels, tooltip, month labels) keeps working unchanged.

Grab the code: GitHub-Style Contribution Heatmap

9. Text Diff Checker

Paste two versions of a paragraph and see exactly which words were removed and added, computed by a real longest-common-subsequence algorithm running entirely client-side — no diff-match-patch, no external library.

How it works: tokenize() splits on /(\s+)/ with a capturing group, which — unlike a plain whitespace split — keeps every run of spaces as its own token, so the diff can tell "quick brown" and "quick brown" apart and never has to guess where spacing goes on reassembly. diffWords() then builds a dynamic-programming table where dp[i][j] holds the length of the longest common subsequence between the tails of both token arrays starting at i and j — the same core recurrence git diff and most real diff tools use. A forward walk from (0, 0) reconstructs the actual edits: matching tokens are equal, and at a mismatch the walk follows whichever neighboring cell in the table holds the larger value, deciding whether that token was deleted or inserted. Deleted and inserted tokens render inside real <del> and <ins> elements — the semantically correct tags, not styled <span>s — with every token passed through escapeHtml() first so pasted angle brackets can't break the output.

Best for: contract and legal document review, copyediting tools, and translation QA — anywhere a word-level "what actually changed" answer matters more than a raw text dump. Tip: the same diffWords() function works at character granularity too — drop the whitespace-preserving regex and split on empty string for finer-grained diffs within a single changed word.

Grab the code: Text Diff Checker

10. Half-Star Rating Input

A pointer-precision star rating that resolves to a genuine half-star, not just five clickable whole stars — the kind of small interaction detail most rating widgets skip entirely.

How it works: most star widgets only ever register whole-number clicks — five buttons, five possible values. This one reads the pointer's exact horizontal position within each star via getBoundingClientRect() and compares it against that star's own midpoint: land in the left half and the value resolves to N - 0.5, land in the right half and it resolves to a full N. The visual fill itself uses an overlay-clip technique — a gray outline star renders first, then a gold star sits directly on top inside an overflow: hidden wrapper whose width is set to exactly the resolved rating percentage, clipping the gold glyph down to reveal only that fraction of it, so a 3.5-star value shows precisely half of the fourth star filled rather than rounding up or down to the nearest whole icon.

Best for: product review forms, app-store-style rating prompts, and any feedback flow where "4 out of 5" genuinely isn't precise enough and a real half-star distinction matters. Tip: pair it with App Store Rating Prompt for a full branching flow that routes high and low ratings to different next steps.

Grab the code: Half-Star Rating Input

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. The GLB and scroll-story snippets need CDN scripts (Three.js core, GLTFLoader, OrbitControls, GSAP, ScrollTrigger); every snippet page lists the exact tags required, and the canvas and mixed snippets in this batch need nothing installed at all.
  2. Simulate, don't fake the silhouette, when the interaction genuinely depends on it. A single ripple looks the same whether it's a real wave equation or an expanding fading circle — the difference only shows up the moment two of them overlap. Reach for a real grid-based simulation specifically when your effect needs multiple instances to interact correctly, not as a default for every canvas animation.
  3. Measure real geometry before you animate against it. The exploded-view snippet's per-part direction and the map journey's marker position both come from asking Three.js or the SVG path what's actually true — a bounding box, a point at a given path length — rather than hardcoding a number that happens to look right for one specific model or route. That discipline is what makes the same code work correctly on a completely different model or path with zero changes.
  4. Clone shared state before you mutate it. Anywhere you're about to change something (a material, a config object, a DOM node) that might be referenced elsewhere, clone it first. The GLB configurator's node.material.clone() is a one-line habit that prevents an entire category of "why did unrelated things change color" bugs.
  5. In React, Vue, or Angular, all of this moves into mount effects with real cleanup. Three.js scenes, ScrollTrigger instances, and WebGL contexts all need to be created once against a ref (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. Every external dependency needs an honest fallback, not a blank state. Every GLTFLoader.load() call in this batch has a real error callback that substitutes a working placeholder instead of leaving the scene empty. Model your own fallbacks the same way whenever a snippet depends on a CDN asset, a permission, or an API that might fail.

Final thought

The thread running through all ten of these isn't a shared visual style — a flocking simulation, a color configurator, and a diff checker don't look anything alike. It's that each one does the actual computation its effect requires instead of approximating it with something that merely looks similar in a screenshot. A metaball field genuinely sums influence per sample point; an exploded view genuinely measures each part's position; a diff checker genuinely runs the LCS recurrence. That discipline is invisible when everything's static, and it's exactly what shows up the moment a user actually interacts with the thing — drags two ripples into each other, scrubs a scroll position back and forth, or pastes in a paragraph with unusual spacing.

Try them, retune the constants, 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