Glowing Metaballs
An interactive WebGL experiment with metaballs: soft, glowing blobs that drift across the canvas, merging and separating as they move. The cursor acts as an attractor — bring the pointer over the canvas and the orbs will gently follow it.
The effect respects the prefers-reduced-motion media query: if reduced motion is enabled in your system settings, the animation is paused and the scene renders as a static frame instead.
SEO Expert Adds More Words
Mobile Version Almost Considered
Testimonial Section Testifies to Nothing
Instructions
count — Number of metaballs
Controls how many metaballs (the liquid blob shapes) are rendered. In the example, it's set to 3. The value is clamped between 1 and maxBalls. Special behavior: if you don't provide this option at all (leaving it null or undefined), the count is randomly generated between 3 and 12 each time you regenerate — that's what the countProvided check does.
size — Ball size multiplier
Scales the radius of every ball. A value of 1.0 is the default; 1.05 in the example makes the blobs slightly larger. Higher numbers mean bigger, fatter blobs; lower numbers make them smaller and more droplet-like.
speed — Animation speed
A time multiplier for the animation. At 1.0 the blobs move at normal speed. Setting it to 2.0 makes everything move twice as fast, 0.5 makes it slow and dreamy, and 0 freezes the motion entirely (which can also disable the animation loop when reduced motion is respected).
threshold — Field merge threshold
Determines how "sticky" the field needs to be before it's rendered as a solid blob. It's the iso-surface cutoff in the shader. Lower values make the balls merge into gooey connected shapes more easily; higher values keep the blobs more separated and distinct. The example uses 0.75, making the balls merge somewhat but remain identifiable.
glow — Glow intensity
Controls how bright and far the halo/glow around the blobs appears. At 0.65 (default) there's a soft luminous aura; higher values create a stronger bloom-like effect, while 0 gives crisp, unlit edges.
noise — Organic distortion
Adds a layer of fractal (fBm) noise to the field, making the blob edges wobbly and organic instead of perfectly smooth. It also subtly perturbs the background. 0.0 (the default and the example value) keeps the shapes clean; small values like 0.2–0.5 give a liquid, lava-lamp feel.
rgbShift — Chromatic aberration
When greater than ~0.005, the shader renders the scene three times (offset for red, green, and blue channels) and combines them, creating a lens/RGB fringing effect. The amount scales with distance from the center, so it's stronger at the edges. The example uses 2.00, which is fairly pronounced; 0 disables it for a cleaner look.
mouse — Mouse interaction
A boolean. When true (default), the blobs are gently attracted toward the cursor position, and there's also a small extra field bump at the pointer. When false, the balls ignore the pointer entirely. The attraction strength is smoothed, so it eases in and out rather than snapping.
hue — Base color hue
Sets the base color of the palette as a hue value from 0 to 1 (on the HSL color wheel: 0 = red, 0.33 = green, 0.58 ≈ blue, 0.675 ≈ blue-purple in the example). If left as null, a random hue is generated each time the scene regenerates. Each ball gets a slight hue jitter on top of this base.
saturation — Color saturation
Clamps the vibrancy of the colors from 0 (grayscale) to 1 (fully saturated). Default is 0.85; the example uses 0.74 for slightly softer colors. Individual balls also get small saturation jitter so they aren't all identical.
scheme — Color distribution scheme
An integer 0–3 that determines how the per-ball hues are spread across the palette:
- 0 — Analogous: each ball shifts slightly from the base hue (+0.045 per ball), producing a smooth gradient family.
- 1 — Alternating: balls alternate between the base hue and its complement (base + 0.5), giving a two-tone contrast.
- 2 — Triadic: hues cycle through three points on the color wheel (0, 0.333, 0.666), producing three distinct color groups.
- 3 — Monochromatic (used in the example): all balls share the same base hue, varying only slightly via per-ball jitter — a single-hue look.
If left null, one is picked at random on generation.
maxBalls — Shader capacity limit
The maximum number of ball slots compiled into the shader (via #define MAX_BALLS). Default is 16. This is a performance cap: the fragment shader loops over this many balls regardless of count (unused ones get radius 0 and are skipped). Changing it requires shader recompilation, so it should be set at creation time and not changed later.
maxDpr — Maximum device pixel ratio
Caps the resolution multiplier used for the canvas backing store. Default is 2. On high-DPI (Retina) screens, the device pixel ratio could be 3 or more — this option limits rendering resolution to save GPU power. Lower it (e.g., to 1) on weaker devices for better performance at the cost of sharpness.
respectReducedMotion — Accessibility flag
A boolean (default true). When enabled, the animation checks the user's OS/browser preference for "reduced motion" (prefers-reduced-motion: reduce). If the user has requested less motion, the animation loop doesn't run — a single static frame is rendered instead. It also listens for changes to this preference at runtime and starts/stops accordingly. Set to false if you want the animation to always run regardless.
autoGenerate — Randomized initial setup
A boolean (default true). When true (or when a seed is provided), the scene is "generated" at startup: ball positions, sizes, animation frequencies, phases, and color jitters are randomized. If false and no seed is given, only the colors are computed and balls stay at their default (center) positions.
seed — Deterministic randomness
An integer seed for the pseudo-random generator (mulberry32). Providing a seed makes the entire random layout — ball positions, motion patterns, and (if hue/scheme are null) the color choices — fully reproducible: the same seed always produces the exact same scene. The example uses 1115219120 to lock in a specific look. If null, a random seed is chosen each run. You can retrieve the current seed later via getSeed() and replay it with regenerate(seed).
Bonus: ignoreSelector (undocumented in defaults)
Not in the default object, but the code supports opts.ignoreSelector — a CSS selector string for elements that should "block" mouse interaction. If the pointer is over an element matching that selector (e.g., a button or text overlay sitting on top of the canvas), the metaballs ignore the pointer as if it were outside the canvas. Useful for keeping blobs from following the cursor over UI elements.
How they interact
countandmaxBallswork together: count is how many are visible, maxBalls is the compiled upper limit.hue+saturation+schemedefine the palette;seedcan override hue/scheme randomly if they'renull.threshold+glow+noiseshape the visual character of the field.size,speed, andmouseaffect motion and scale at runtime (all changeable viasetSize,setSpeed, etc. after creation).maxDpr,maxBalls, andrespectReducedMotionare primarily performance/accessibility controls.