More Animations Snippets
Liquid Blob — Free HTML CSS JS SVG Morph Snippet
Morphing Liquid Blob · Animations · Plain HTML, CSS & JS · Live preview
What's included
Features
About this UI Snippet
Liquid Blob — SVG Path Morphing, sin/cos Noise per Vertex & rAF Loop

A liquid blob is an organic, constantly morphing shape that pulses and breathes. Used on AI product pages, creative agencies, and any interface communicating fluidity and organic intelligence — pair it with an aurora background, floating particles, or a gradient mesh hero.
The genBlob(t, amp) function places 8 equally-spaced points around a circle of radius 70. For each point, a noise value is calculated: amp * (Math.sin(t * 0.8 + i * 1.3) * 0.5 + Math.cos(t * 0.5 + i * 0.9) * 0.5). The t parameter increases each frame for continuous motion. The amp parameter controls deviation amplitude — higher values create more jagged morphing.
Points are converted to a smooth SVG cubic bezier path using a spline algorithm that calculates bezier control points through all vertices. requestAnimationFrame calls genBlob() every frame, updating the SVG path.
Hovering the blob toggles between normal amplitude (18) and expanded amplitude (30) via mouseenter/mouseleave listeners, creating an interactive morphing response.
SVG path morphing
The blob uses an SVG <path> element whose d attribute is animated between multiple organic shapes via SMIL animateValues or CSS @keyframes with custom properties. Each keyframe defines a different cubic bezier path with matching numbers of control points. The browser interpolates the path coordinates smoothly between keyframes, creating the organic morphing effect.
Alternative: CSS clip-path
The snippet can also use CSS clip-path: polygon() with animated coordinates to create a similar morphing blob on a div element rather than an SVG. This approach is simpler but produces slightly less smooth morphing than SVG path animation.
The blur and scale pulse
A CSS filter: blur() value that pulses slightly (0px to 2px) makes the blob edges feel soft and alive. Combined with scale animation (1.0 to 1.05), the blob breathes visually. Both filter and transform changes are GPU-composited properties — they do not trigger layout or paint recalculation.
Colour and gradient fill
The blob fill uses a radial gradient from a bright centre colour to a darker edge, creating a 3D sphere illusion. Animating the gradient coordinates (cx, cy values in the SVG radialGradient) can make the highlight follow a simulated light source as the blob morphs.
Use cases for the liquid blob
The blob works best as an ambient background element, not a foreground content container. Place it behind hero text with z-index: -1 and pointer-events: none. Multiple blobs at different sizes and positions (top-left, bottom-right) create the ambient painted background similar to the Gradient Mesh Hero snippet in this library.
Build with AI
Build, Understand, Optimize, and Extend It With AI
You do not have to unpack the trigonometry inside genBlob by staring at it alone. Paste this snippet's HTML, CSS, and JS into an AI coding assistant like Claude and ask it to explain exactly why each of the 8 points uses two different sine and cosine frequencies (0.8 and 0.5) with different phase offsets (i times 1.3 and i times 0.9), and how the resulting noise value gets turned into a smooth cubic bezier path rather than a faceted polygon. The same assistant can help optimize it, for example asking whether recalculating and setting a full path string every single animation frame could be throttled to every other frame without a visible quality loss, or whether the amp easing toward targetAmp is stable at very high amplitudes. It is also useful for extending the blob: ask it to drive the amplitude from live Web Audio frequency data, add a second layered blob at a phase offset for more depth, or make the hover-expand transition itself easing-driven rather than instant on state change. Treat the code less like a finished artifact and more like a starting point for a conversation.
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 "morphing liquid blob" in plain HTML, CSS, and JavaScript using an SVG path regenerated every animation frame with requestAnimationFrame — no canvas, no libraries.
Requirements:
- An SVG path element filled with a gradient, whose d attribute is entirely recomputed and reset on every frame rather than driven by CSS keyframes.
- A point-generation function that places a fixed number of points (e.g. 8) evenly around a circle using angle = (index / count) * Math.PI * 2, then perturbs each point's radius by a noise value combining at least two sine/cosine terms with different frequencies and different per-point phase offsets (based on the point's index), so no two points move identically.
- The noise's amplitude must be a tunable variable that eases toward a target amplitude value on every frame (e.g. amp += (targetAmp - amp) * 0.08) rather than snapping instantly, so hovering smoothly increases the wobble and un-hovering smoothly settles it back down.
- Convert the perturbed points into a smooth closed curve using cubic bezier "C" commands with control points derived from each point's neighbors (not straight "L" lines), so the shape reads as an organic blob rather than a faceted polygon.
- A continuously incrementing time variable must feed into the noise functions every frame so the blob never stops moving, even at rest amplitude.
- Wrap static, non-animated content (an icon and a label) inside the blob using absolute positioning so the content stays legible and centered while the SVG path morphs underneath it.
- The animation loop itself must be a self-scheduling requestAnimationFrame call, not a setInterval.Step by step
How to Use
- 1Watch and click the blobThe blob morphs continuously. Click it to toggle between normal and expanded amplitude for a click-to-burst effect.
- 2Change the morph amplitudeIn the JS panel, update the 25 (normal) and 45 (expanded) amplitude values in the animate() function.
- 3Change the animation speedUpdate the t increment value (currently +=0.015) in the animation loop to speed up or slow down the morphing.
- 4Change the blob colourUpdate the fill gradient colours on the SVG linearGradient in the HTML panel.
- 5Change the number of verticesUpdate const pts = 8 in the JS. More points (12-16) create smoother morphing; fewer (5-6) create more angular blobs.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for a React component, or "Tailwind" for a React + Tailwind version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
8 points are placed equally around a circle (i * 2π / 8). Each point's radius is perturbed by amp * (sin(t*0.8 + i*1.3)*0.5 + cos(t*0.5 + i*0.9)*0.5). Different frequencies (0.8, 0.5) and phases (i*1.3, i*0.9) per point index ensure no two points move identically, creating organic variation rather than uniform pulsing.
spline() converts the 8 perturbed points into a smooth cubic bezier curve. Without it, straight lines between adjacent points would create a faceted polygon, not an organic blob. The function calculates bezier control points that create smooth curves through all 8 vertices.
Increase the amplitude parameter: 25 (normal) to 40-50 for more dramatic morphing. Also try increasing the t increment per frame from 0.015 to 0.03 for faster motion. Be aware that very high amplitude (60+) can cause the blob to become concave (fold in on itself).
The blob uses an SVG linearGradient element in the defs. Update the stop-color values in the gradient definition in the HTML panel. You can also change the shape to use a radial gradient for a different look, or add a feGaussianBlur filter for a glowing effect.
Create multiple SVG elements each with their own path, gradient, and animation instance. Give each a different initial time offset: the first blob starts animate(t) at t=0, the second at t=100. They will be in different phases of their morphing cycles, creating organic variety.
Yes. Use useRef on the SVG path element and for the rAF handle. In useEffect, start the animation loop: const loop = () => { pathRef.current.setAttribute("d", genBlob(t, amp)); t += 0.015; rafRef.current = requestAnimationFrame(loop); }. Clean up in the return: cancelAnimationFrame(rafRef.current).