Drops swell under a bar of goo, stretch a neck, let go, and fall into a pool that swallows them. The necks are real: an inline SVG goo filter joins nearby shapes, so drops and pools merge.
Also known as: slime drip animation, dripping liquid css, melting loader.
<div class="ldr-dripping-goo" role="status" aria-label="Loading">
<svg width="0" height="0" aria-hidden="true">
<filter id="ldr-dripping-goo-filter" primitiveUnits="objectBoundingBox">
<feGaussianBlur stdDeviation="0.04 0.05" />
<feColorMatrix values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 20 -9" />
</filter>
</svg>
<div class="ldr-dripping-goo-goo"><span></span><span></span><span></span></div>
</div>.ldr-dripping-goo {
--s: var(--ldr-size, 48px);
--c: var(--ldr-color, #3b82f6);
position: relative;
width: calc(var(--s) * 1.4);
height: calc(var(--s) * 1.2);
}
.ldr-dripping-goo > svg {
position: absolute;
}
.ldr-dripping-goo-goo {
position: absolute;
inset: 0;
filter: url(#ldr-dripping-goo-filter);
}
.ldr-dripping-goo-goo::before,
.ldr-dripping-goo-goo::after,
.ldr-dripping-goo-goo span {
content: "";
position: absolute;
border-radius: calc(var(--s) * 0.1);
background: var(--c);
}
.ldr-dripping-goo-goo::before {
top: 0;
left: 0;
right: 0;
height: calc(var(--s) * 0.2);
}
.ldr-dripping-goo-goo::after {
bottom: 0;
left: 12%;
right: 12%;
height: calc(var(--s) * 0.14);
}
.ldr-dripping-goo-goo span {
top: calc(var(--s) * 0.04);
left: var(--x);
width: calc(var(--s) * 0.24);
height: calc(var(--s) * 0.24);
border-radius: 50%;
animation: ldr-dripping-goo-drip var(--t) infinite;
animation-delay: var(--delay);
}
.ldr-dripping-goo-goo span:nth-child(1) { --x: 16%; --t: 2.2s; --delay: 0s; }
.ldr-dripping-goo-goo span:nth-child(2) { --x: 43%; --t: 2.6s; --delay: -0.9s; }
.ldr-dripping-goo-goo span:nth-child(3) { --x: 70%; --t: 2.4s; --delay: -1.7s; }
@keyframes ldr-dripping-goo-drip {
0% { transform: translateY(0) scale(0.3); animation-timing-function: cubic-bezier(0.3, 0, 0.6, 1); }
45% { transform: translateY(calc(var(--s) * 0.14)) scale(0.95, 1.12); animation-timing-function: cubic-bezier(0.55, 0, 1, 0.6); }
78% { transform: translateY(calc(var(--s) * 0.82)) scale(0.85, 1.15); animation-timing-function: ease-out; }
100% { transform: translateY(calc(var(--s) * 0.9)) scale(0.2); }
}
@media (prefers-reduced-motion: reduce) {
.ldr-dripping-goo, .ldr-dripping-goo *, .ldr-dripping-goo::before, .ldr-dripping-goo::after, .ldr-dripping-goo *::before, .ldr-dripping-goo *::after {
animation-duration: 3s;
}
}--ldr-color on the element or a parent.
Currently previewing #3b82f6.--ldr-size. Every dimension in the snippet is
derived from it. Currently previewing 64px.animation shorthand.
Delays on the children are relative, so they keep their rhythm.role="status" and an
aria-label so screen readers announce the wait. The reduced-motion rule
at the end slows the animation for users who asked for less motion.