You Might Also Like
Canvas Fluid Cursor Trail — Free Soft Blended Trail Snippet
Canvas Fluid Cursor Trail · Animations · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Canvas Fluid Cursor Trail — A Lightweight, Glowing Cursor Trail

The fluid cursor trail snippet follows your cursor with a soft, glowing streak of blended blobs that stretch along your movement direction and fade out behind you. It's worth being upfront about what this is and isn't: it's a tasteful *approximation* of a fluid look, built from layered radial gradients, velocity-based stretching, and additive blending on the Canvas 2D API — not a real Navier-Stokes fluid simulation, and it doesn't need to be one to look good.
Velocity, not just position, drives the shape
Every pointer move, spawnBlob() compares the current pointer position to the last recorded one to get a velocity vector. That velocity feeds three things at once: the blob's own drift (vx * 0.15), its stretch factor (faster movement elongates the blob along the direction of travel via ctx.rotate + ctx.scale), and its hue, so quick flicks read visually differently from slow, deliberate movement.
Additive blending is what makes it glow
Each blob is drawn with ctx.globalCompositeOperation = 'lighter', which adds color values where blobs overlap instead of simply layering opaque shapes on top of each other. Where several fresh, bright blobs overlap near the cursor, colors blow out toward white-hot; where they're sparse and faded, they stay a soft, dim glow — that additive overlap is most of what sells the "fluid" read, along with the radial gradient giving each blob a soft falloff instead of a hard edge.
The trail fades via a translucent overlay, not per-blob alpha alone
Instead of clearing the canvas every frame, tick() paints a low-opacity dark rectangle over the whole canvas (rgba(5,6,10,0.14)) before drawing new blobs. That partial-opacity wash is what leaves old blobs visibly lingering and dimming across several frames — a cheap way to get a persistent trail without storing and re-rendering a history array of past frames.
Individually aging, independently moving blobs
Each blob is a plain object with its own position, velocity (with damping via *= 0.94), lifespan, size, and hue, pushed into a blobs array and filtered out once its life reaches zero. There's no shared physics grid or pressure field — this is closer to a lightweight particle system tuned to look fluid-ish than a genuine fluid solver, and that honesty is intentional: it stays fast and dependency-free.
Customizing it
Adjust the fade rectangle's opacity for a longer or shorter trail, tune the hue formula for a different palette, or change globalCompositeOperation to 'screen' or 'overlay' for a different blend character. Pair it with particle network or starfield for other ambient canvas backgrounds, or canvas ripple click effect for a click-driven companion interaction.
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 why additive blending (globalCompositeOperation: 'lighter') combined with a translucent fade-overlay produces a convincing glowing trail without any real fluid physics, and how the velocity-based stretch and hue calculations connect cursor speed to the trail's visual character. It's also a good snippet to extend with an assistant — ask for a version with multiple color palettes, a toggle between additive and normal blending to compare the effect, or a lightweight approximation of viscosity by smoothing the velocity used for stretching. Being upfront that this isn't a true fluid simulation is a good prompt starting point if you want the assistant to suggest a real WebGL-based fluid shader instead for a more physically accurate (and more expensive) alternative.
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 "fluid cursor trail" effect in plain HTML, CSS, and JavaScript using only the Canvas 2D API — no external libraries, no WebGL, and no real fluid-dynamics solver. This should be a lightweight visual approximation of a fluid trail, not a Navier-Stokes simulation, and the code/comments should be honest about that distinction.
Requirements:
- A canvas panel that tracks mouse and touch movement within its bounds and spawns a stream of "blob" particles at the pointer's position as it moves, each carrying its own position, velocity, size, rotation/stretch, lifespan, and color.
- Derive each new blob's stretch factor and size from the pointer's recent velocity (distance moved since the last frame) so fast movement produces larger, more elongated blobs and slow movement produces smaller, rounder ones — apply the stretch via canvas rotate/scale transforms aligned to the direction of movement, not by drawing a pre-stretched sprite.
- Render each blob as a soft radial gradient that fades to transparent at its edge (not a hard-edged circle), and draw all blobs using an additive blend mode (globalCompositeOperation: 'lighter') so overlapping blobs visually brighten where they intersect.
- Implement the trail fade-out by painting a low-opacity, dark, full-canvas rectangle over the scene at the start of each animation frame (before drawing new blobs) rather than fully clearing the canvas or manually tracking per-blob alpha decay across a stored history array.
- Age out blobs by decrementing an internal lifespan value each frame and removing them from the active array once it reaches zero, and dampen each blob's own velocity over time so it drifts to a stop rather than sliding indefinitely.
- Support both mouse and touch input, and stop spawning new blobs when the pointer leaves the canvas.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
- 1Paste HTML, CSS, and JSA canvas panel and hint text render.
- 2Move the cursor over the panelA glowing, stretched blob trail follows it.
- 3Move quicklyBlobs stretch and shift hue with higher velocity.
- 4Hold stillThe trail fades out and stops growing.
- 5Leave the panelNew blobs stop spawning until the cursor re-enters.
- 6Resize the windowThe canvas adapts to the new panel size.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
No, and it's not trying to be. A genuine fluid simulation solves the Navier-Stokes equations across a velocity field, which is significantly more expensive to compute. This snippet approximates a fluid look using layered, velocity-stretched radial gradients and additive blending — a lightweight visual trick that looks fluid-ish without the underlying physics.
Two things together: ctx.globalCompositeOperation = 'lighter' adds overlapping colors rather than layering opaque shapes, so dense clusters of blobs near the cursor blow out brighter; and each blob is drawn as a radial gradient fading to transparent, giving it a soft, glowing edge instead of a hard circle outline.
Instead of clearing the canvas each frame, tick() paints a low-opacity dark rectangle over the entire canvas before drawing new blobs. That partial wash slightly dims everything already there on every frame, so older blobs visibly fade over several frames — a much cheaper approach than storing and re-rendering a growing history of past positions.
Blob size, stretch factor, and hue are all derived from the pointer's velocity between frames. Faster movement produces larger, more elongated blobs with a shifted hue, while slow, deliberate movement produces smaller, rounder, more static-colored blobs — that's what gives fast flicks and slow drags visually distinct trails.
Move the canvas setup, pointer listeners, and animation loop into a mount effect scoped to a canvas ref, and cancel the requestAnimationFrame loop plus remove event listeners in the cleanup function. Keep the blobs array and pointer state outside of component re-renders (in refs) so the animation loop doesn't get recreated on every render.