Canvas Ripple Click Effect — Free Expanding Ring Snippet

Canvas Ripple Click Effect · Animations · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Independent ripple objects
Each click's rings animate on their own timeline.
Concurrent ripples
Rapid clicks overlap without interrupting each other.
Staggered concentric rings
Three rings per click trail each other outward.
Synchronized growth and fade
One progress value drives radius and opacity together.
Self-cleaning array
Finished ripples are filtered out each frame.
Touch support
Responds to touchstart as well as click.
No external library
Pure Canvas 2D API, zero dependencies.
DPR-aware canvas
Crisp rings on high-density displays.

About this UI Snippet

Canvas Ripple Click Effect — Expanding Rings on Every Click

Screenshot of the Canvas Ripple Click Effect snippet rendered live

The canvas ripple click effect snippet spawns an expanding, fading ring at every click point on a canvas — click multiple times in quick succession and each ripple grows and fades entirely on its own, overlapping the others freely. It's built from a plain array of ripple objects animated on the Canvas 2D API, with no physics or graphics library involved.

Each click adds independent state, not a single shared effect

spawnRipple(x, y) doesn't animate anything itself — it pushes plain objects describing a ripple's position, growth target, and age into a ripples array. The actual animation loop, tick(), iterates that array every frame and advances each ripple by its own age, completely independent of any other ripple in the array. That's what lets you click five times rapidly and see five rings at different stages of expansion simultaneously, instead of one ripple restarting on every click.

Three rings per click, gently offset

Rather than a single ring, each click actually queues three ripple objects with a staggered delay (i * 8 frames) and a slightly different lineWidth. Because the delay is checked before a ripple starts advancing its radius (if (r.age < r.delay) return), the three rings from one click trail each other outward like real concentric waves instead of drawing as one flat ring.

Radius and opacity are driven by the same progress value

Each ripple computes a single t value — its progress from 0 to 1 based on age — and derives both its current radius (growing toward maxRadius) and its stroke alpha (fading toward 0) from that same t. Tying both to one progress value keeps the ring's growth and its fade perfectly synchronized: it's always fully faded out exactly as it reaches full size, never lingering as a large but invisible ring wasting array space.

Cleanup keeps the array small

The filter at the end of tick() drops any ripple that has both finished its delay and reached full radius, so the ripples array never grows unbounded even under rapid clicking — old, fully-expanded ripples are removed rather than kept around and skipped every frame.

Customizing it

Change the ring count per click, the stagger delay, maxRadius, or the hue formula for a different palette per click (try varying hue by click position instead of randomly). Pair it with canvas fluid cursor trail for a combined move-and-click canvas interaction, or canvas confetti burst for another click-triggered canvas effect.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain how storing ripples as independent objects in an array, each with its own age, lets multiple clicks animate concurrently without interrupting each other, and why deriving both radius and opacity from a single shared progress value keeps a ring's growth and fade in sync. It's also useful for extending the effect — ask for ripple color that depends on click position instead of randomness, a version that also responds to a keyboard action for accessibility, or combining it with the fluid cursor trail snippet for a richer canvas background. Use the conversation to understand the object-array animation pattern well enough to reuse it for other click- or event-driven canvas effects.

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 "ripple click effect" in plain HTML, CSS, and JavaScript using only the Canvas 2D API — no external libraries or CDNs.

Requirements:
- A canvas panel that listens for click (and touchstart) events and, on each one, spawns an expanding ring animation centered at the click coordinates (converted correctly from page/client coordinates to canvas-local coordinates).
- Represent each ripple as a plain object in an array (position, current radius, max radius, age, and any per-ripple visual properties), and drive the animation loop by iterating that array every frame — do not use a single shared animation state that only supports one ripple at a time.
- Each click must spawn multiple concentric rings (e.g. three) with a staggered start delay between them so they visibly trail each other outward as they expand, rather than drawing as a single flat ring.
- Compute each ripple's current radius and stroke opacity from the same underlying progress/age value so growth and fade-out stay synchronized — the ring should be fully faded out right as it reaches its maximum radius, not before or long after.
- Support multiple ripples animating concurrently and independently: rapid clicks in different (or the same) locations must all animate to completion without resetting or interrupting each other.
- Remove completed ripples from the tracked array each frame (once they've both passed their stagger delay and reached full radius) so the array does not grow unbounded under repeated clicking.
- Make the canvas device-pixel-ratio aware so rings render sharp on high-density displays, and handle window resize by recalculating canvas dimensions.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSA canvas panel and hint text render.
  2. 2
    Click anywhere in the panelThree staggered rings expand and fade from that point.
  3. 3
    Click rapidly in different spotsMultiple ripples animate independently, overlapping freely.
  4. 4
    Tap on a touch deviceTouchstart spawns the same ripple effect.
  5. 5
    Resize the windowThe canvas and ripple max radius adapt.
  6. 6
    Tune ring count or timingEdit the loop in spawnRipple and the progress math in tick.

Real-world uses

Common Use Cases

Click feedback layers
Pair with canvas confetti burst for celebratory clicks.
Interactive backgrounds
Game feedback
Reuse the ripple pattern in a whack a mole game-style hit.
Map/location pickers
Mark a selected point with an expanding ring.
Button/CTA emphasis
Layer a ripple canvas behind an important action button.
Ambient landing sections
Invite exploration with satisfying click feedback.

Got questions?

Frequently Asked Questions

Every click pushes new plain objects into a shared ripples array, each tracking its own position, age, and radius. The animation loop iterates that array every frame and advances each object using only its own state, so ripples from different clicks never interfere with or reset each other — they simply coexist in the same array.

spawnRipple pushes three ripple objects per click, each with a staggered delay value (multiples of 8 frames) and a slightly different line width. Because each ripple waits until its own age passes its delay before it starts expanding, the three rings visibly trail each other outward, reading as concentric waves rather than a single flat ring.

Both the radius and the stroke opacity are derived from the same progress value t, computed from the ripple's age divided by its total animation duration. Since radius grows toward maxRadius and alpha fades toward 0 using that identical t, the ring is always fully transparent right as it reaches full size, rather than fading independently of its growth.

No. At the end of every tick(), the array is filtered to drop any ripple that has both passed its stagger delay and reached full radius. Only ripples still mid-delay or still expanding are kept, so rapid clicking doesn't accumulate stale, fully-faded ripple objects indefinitely.

Move the canvas setup, click/touch listeners, and animation loop into a mount effect scoped to a canvas ref, keeping the ripples array in a ref (not component state) so it isn't reset on re-render. Cancel the requestAnimationFrame loop and remove the event listeners in the effect's cleanup function.