Organic Blob
Organic background animated by CSS border-radius morphing and SVG feTurbulence grain — a living blob with zero JavaScript.
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



How it works
The shape is a plain <div> given a linear gradient background (135°, violet → pink by default) and driven by the CSS animation ngBlobMorph (8 s, ease-in-out, infinite). It cycles through four border-radius configurations with eight independent values each — the classic CSS blob morphing technique. Each keyframe transition produces an asymmetric expansion/contraction that mimics a soft organism in motion, with no JavaScript involved.
A grain texture is layered on top via the ::after pseudo-element: an SVG encoded as a Data URI applies a <feTurbulence type="fractalNoise" baseFrequency="0.7" numOctaves="3"> filter to a full-size rectangle, rendered with mix-blend-mode: overlay at opacity: 0.15. The grain appears embedded in the blob surface without masking the underlying gradient.
The CSS also references an external SVG filter via filter: url(#ng-blob-filter). This filter — declared once in the page inside an invisible <svg> tag — chains an animated <feTurbulence> (transitioning baseFrequency via <animate>) and a <feDisplacementMap scale="22"> that ripples the blob's edges in real time, adding a second layer of organic deformation beyond the CSS morphing.
The effect is entirely driven by the browser's CSS engine and SVG pipeline: zero JavaScript, zero dependencies, zero requestAnimationFrame loop. Performance remains constant regardless of the host page's JS load.
Accessibility
- prefers-reduced-motion not handled: the
ngBlobMorphanimation (8 s loop) runs regardless of the system preference. For accessible use, wrap theanimationrule inside@media (prefers-reduced-motion: no-preference) { .ng-blob { animation: … } }. - The effect is purely decorative — add
aria-hidden="true"to.ng-blob-sceneand the SVG filter tag so screen readers ignore them entirely. - No keyboard interaction: the blob never receives focus and responds to no events. The host page's keyboard navigation is unaffected.
- Overlaid text: the multicolor gradient can create low-contrast zones. Use a semi-opaque scrim (
rgba(0,0,0,.45)) between the blob and text to ensure WCAG AA compliance (ratio ≥ 4.5:1). - The effect is 100% CSS/SVG: no script executes — users who block JavaScript see the blob working normally.
Browser compatibility
Requires multi-value CSS border-radius, CSS3 animations, inline SVG, and feDisplacementMap — available in all modern browsers since 2020.
If <code>feDisplacementMap</code> is not supported (extremely rare), the blob displays with CSS morphing and grain only — still animated, just without the SVG edge ripple. No errors, no broken dependencies.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<!-- SVG filter — place once in the page (out of flow) -->
<svg aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<defs>
<filter id="ng-blob-filter" x="-20%" y="-20%" width="140%" height="140%">
<feTurbulence type="fractalNoise" baseFrequency="0.012 0.008" numOctaves="3" result="noise">
<animate attributeName="baseFrequency"
values="0.012 0.008;0.016 0.012;0.012 0.008"
dur="8s" repeatCount="indefinite"/>
</feTurbulence>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="22"
xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs>
</svg>
<!-- Blob -->
<div class="ng-blob-scene" aria-hidden="true">
<div class="ng-blob"></div>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| background (CSS property on .ng-blob) | linear-gradient(135deg, #8b5cf6, #ec4899) | Blob colors — replace the two hex values with your brand palette. Accepts any CSS format: radial-gradient, solid color, etc. |
| width / height (.ng-blob) | 160px × 160px | Blob dimensions. Scale up to 300–400 px for a full-screen background, down to 80–120 px for a composition element. |
| animation-duration (animation shorthand on .ng-blob) | 8s | Morphing speed. 5 s = fast and organic, 14 s = slow and hypnotic. |
| opacity (::after) | 0.15 | Grain surface intensity. 0.05 = nearly invisible, 0.35 = pronounced grain with a paper texture feel. |
| baseFrequency (SVG grain inside .ng-blob::after) | 0.7 | feTurbulence grain density. Low values (0.3) = coarse grain, high values (1.2) = fine film-like grain. |
| scale (feDisplacementMap inside #ng-blob-filter) | 22 | SVG edge distortion intensity. 0 = smooth CSS-only edges, 40 = heavily rippled edges. |
| border-radius (keyframes ngBlobMorph — 4 steps) | see keyframes | Morphing shape — edit the 4 sets of border-radius values for rounder, more angular, or more asymmetric motion. |
FAQ
#ng-blob-filter must be declared once in your page, inside an <svg aria-hidden="true" style="position:absolute;width:0;height:0"> tag. It holds an animated <feTurbulence> and a <feDisplacementMap> that ripple the blob's edges. Without it, filter: url(#ng-blob-filter) is simply ignored and the blob animates with CSS morphing alone — no error, just without the SVG edge distortion..ng-blob div as many times as needed. Add a negative animation-delay (e.g. -3s) to each instance to start mid-loop rather than waiting, and vary animation-duration so shapes don't synchronize. The SVG filter #ng-blob-filter is shared — a single declaration is enough for all instances.will-change: border-radius, filter to .ng-blob so the browser allocates a dedicated composite layer — CSS morphing and the SVG filter then run on the GPU without repainting the rest of the page. On mobile, reduce the feDisplacementMap scale to 12–15 and the grain baseFrequency to 0.5 to preserve battery life.