← Back to gallery
SVG

Gooey Blob Metaball Filter

True metaball merging — multiple SVG circles travel along independent orbits and visually fuse into one gooey shape via the same blur+threshold filter. Three orbit programs: anchor + drifters, dual binary, and a quiet single satellite.

metaballsvg-filterfeGaussianBlurfeColorMatrixorbitcircle-primitiveprefers-reduced-motion

Orbital metaball · blur + alpha threshold

Gooey Blob Metaball Filter

Authored circles orbit a central anchor and fuse through feGaussianBlur + feColorMatrix into a single fluid silhouette. The filter coefficients stay static; only the orbit rotation animates. One polished expression of the metaball technique — anchor + four orbiting satellites with phase-staggered bridges.

Anchor + 4 orbiting satellites · warm palette

Liquid Pulse

Four satellites orbit the anchor on a true circular path (each satellite is wrapped in its own <g> so transform-origin pivots on the anchor center, not on the satellite). One rotate(1turn) keyframe sweeps the orbit; animation-delay offsets each satellite by 90° so they sit at four equally-spaced positions on the circle. A separate ease-in-out scale keyframe ripples the bridges as they form.

  • 4 satellites
  • 90° offset
  • bridge ripple

Metaball inspector

Liquid Pulse

  • 4 satellites
  • 90° offset
  • bridge ripple

Four satellites orbit the anchor on a true circular path (each satellite is wrapped in its own <g> so transform-origin pivots on the anchor center, not on the satellite). One rotate(1turn) keyframe sweeps the orbit; animation-delay offsets each satellite by 90° so they sit at four equally-spaced positions on the circle. A separate ease-in-out scale keyframe ripples the bridges as they form.

Helped you ship something? 🐟 Send my cat a churu

/* Orbital metaball: 4 satellites wrapped in <g> elements
   share one transform-origin (anchor center). One rotate(1turn) keyframe
   sweeps a true circle; animation-delay offsets each satellite by 1/4
   of the cycle so they sit at 4 equally-spaced rotational positions. */
.metaball-anchor {
  transform-box: fill-box;
  transform-origin: center;
  animation: metaball-breath 4.50s ease-in-out infinite;
}
.metaball-orbit {
  /* transform-box: view-box lets transform-origin use viewBox coords —
     point at anchor center so rotate(1turn) sweeps a true circle. */
  transform-box: view-box;
  transform-origin: 100px 82px;
  animation: metaball-orbit-cw 4.50s linear infinite;
}
.metaball-orbit:nth-of-type(1) { animation-delay: calc(4.50s * -0.0000); }
.metaball-orbit:nth-of-type(2) { animation-delay: calc(4.50s * -0.2500); }
.metaball-orbit:nth-of-type(3) { animation-delay: calc(4.50s * -0.5000); }
.metaball-orbit:nth-of-type(4) { animation-delay: calc(4.50s * -0.7500); }
.metaball-sat {
  transform-box: fill-box;
  transform-origin: center;
  animation: metaball-sat-bridge 4.50s ease-in-out infinite;
}

@keyframes metaball-breath {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(calc(1 + 0.04 * 1.00)); }
}
@keyframes metaball-orbit-cw {
  to { transform: rotate(1turn); }
}
@keyframes metaball-sat-bridge {
  0%, 100% { transform: scale(1); }
  50% { transform: scale(calc(1 + 0.18 * 1.00)); }
}

@media (prefers-reduced-motion: reduce) {
  .metaball-anchor, .metaball-orbit, .metaball-sat { animation: none; }
}

How to make this

A gooey metaball filter keeps SVG blur and alpha threshold values static, then animates circle distance with transform so overlapping alpha fields merge into one fluid shape.

html
<svg class="metaball-filter" viewBox="0 0 200 160" aria-hidden="true">  <defs>    <linearGradient id="metaball-filter-fill" x1="40" x2="160" y1="40" y2="120">      <stop offset="0%" stop-color="#c4b5fd" />      <stop offset="100%" stop-color="#2dd4bf" />    </linearGradient>    <filter id="metaball-filter-goo"8      x="-25%" y="-25%" width="150%" height="150%">9      <feGaussianBlur in="SourceGraphic" stdDeviation="9" result="blur" />10      <feColorMatrix in="blur" mode="matrix"        values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 22 -10"        result="goo" />      <feBlend in="SourceGraphic" in2="goo" />    </filter>  </defs>16  <g filter="url(#metaball-filter-goo)" fill="url(#metaball-filter-fill)">    <ellipse class="metaball-filter__anchor" cx="100" cy="82" rx="42" ry="34" />    <g class="metaball-filter__orbit metaball-filter__orbit--one">      <circle class="metaball-filter__sat" cx="42" cy="44" r="14" />    </g>    <g class="metaball-filter__orbit metaball-filter__orbit--two">      <circle class="metaball-filter__sat" cx="158" cy="42" r="12" />    </g>    <g class="metaball-filter__orbit metaball-filter__orbit--three">      <circle class="metaball-filter__sat" cx="160" cy="120" r="14" />    </g>  </g></svg> <style>.metaball-filter {  width: min(100%, 260px);  overflow: visible;  filter: drop-shadow(0 0 18px rgba(196, 181, 253, .18));}.metaball-filter__anchor {  transform-box: fill-box;  transform-origin: center;  animation: metaball-filter-breath 5.4s ease-in-out infinite;}.metaball-filter__orbit {42  transform-box: view-box;  transform-origin: 100px 82px;  animation: metaball-filter-orbit 5.4s linear infinite;}.metaball-filter__orbit--two { animation-delay: -1.35s; }.metaball-filter__orbit--three { animation-delay: -2.7s; }.metaball-filter__sat {  transform-box: fill-box;  transform-origin: center;  animation: metaball-filter-bridge 5.4s ease-in-out infinite;}@keyframes metaball-filter-breath {  0%, 100% { transform: scale(1); }  50% { transform: scale(1.04); }}@keyframes metaball-filter-orbit {  to { transform: rotate(1turn); }}@keyframes metaball-filter-bridge {  0%, 100% { transform: scale(1); }  50% { transform: scale(1.18); }}64@media (prefers-reduced-motion: reduce) {  .metaball-filter__anchor,  .metaball-filter__orbit,  .metaball-filter__sat { animation: none; }}</style>

Annotated snippet

  1. Line 8The filter box is larger than the artwork bounds so the blurred alpha can expand before thresholding. Tight filter regions cut off the fluid bridge at the edges.
    PitfallWhat is the difference between a gooey blob and a metaball cluster?

    The filter recipe is similar, but the design intent differs. A metaball cluster relies on distance between multiple masses: as circles drift, pulse, or orbit near the anchor, their blurred alpha fields bridge and separate. The motion choreography is part of the effect.

  2. Line 9feGaussianBlur softens every circle into an alpha field. The metaball connection appears when neighboring blurred fields overlap.
    PitfallShould I animate feGaussianBlur for a metaball effect?

    Usually no. Keep stdDeviation and the color matrix static, then animate transform on circles or groups. Changing blur radius every frame is more expensive and makes the threshold edge harder to tune consistently.

  3. Line 10feColorMatrix turns the soft overlap back into a crisp silhouette by multiplying alpha and subtracting a threshold. Keep these coefficients static for predictable rendering.
  4. Line 16The filter is applied to the whole group, not to individual circles. Metaballs need the anchor and satellites to be processed together so their alpha fields can merge.

    Only the filter target changes here. Applied per circle, each blob is blurred and thresholded in isolation — the satellites stay as separate dots. Applied to the parent group, the alpha fields blur and threshold together, so the satellites stretch into the anchor as one fused blob.

    PitfallWhat is the difference between a gooey blob and a metaball cluster?

    The filter recipe is similar, but the design intent differs. A metaball cluster relies on distance between multiple masses: as circles drift, pulse, or orbit near the anchor, their blurred alpha fields bridge and separate. The motion choreography is part of the effect.

  5. Line 42transform-box: view-box lets transform-origin use the SVG coordinate system. Pointing it at the anchor center makes the satellite wrapper orbit around the pool instead of spinning around itself.

    Both sides run the EXACT same animation: rotate(1turn) at 2.4s linear infinite. The only difference is the orbit wrapper's transform-box + transform-origin — fill-box: center makes the satellite spin around its own midpoint, view-box at anchor coordinates makes it orbit the anchor center instead.

    PitfallWhy does my orbiting metaball spin in place?

    The orbit wrapper is probably using transform-box: fill-box with transform-origin: center. For a true orbit around the anchor, wrap the satellite in a group, set transform-box: view-box, and set transform-origin to the anchor center in SVG coordinates.

  6. Line 64Reduced motion stops anchor breathing, orbit rotation, and satellite scale together. The static cluster still demonstrates the filter merge without continuous motion.
    PitfallHow should metaball filters handle accessibility and reduced motion?

    Treat decorative blobs as aria-hidden and provide any meaning as nearby text. Under prefers-reduced-motion, stop all orbit, drift, and scale animations while leaving the static filtered cluster visible.

Other pitfalls

Are SVG metaball filters supported across browsers?
Modern Chromium, Firefox, and Safari support feGaussianBlur, feColorMatrix, and CSS transforms on SVG elements, but transform-box behavior is worth testing. Keep ids unique and inline the filter near the SVG to avoid URL reference issues.

Notes

Overview

True metaball merging via SVG primitives (real <circle> + <ellipse> elements) that travel on independent orbits and visually fuse into one shape via the gooey blur + colorMatrix threshold filter. Three orbit programs ship: anchor + dual drifters, binary dance, and a single quiet satellite.

When to use it

Reach for metaball when you need genuine fluid merging between primitives (loading dots that absorb into each other, droplets coalescing, navigation indicators that morph between states). Skip it for static-content decoration; the constant motion in peripheral vision is exhausting outside the active focus area.

How it works

Real SVG <circle> and <ellipse> primitives travel independently via CSS or SMIL transform animations. The parent <g> applies a two-stage filter via filter="url(#gooey)": <feGaussianBlur stdDeviation="10" /> blurs the primitives into soft clouds; then <feColorMatrix type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7" /> applies a threshold to the alpha channel — alpha values above the threshold snap to opaque, below snap to transparent. The result is soft clouds that merge wherever their blurred edges overlap.

Production gotchas

The gooey filter region must extend past the visible bounds of the primitives or the blur clips at the edges and the merge looks abrupt. Set filter with explicit x="-50%" y="-50%" width="200%" height="200%" on the filter definition. The colorMatrix threshold values (18, -7) are tuned for stdDeviation="10"; adjust both together. Safari iOS occasionally exhibits banding at the threshold edge — raise the blur slightly to smooth it. Each animated primitive triggers a filter re-rasterization, so keep the count under ~6 for smooth playback.

Accessibility

The merging blobs are decorative — mark the wrapping SVG with role="presentation" or aria-hidden="true". Under prefers-reduced-motion: reduce stop the primitive orbits and pin them at rest positions so the merge becomes static. Verify the filtered output maintains sufficient contrast against the background; the threshold operation can produce edges that flicker between high and low contrast as primitives drift.

References

Implementation depth

Metaballs need enough independent motion to prove they are separate primitives, then enough blur overlap to merge visually. The SVG circles remain simple; the filter creates the fluid surface.

Filter bounds and primitive count decide performance. Expand the filter region so blur is not clipped, keep the animated circle count modest, and freeze orbits under reduced motion instead of removing the composed shape.