Backgrounds✨ Premium

Ink Bloom

Ink blooming in water: a plain radial gradient, pushed through a feDisplacementMap driven by fractal turbulence, becomes a stain with ragged edges that grows and breathes. Six different seeds guarantee no two blooms ever look alike.

feTurbulencefeDisplacementMapClick

You'll get access to the interactive demo with a free account.

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Backgrounds has 50 effects, including 6 free. Explore the category →

3 usage examples

🎈

① Page for a publisher, literary review or writing workshop

WhenThe world is paper and ink, and the page needs a living gesture rather than a frozen texture.
WhyBlooming ink is the founding gesture of handwriting. In the paper palette with multiply blending, the stain behaves like real pigment on fibre: it darkens the ground instead of sitting on top of it.
SettingsUse the paper palette, a moderate displacement scale (around 45) for supple rather than ragged edges, and keep the 4 pre-bloomed stains: they give a composition the moment the page loads.
🎊

② Reaction or voting surface on a community page

WhenYou want a click to leave a persistent, personal trace, instead of an animation that vanishes at once.
WhyEvery click lays down a unique stain — position, size between 28 and 58 % of the width, turbulence seed, centre offset: the combination does not recur. Visitors see their own mark, and the panel becomes a collective trace.
SettingsRaise the pool cap to 20 for richer accumulation, and lower the core opacity to 60 % so stains overlap without going opaque.

③ Transition between two sections, or content reveal

WhenMoving from one part to the next calls for a fade less mechanical than linear opacity.
WhyAn ink bloom has a natural dynamic — fast at first, then slowing — that reads as organic. Used as a mask, it reveals content through a shape that is anything but geometric.
SettingsTrigger a single very large bloom (past 100 % of the width) and use it as a mask-image on the section being revealed.

How it works

Each bloom starts as a four-stop radial gradient — core at 80 % opacity, ring at 55 %, fade at 18 %, then transparent. Those values mimic how pigment concentrates at the centre of a watercolour pool and lightens toward the rim. The gradient centre is randomly offset, which avoids the look of a perfect target. That gradient passes through a feDisplacementMap whose map is a feTurbulence in fractalNoise mode, 3 octaves. The displacement (scale 62) twists the gradient's outline: smooth edges become fingering, exactly like the front of one liquid pushing into another. Six identical filters with different seeds run in parallel, and each new bloom draws one at random. The animation then oscillates the baseFrequency around a base value memorised per filter — never by reading and rewriting the current value, which would turn the frequency into a random walk over minutes. The fractal edges therefore breathe in place, without ever running away.

Accessibility

  • The trigger is a real <button> covering the panel, with an explicit aria-label: the effect is keyboard-operable — Enter or Space blooms a stain at the centre — not mouse-only.
  • Under prefers-reduced-motion: reduce, blooms appear with a simple fade, with no growth phase and no edge breathing. The effect keeps its meaning (a stain appears where you click) while dropping the animation.
  • Two palettes ship: one in multiply blending for a light paper ground, one in screen for a dark one. Picking the right pair avoids stains that turn dirty grey through wrong compositing.
  • The pool is capped at 14 blooms; beyond that the oldest are removed. That bounds how many SVG filters run at once, which protects both performance and the panel's legibility.

Browser compatibility

SVG feTurbulence and feDisplacementMap filters, CSS radial gradients, mix-blend-mode, plus requestAnimationFrame and matchMedia. No library, no canvas.

Chrome 80+✓ Full
Edge 80+✓ Full
Firefox 75+✓ Full
Safari 14+✓ Full
Safari iOS 14+✓ Full — cap the pool at 8 blooms on mobile
Chrome Android 80+✓ Full

Without JavaScript the opening composition is not laid down and the panel stays empty: give .ink-scene a background (paper or ink). If SVG filters are unsupported, blooms render as plain radial gradients — less organic, but perfectly presentable.

The code

HTML structure to paste into your page (CSS + JS available with a premium account):

index.html — structure
<!-- The 6 displacement filters live in a zero-sized <svg>,
     placed anywhere in the page (see the full code). -->

<div class="ink-scene" id="inkScene">
  <!-- Isolated layer: this is what receives the blooms -->
  <div class="ink-canvas" id="inkCanvas"></div>

  <!-- Full-panel button: click = bloom at the cursor, Enter/Space = bloom at centre.
       A button, not a div: the effect stays keyboard-operable. -->
  <button class="ink-trigger" id="inkTrigger"
          aria-label="Bloom a drop of ink — click to place, Enter to centre">
  </button>
</div>
🔒 Unlock the full code — from €2.99 the first month

Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
feDisplacementMap scale 62 The primary control. At 20 the edges barely ripple, at 62 they form real fingering, past 100 the stain breaks apart into separate filaments.
Turbulence baseFrequency 0.066 How finely the outline is cut. A low frequency gives a few large lobes; a high one, tight lace that reads more like blotting paper than water.
numOctaves 3 Richness of the fractal detail. 1 gives a smooth undulating outline, 4 or 5 very irregular edges — at the cost of a markedly heavier filter.
Gradient stops 80 % / 55 % / 18 % / transparent The pigment's density profile. Bringing the stops together gives a crisp-edged stain; spreading them, a diffuse wash that fades out gradually.
Bloom size 28 to 58 % of the width Range drawn at random on each bloom. Narrowing it gives a regular composition; widening it, a mix of large pools and small drops.
Pool cap 14 blooms How many stains coexist before the oldest are removed. It is also the performance guard: each stain keeps an SVG filter alive.
Palette and blend mode paper (multiply) / dark (screen) multiply for dark ink on a light ground, screen for luminous ink on a dark one. Mismatching the pair gives dull stains.

FAQ

Because a feTurbulence with the same seed always produces exactly the same noise. With a single filter, every bloom would share a rigorously identical outline up to scale — and the eye spots that repetition immediately. Six distinct seeds are enough to break the impression: short of comparing two stains side by side, you never notice they come from a finite catalogue.
It is a classic mistake, and one hit here. Reading the current baseFrequency and adding a small variation each frame turns the oscillation into a random walk: after a few minutes the frequency has drifted far from its original value and the stain no longer looks like anything. So the code memorises each filter's base and always computes base + oscillation, never current + delta.
Each bloom keeps an SVG filter live, recomputed every frame while the edges breathe. On desktop, 14 is comfortable and 25 remains workable. On mobile, come down to 8: that is the point where per-frame cost becomes negligible again. If you need more stains, stop animating the older ones — a frozen stain costs almost nothing.