Warm wax blobs rise from a pool at the bottom, stretch, merge into the pool at the top and sink back. The goo is the blur and contrast trick in em, so it scales with --bgx-size.
Also known as: lava lamp css background, gooey blobs background, metaball background css.
<div class="bgx-lava-lamp" aria-hidden="true"> <div class="bgx-lava-lamp-wax"><span></span><span></span><span></span><span></span><span></span><span></span></div> </div>
.bgx-lava-lamp {
--c: var(--bgx-color, #3b82f6);
font-size: var(--bgx-size, 16px);
position: absolute;
inset: 0;
overflow: hidden;
background: #000;
isolation: isolate;
}
.bgx-lava-lamp-wax {
position: absolute;
inset: 0;
background: #000;
filter: contrast(24);
}
.bgx-lava-lamp-wax::before,
.bgx-lava-lamp-wax::after {
content: "";
position: absolute;
left: -10%;
right: -10%;
border-radius: 50%;
background: #fff;
filter: blur(1em);
}
.bgx-lava-lamp-wax::before { bottom: -3.4em; height: 6em; }
.bgx-lava-lamp-wax::after { top: -3.8em; height: 5.4em; }
.bgx-lava-lamp-wax span {
position: absolute;
top: 0;
left: var(--x);
width: var(--size);
height: 100%;
animation: bgx-lava-lamp-rise var(--t) cubic-bezier(0.45, 0, 0.55, 1) var(--d) infinite;
}
.bgx-lava-lamp-wax span::before {
content: "";
position: absolute;
top: 50%;
left: 0;
width: 100%;
aspect-ratio: 1;
translate: 0 -50%;
border-radius: 50%;
background: #fff;
filter: blur(1em);
animation: bgx-lava-lamp-squish calc(var(--t) / 2) ease-in-out var(--d) infinite alternate;
}
.bgx-lava-lamp-wax span:nth-child(1) { --x: 8%; --size: 5em; --t: 13s; --d: -2s; }
.bgx-lava-lamp-wax span:nth-child(2) { --x: 26%; --size: 3.6em; --t: 10s; --d: -7s; }
.bgx-lava-lamp-wax span:nth-child(3) { --x: 42%; --size: 6.2em; --t: 16s; --d: -12s; }
.bgx-lava-lamp-wax span:nth-child(4) { --x: 60%; --size: 4.2em; --t: 11.5s; --d: -4s; }
.bgx-lava-lamp-wax span:nth-child(5) { --x: 74%; --size: 5.6em; --t: 14s; --d: -9s; }
.bgx-lava-lamp-wax span:nth-child(6) { --x: 88%; --size: 3.8em; --t: 9s; --d: -1s; }
.bgx-lava-lamp::before,
.bgx-lava-lamp::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
}
.bgx-lava-lamp::before {
z-index: 1;
background: linear-gradient(to top, oklch(from var(--c) 0.72 0.19 calc(h + 120)), oklch(from var(--c) 0.64 0.24 calc(h + 80)) 55%, oklch(from var(--c) 0.58 0.22 calc(h + 40)));
mix-blend-mode: multiply;
}
.bgx-lava-lamp::after {
z-index: 2;
background: radial-gradient(ellipse 80% 70% at 50% 50%, oklch(from var(--c) 0.26 0.08 calc(h + 60)), oklch(from var(--c) 0.16 0.05 calc(h + 40)));
mix-blend-mode: lighten;
}
@keyframes bgx-lava-lamp-rise {
0%, 100% { transform: translate(0, 54%); }
50% { transform: translate(18%, -54%); }
}
@keyframes bgx-lava-lamp-squish {
from { transform: scale(1, 1); }
to { transform: scale(0.84, 1.2); }
}
@media (prefers-reduced-motion: reduce) {
.bgx-lava-lamp, .bgx-lava-lamp *, .bgx-lava-lamp::before, .bgx-lava-lamp::after, .bgx-lava-lamp *::before, .bgx-lava-lamp *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}--bgx-color on the element or a parent.
Currently previewing #3b82f6.--bgx-size. Every other dimension is in
em, so the whole thing scales together. Currently previewing 16px.transition or animation
shorthand. Staggered delays are relative, so they keep their rhythm.