Liquid Morph
Three drops in violet, pink and cyan (80, 70 and 50 px) orbit the center of a 280 × 160 px stage, sticking together and pulling apart every 4 s: an SVG "goo" filter written in the HTML blurs the stage then sharpens the edges again, so two nearby drops weld through a bridge of matter before they touch — CSS, zero JavaScript, frozen mid-merge when the user reduces motion.
Updated
This effect is part of Effect.Labs — 840 vanilla effects, some free, some premium. Liquid has 31 effects, including 6 free. Explore the category →
3 usage examples



How it works
The root .fx-0408-root (#0a0a0f background, at least 300 px tall) holds two things. First, a 0 × 0 px <svg class="fx-0408-defs">, taken out of the flow, whose only job is to carry the <filter id="fx-0408-goo">. Then the stage .liquid-morph-stage, 280 × 160 px with no background, which gets filter: url(#fx-0408-goo) and centers three round .liquid-drop elements: violet #a855f7 at 80 px, pink #ec4899 at 70 px, cyan #06b6d4 at 50 px. Each has its own 4 s loop (ease-in-out): the violet one travels from −60 px to +20 px horizontally, the pink one from +60 px to −20 px, the cyan one from 40 px below the center to 20 px above, with a slight swell between ×0.95 and ×1.1. At the start of the loop, 45 px separate violet from pink; around 400 ms they meet; from 1 to 3 s all three overlap at the center, then each goes back its own way.
The filter is three lines long. feGaussianBlur stdDeviation="10" blurs the whole stage: each drop's edge turns into an opacity gradient, and between two nearby drops the two halos add up. feColorMatrix keeps the colors and rewrites opacity (last row 0 0 0 20 -9: opacity × 20 − 9): anything under 45% becomes transparent, anything above 50% becomes solid. The edge turns sharp again, and the area between two close drops where the combined halos pass the threshold becomes a solid bridge. feComposite operator="atop" finally redraws the original drops on top of that silhouette: colors stay crisp, only the bridge takes the blended tint of the blur. Measured on the horizontal axis 400 ms into the loop: 12 to 13 px of empty space between violet and pink without the filter, 0 px with it, identical in Chromium 151, Firefox 153 and WebKit 26. color-interpolation-filters="sRGB" keeps the blur, otherwise computed in linear light, from darkening the bridges.
Three choices hold the effect together. The stage has no background: the threshold would apply to it and turn it into a solid rectangle, leaving the drops no edge to weld. The filter lives in the HTML, with a prefixed id: url(#…) only looks inside the document, and Firefox stops drawing the stage altogether when the id cannot be found or the <svg> is display: none (measured: empty stage), while Chrome and Safari ignore the filter and show three flat disks. Finally, the filter has a cost: measured in Chromium with software rendering (no graphics card, the slowest case), it takes 0.87 ms per frame at the sold size versus 0.10 ms without it; the same stage scaled up four times (1,120 × 640 px, blur of 40) climbs to 15.7 ms per frame and halves the frame rate. The filter is made for a small stage, not for a full-screen background.
Accessibility
- Reduced motion: under
prefers-reduced-motion: reduce, a block scoped to.fx-0408-rootstops the three loops and places the drops at −38 px, +38 px and 34 px below the center: a still image where the three drops are welded together, so the merge reads without motion. Measured: 0% of pixels change over 500 ms in Chromium, Firefox and WebKit (3.6% without the preference). - Decorative content: the stage carries no text and no information. The filter's
<svg>is markedaria-hidden="true"andfocusable="false", and the drops are emptydivelements that screen readers skip. If the colors mean something (three scents, three ingredients), write it next to the stage, like the legend in the scenes above: do not put text inside the stage, the blur and threshold would make it unreadable. - Contrast: on the
#0a0a0fbackground, the violet drop measures 5.0:1, the pink one 5.6:1 and the cyan one 8.1:1, above the 3:1 threshold for graphics. On a light background, darken the drops: the filter does not change colors. - Pace: a 4 s loop, with no flashing and no sudden change in brightness; the motion stays inside a 280 × 160 px frame.
Browser compatibility
CSS (filter: url() on an HTML element, transform animations) + a three-primitive SVG filter written in the HTML, zero JavaScript, no dependencies. Measured in Chromium 151, Firefox 153 and WebKit 26: same merge (solid bridge 400 ms into the loop), same stop under reduced motion. Two copies of the effect on one page, sharing the id, work in all three (identical filters); change the id if you tune one copy differently. Cost measured in Chromium with software rendering: 0.87 ms per frame at 280 × 160 px, 15.7 ms scaled up ×4.
Without SVG filters on HTML (very old browsers), the stage shows three crisp disks crossing each other, with no bridge: the motion remains. If you move the <svg> block to another page or hide it with display: none, Firefox stops drawing the stage entirely: keep the 0 × 0 px svg in the same page as the stage.
The code
HTML structure to paste into your page (the CSS + JS come with your purchase):
<div class="fx-0408-root">
<svg class="fx-0408-defs" width="0" height="0" aria-hidden="true" focusable="false">
<filter id="fx-0408-goo" color-interpolation-filters="sRGB">
<feGaussianBlur … stdDeviation="10" />
<feColorMatrix … values="… 0 0 0 20 -9" />
<feComposite in="SourceGraphic" in2="goo" operator="atop" />
</filter>
</svg>
<div class="liquid-morph-stage">
<div class="liquid-drop liquid-drop-1"></div> … (3 drops)
</div>
</div>
One-time payment, no subscription — the code is yours forever
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
Merge strength (HTML — stdDeviation="10" on feGaussianBlur) |
10 | The wider the blur, the earlier the bridge forms. Measured 300 ms into the loop (26 px gap without the filter): 27 px of gap left at 6, 22 px at 10, 0 px at 16; but at 16 the drops round off into a soft mass and the small cyan one shrinks. Stay between 8 and 14 for drops this size. |
Threshold (HTML — last row of feColorMatrix, 0 0 0 20 -9) |
20 −9 (threshold around 45 to 50% opacity) | 18 -7 lowers the threshold: fatter drops and bridges (+10% measured area), earlier merge. 30 -14 gives drier edges and thinner bridges. Keep the second number at about half the first. |
Colors (CSS — background of .liquid-drop, -2, -3) |
#a855f7, #ec4899, #06b6d4 | The filter keeps colors as they are; only the bridge takes the blended tint of the blur. Neighboring colors read as one substance, opposite colors show the mixing better. |
Speed (CSS — 4s in the three animation declarations) |
4 s | Change all three durations together to keep the drops in phase. 6 s gives a thicker substance, 2.5 s a bubbling one. The same negative animation-delay on all three shifts where the loop starts. |
Stage and drop size (CSS — .liquid-morph-stage, .liquid-drop-1/2/3, keyframes) |
280 × 160 px; drops of 80, 70 and 50 px; paths of 60 and 40 px | To scale up, multiply the stage, the drops, the keyframe distances and the stdDeviation together. The filter's cost follows the area: at ×4 in width and height, the frame rate halves with software rendering. |
Background (CSS — .fx-0408-root) |
#0a0a0f | Change the root's background or remove it to set the drops on your page. Never give .liquid-morph-stage a background: the threshold would turn it into a solid rectangle. |
FAQ
filter: url(#fx-0408-goo) points to an element of the document itself: the filter must sit in the page that holds the stage. Keep the <svg class="fx-0408-defs"> block as is, at 0 × 0 px. Measured: if the id cannot be found or the svg is set to display: none, Firefox stops drawing the stage entirely, while Chrome and Safari show three flat disks with no bridge. Two copies of the effect on the same page can share the id (identical filters); if you tune one differently, give it its own id and its own filter: url().