animation · css
Three ways to animate a blob
CSS keyframes on the d property, an SMIL animate element, or a GSAP tween. What each one needs, which browsers run it, and the code for all three.
Published September 18, 2026
A blob that morphs is one path element being redrawn between two outlines. There are three practical ways to drive that redraw in a browser, and they differ less in what they look like than in where they run and who controls them.
All three examples below morph between the same pair of shapes. Here is the first, taken straight out of the generator:
M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z
And here is the second, which is the same path with a few coordinates nudged:
M430,92Q486,176,452,256Q418,336,344,390Q270,444,196,414Q122,384,92,308Q62,232,108,164Q154,96,226,70Q298,44,358,64Q430,92,430,92Z
The rule that governs all three
Both strings have one M, eight Q commands and one Z, in that order. That is not decoration, it is the requirement. Interpolation pairs the numbers in one path with the numbers in the other, so the two paths must have the same commands in the same sequence. MDN states the condition for the CSS d property plainly: when animating, both paths must have the same number of data points.
This is why the animated blob generator locks every frame to the node count of the first one. Complexity stays put, the seed changes, and the paths stay compatible. If you write your own frames by hand, copy a path and edit numbers rather than drawing a new shape.
CSS keyframes on the d property
The d CSS property sets the path drawn by an SVG <path> element and overrides the element’s d attribute. It takes a path() function, and it is animatable.
<svg viewBox="0 0 500 500" width="320" height="320" aria-hidden="true">
<path class="blob" fill="#1e8fe5" d="M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z" />
</svg>
@keyframes blob-morph {
0%,
100% {
d: path("M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z");
}
50% {
d: path("M430,92Q486,176,452,256Q418,336,344,390Q270,444,196,414Q122,384,92,308Q62,232,108,164Q154,96,226,70Q298,44,358,64Q430,92,430,92Z");
}
}
.blob {
animation: blob-morph 12s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@media (prefers-reduced-motion: reduce) {
.blob {
animation: none;
}
}
Browser support. MDN labels the d property as limited availability, with a note that it is not Baseline because it does not work in some of the most widely used browsers. Caniuse puts numbers on that: Chrome from 52, Edge from 79, Firefox from 97, Opera from 60, Samsung Internet from 6.2, and Safari unsupported on both desktop and iOS, for roughly 81 percent of tracked global traffic.
Use it when the blob is decoration in an ordinary web page, you want no JavaScript at all, and a still shape in Safari is an acceptable outcome. For a background blob it usually is. For anything a user is meant to notice, it is not.
SMIL: an animate element inside the SVG
SMIL keeps the animation in the file. An <animate> element inside the path animates the d attribute through a list of values.
<svg viewBox="0 0 500 500" width="320" height="320" aria-hidden="true">
<path fill="#1e8fe5" d="M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z">
<animate
attributeName="d"
dur="12s"
repeatCount="indefinite"
calcMode="spline"
keyTimes="0;0.5;1"
keySplines="0.4 0 0.2 1; 0.4 0 0.2 1"
values="M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z;
M430,92Q486,176,452,256Q418,336,344,390Q270,444,196,414Q122,384,92,308Q62,232,108,164Q154,96,226,70Q298,44,358,64Q430,92,430,92Z;
M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z" />
</path>
</svg>
A few mechanics worth knowing. values is a semicolon-separated list and the last entry repeats the first so the loop closes without a jump. keyTimes gives each value its position in the duration and must have the same number of entries as values. keySplines needs calcMode="spline" and one spline fewer than there are values, which is why two appear for three shapes. Drop calcMode and keySplines and you get the SMIL default, which is a linear pace.
Browser support. MDN records the <animate> element as Baseline widely available, working across browsers since January 2020. Caniuse puts SVG SMIL animation at about 97 percent of tracked global traffic, with Safari supported from version 6 and iOS Safari from version 6; Opera Mini and Internet Explorer are the gaps.
Use it when the animation has to survive being detached from your page. Because the motion is inside the file, an SVG exported this way keeps running inside an img tag, a CSS background, or opened on its own. That makes it the right answer for an asset you hand to someone else. The cost is control: SMIL runs on its own clock, and starting, pausing or reversing it means calling element methods rather than driving a timeline.
GSAP: tweening the attribute
GSAP animates the d attribute through its attribute plugin. Because the two strings share a command structure, the library interpolates the numbers between them.
import gsap from "gsap";
const shapeA =
"M413,84.5Q476,169,447,249.5Q418,330,349,383Q280,436,204,412Q128,388,96,314Q64,240,105,169.5Q146,99,220,74.5Q294,50,353.5,67.25Q413,84.5,413,84.5Z";
const shapeB =
"M430,92Q486,176,452,256Q418,336,344,390Q270,444,196,414Q122,384,92,308Q62,232,108,164Q154,96,226,70Q298,44,358,64Q430,92,430,92Z";
const path = document.querySelector("#blob path");
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)");
if (path && !reduced.matches) {
gsap.to(path, {
attr: { d: shapeB },
duration: 6,
ease: "power2.inOut",
repeat: -1,
yoyo: true,
});
}
yoyo: true with repeat: -1 plays the tween forward and back forever, so six seconds each way gives the same twelve-second loop as the other two examples. For more than two frames, chain the shapes on a timeline instead.
Browser support. GSAP is a script, so it runs wherever your other JavaScript runs, Safari included. Its pricing page states that the whole library, plugin set included, is now available to everyone at no cost, which means the dedicated morphing plugin is also an option when two paths do not share a node count and the straight attribute tween will not work.
Use it when the blob belongs to a sequence, when scroll position or a click should drive the shape, or when you need the same result in every browser and are already shipping JavaScript.
Reduced motion, in all three
A morphing background is exactly the kind of thing the prefers-reduced-motion setting exists for. The CSS example above handles it with a media query. The GSAP example checks the same query before creating the tween and simply never starts it.
SMIL is the awkward one, because it does not read CSS media queries. Inside a page you control, check the query in script and pause the document’s animations:
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)");
const svg = document.querySelector("#blob");
if (svg && reduced.matches) {
svg.pauseAnimations();
}
Where the SVG is handed over as a standalone file with no script alongside it, export a still frame instead and keep the animated version for contexts you own.
Choosing
Decoration in a page you control, no script: CSS. An asset that has to animate wherever it lands: SMIL. Motion that belongs to a sequence or must match everywhere: GSAP. All three come out of the animated blob generator from the same set of frames, so trying one and switching costs nothing but a click.