25 CSS Background Animations07 / 25
Animated SVG Wave Layers
Four wave bands sliding across each other at different speeds and amplitudes — the layered-ocean hero, built the way it should be. Each wave is one seamlessly tiling SVG path duplicated end to end inside a 200%-wide layer, translated by exactly 50%, so the loop is mathematically invisible. Layers also bob vertically on a longer, coprime period, which is what stops the parallax from ever repeating a composition.
Published
The code
<section class="bga-07" aria-label="Layered animated SVG wave background demo">
<div class="bga-07__sky" aria-hidden="true"></div>
<div class="bga-07__sun" aria-hidden="true"></div>
<div class="bga-07__sea" aria-hidden="true">
<div class="bga-07__wrap" style="--bga-07-bob:9s">
<div class="bga-07__layer" style="--bga-07-t:43s;--bga-07-o:.34;--bga-07-y:0px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,96 C240,168 480,24 720,96 C960,168 1200,24 1440,96 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,96 C240,168 480,24 720,96 C960,168 1200,24 1440,96 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:7s">
<div class="bga-07__layer" style="--bga-07-t:31s;--bga-07-o:.46;--bga-07-y:26px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,110 C180,50 420,170 720,110 C1020,50 1260,170 1440,110 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,110 C180,50 420,170 720,110 C1020,50 1260,170 1440,110 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:5.5s">
<div class="bga-07__layer bga-07__layer--rev" style="--bga-07-t:25s;--bga-07-o:.62;--bga-07-y:56px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,120 C300,190 540,40 720,120 C900,200 1140,50 1440,120 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,120 C300,190 540,40 720,120 C900,200 1140,50 1440,120 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:4s">
<div class="bga-07__layer" style="--bga-07-t:18s;--bga-07-o:1;--bga-07-y:92px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,140 C220,80 460,196 720,140 C980,84 1220,196 1440,140 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,140 C220,80 460,196 720,140 C980,84 1220,196 1440,140 L1440,200 L0,200 Z"/></svg>
</div>
</div>
</div>
<div class="bga-07__stage">
<div class="bga-07__copy">
<p class="bga-07__eyebrow">Layered motion · 07</p>
<h1 class="bga-07__title">Four waves.<br>Coprime periods.<br>Never repeats.</h1>
<p class="bga-07__sub">18s, 25s, 31s and 43s share no common factor, so the composition you are looking at will not occur again for over four hours.</p>
<div class="bga-07__depths" aria-hidden="true">
<span>43s</span><span>31s</span><span>25s</span><span>18s</span>
</div>
</div>
</div>
</section><section class="bga-07" aria-label="Layered animated SVG wave background demo">
<div class="bga-07__sky" aria-hidden="true"></div>
<div class="bga-07__sun" aria-hidden="true"></div>
<div class="bga-07__sea" aria-hidden="true">
<div class="bga-07__wrap" style="--bga-07-bob:9s">
<div class="bga-07__layer" style="--bga-07-t:43s;--bga-07-o:.34;--bga-07-y:0px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,96 C240,168 480,24 720,96 C960,168 1200,24 1440,96 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,96 C240,168 480,24 720,96 C960,168 1200,24 1440,96 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:7s">
<div class="bga-07__layer" style="--bga-07-t:31s;--bga-07-o:.46;--bga-07-y:26px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,110 C180,50 420,170 720,110 C1020,50 1260,170 1440,110 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,110 C180,50 420,170 720,110 C1020,50 1260,170 1440,110 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:5.5s">
<div class="bga-07__layer bga-07__layer--rev" style="--bga-07-t:25s;--bga-07-o:.62;--bga-07-y:56px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,120 C300,190 540,40 720,120 C900,200 1140,50 1440,120 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,120 C300,190 540,40 720,120 C900,200 1140,50 1440,120 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:4s">
<div class="bga-07__layer" style="--bga-07-t:18s;--bga-07-o:1;--bga-07-y:92px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,140 C220,80 460,196 720,140 C980,84 1220,196 1440,140 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,140 C220,80 460,196 720,140 C980,84 1220,196 1440,140 L1440,200 L0,200 Z"/></svg>
</div>
</div>
</div>
<div class="bga-07__stage">
<div class="bga-07__copy">
<p class="bga-07__eyebrow">Layered motion · 07</p>
<h1 class="bga-07__title">Four waves.<br>Coprime periods.<br>Never repeats.</h1>
<p class="bga-07__sub">18s, 25s, 31s and 43s share no common factor, so the composition you are looking at will not occur again for over four hours.</p>
<div class="bga-07__depths" aria-hidden="true">
<span>43s</span><span>31s</span><span>25s</span><span>18s</span>
</div>
</div>
</div>
</section>.bga-07,
.bga-07 *,
.bga-07 *::before,
.bga-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-07 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-07-deep: oklch(0.32 0.1 236);
--bga-07-mid: oklch(0.55 0.13 210);
--bga-07-foam: oklch(0.86 0.09 190);
--bga-07-acc: oklch(0.85 0.14 88);
background: oklch(0.19 0.05 258);
color: oklch(0.98 0.008 220);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-07__sky {
position: absolute;
inset: 0;
background: linear-gradient(180deg,oklch(0.19 0.05 258) 0%,oklch(0.3 0.08 268) 38%,oklch(0.45 0.11 300) 62%,oklch(0.66 0.14 40) 88%,oklch(0.78 0.15 62) 100%);
}
.bga-07__sun {
position: absolute;
left: 50%;
bottom: 26%;
translate: -50% 0;
width: min(38vw,340px);
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle,oklch(0.95 0.12 80) 0 42%,color-mix(in oklab,var(--bga-07-acc) 55%,transparent) 55%,transparent 72%);
filter: blur(2px);
animation: bga-07-rise 16s ease-in-out infinite alternate;
}
@keyframes bga-07-rise {
to {
translate: -50% -22px;
filter: blur(6px);
}
}
.bga-07__sea {
position: absolute;
inset-inline: 0;
inset-block-end: 0;
height: min(62%,520px);
display: grid;
}
.bga-07__wrap {
position: absolute;
inset: 0;
animation: bga-07-swell var(--bga-07-bob,7s) ease-in-out infinite alternate;
}
@keyframes bga-07-swell {
to {
translate: 0 -16px;
}
}
.bga-07__layer {
position: absolute;
inset-block-end: 0;
inset-inline-start: 0;
width: 200%;
display: flex;
translate: 0 var(--bga-07-y,0);
opacity: var(--bga-07-o,1);
animation: bga-07-roll var(--bga-07-t,20s) linear infinite;
}
.bga-07__layer--rev {
animation-direction: reverse;
}
.bga-07__layer svg {
width: 50%;
height: min(38vh,240px);
display: block;
flex: none;
}
.bga-07__layer:nth-of-type(1) path {
fill: var(--bga-07-deep);
}
.bga-07__wrap:nth-child(1) path {
fill: color-mix(in oklab,var(--bga-07-foam) 70%,transparent);
}
.bga-07__wrap:nth-child(2) path {
fill: var(--bga-07-mid);
}
.bga-07__wrap:nth-child(3) path {
fill: color-mix(in oklab,var(--bga-07-deep) 82%,oklch(0.2 0.06 250));
}
.bga-07__wrap:nth-child(4) path {
fill: oklch(0.2 0.06 252);
}
@keyframes bga-07-roll {
from {
translate: 0 var(--bga-07-y,0);
}
to {
translate: -50% var(--bga-07-y,0);
}
}
.bga-07__stage {
position: relative;
z-index: 3;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
padding: clamp(22px,5vw,68px) clamp(22px,5vw,68px) clamp(160px,26vh,300px);
}
.bga-07__copy {
width: min(100%,680px);
display: grid;
gap: 15px;
justify-items: center;
text-align: center;
}
.bga-07__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .22em;
text-transform: uppercase;
color: var(--bga-07-acc);
}
.bga-07__title {
font-size: clamp(30px,6.2vw,64px);
font-weight: 660;
line-height: 1;
letter-spacing: -.05em;
text-wrap: balance;
text-shadow: 0 4px 40px oklch(0.15 0.05 250/.6);
}
.bga-07__sub {
max-width: 46ch;
font-size: clamp(13.5px,1.3vw,16.5px);
line-height: 1.58;
color: oklch(0.96 0.01 220/.82);
text-wrap: pretty;
text-shadow: 0 1px 14px oklch(0.15 0.05 250/.7);
}
.bga-07__depths {
display: flex;
gap: 8px;
margin-top: 8px;
}
.bga-07__depths span {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
padding: 6px 12px;
border-radius: 99px;
background: oklch(1 0 0/.12);
border: 1px solid oklch(1 0 0/.2);
color: oklch(0.98 0 0/.85);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
.bga-07__layer,
.bga-07__wrap,
.bga-07__sun {
animation: none;
}
}.bga-07,
.bga-07 *,
.bga-07 *::before,
.bga-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-07 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-07-deep: oklch(0.32 0.1 236);
--bga-07-mid: oklch(0.55 0.13 210);
--bga-07-foam: oklch(0.86 0.09 190);
--bga-07-acc: oklch(0.85 0.14 88);
background: oklch(0.19 0.05 258);
color: oklch(0.98 0.008 220);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-07__sky {
position: absolute;
inset: 0;
background: linear-gradient(180deg,oklch(0.19 0.05 258) 0%,oklch(0.3 0.08 268) 38%,oklch(0.45 0.11 300) 62%,oklch(0.66 0.14 40) 88%,oklch(0.78 0.15 62) 100%);
}
.bga-07__sun {
position: absolute;
left: 50%;
bottom: 26%;
translate: -50% 0;
width: min(38vw,340px);
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle,oklch(0.95 0.12 80) 0 42%,color-mix(in oklab,var(--bga-07-acc) 55%,transparent) 55%,transparent 72%);
filter: blur(2px);
animation: bga-07-rise 16s ease-in-out infinite alternate;
}
@keyframes bga-07-rise {
to {
translate: -50% -22px;
filter: blur(6px);
}
}
.bga-07__sea {
position: absolute;
inset-inline: 0;
inset-block-end: 0;
height: min(62%,520px);
display: grid;
}
.bga-07__wrap {
position: absolute;
inset: 0;
animation: bga-07-swell var(--bga-07-bob,7s) ease-in-out infinite alternate;
}
@keyframes bga-07-swell {
to {
translate: 0 -16px;
}
}
.bga-07__layer {
position: absolute;
inset-block-end: 0;
inset-inline-start: 0;
width: 200%;
display: flex;
translate: 0 var(--bga-07-y,0);
opacity: var(--bga-07-o,1);
animation: bga-07-roll var(--bga-07-t,20s) linear infinite;
}
.bga-07__layer--rev {
animation-direction: reverse;
}
.bga-07__layer svg {
width: 50%;
height: min(38vh,240px);
display: block;
flex: none;
}
.bga-07__layer:nth-of-type(1) path {
fill: var(--bga-07-deep);
}
.bga-07__wrap:nth-child(1) path {
fill: color-mix(in oklab,var(--bga-07-foam) 70%,transparent);
}
.bga-07__wrap:nth-child(2) path {
fill: var(--bga-07-mid);
}
.bga-07__wrap:nth-child(3) path {
fill: color-mix(in oklab,var(--bga-07-deep) 82%,oklch(0.2 0.06 250));
}
.bga-07__wrap:nth-child(4) path {
fill: oklch(0.2 0.06 252);
}
@keyframes bga-07-roll {
from {
translate: 0 var(--bga-07-y,0);
}
to {
translate: -50% var(--bga-07-y,0);
}
}
.bga-07__stage {
position: relative;
z-index: 3;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
padding: clamp(22px,5vw,68px) clamp(22px,5vw,68px) clamp(160px,26vh,300px);
}
.bga-07__copy {
width: min(100%,680px);
display: grid;
gap: 15px;
justify-items: center;
text-align: center;
}
.bga-07__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .22em;
text-transform: uppercase;
color: var(--bga-07-acc);
}
.bga-07__title {
font-size: clamp(30px,6.2vw,64px);
font-weight: 660;
line-height: 1;
letter-spacing: -.05em;
text-wrap: balance;
text-shadow: 0 4px 40px oklch(0.15 0.05 250/.6);
}
.bga-07__sub {
max-width: 46ch;
font-size: clamp(13.5px,1.3vw,16.5px);
line-height: 1.58;
color: oklch(0.96 0.01 220/.82);
text-wrap: pretty;
text-shadow: 0 1px 14px oklch(0.15 0.05 250/.7);
}
.bga-07__depths {
display: flex;
gap: 8px;
margin-top: 8px;
}
.bga-07__depths span {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
padding: 6px 12px;
border-radius: 99px;
background: oklch(1 0 0/.12);
border: 1px solid oklch(1 0 0/.2);
color: oklch(0.98 0 0/.85);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
.bga-07__layer,
.bga-07__wrap,
.bga-07__sun {
animation: none;
}
}Here's a working CSS Background Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Animated SVG Wave Layers
Source: https://codefronts.com/motion/css-background-animations/video-mimicking-subtle-wave-overlays/
Four wave bands sliding across each other at different speeds and amplitudes — the layered-ocean hero, built the way it should be. Each wave is one seamlessly tiling SVG path duplicated end to end inside a 200%-wide layer, translated by exactly 50%, so the loop is mathematically invisible. Layers also bob vertically on a longer, coprime period, which is what stops the parallax from ever repeating a composition.
## HTML
```html
<section class="bga-07" aria-label="Layered animated SVG wave background demo">
<div class="bga-07__sky" aria-hidden="true"></div>
<div class="bga-07__sun" aria-hidden="true"></div>
<div class="bga-07__sea" aria-hidden="true">
<div class="bga-07__wrap" style="--bga-07-bob:9s">
<div class="bga-07__layer" style="--bga-07-t:43s;--bga-07-o:.34;--bga-07-y:0px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,96 C240,168 480,24 720,96 C960,168 1200,24 1440,96 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,96 C240,168 480,24 720,96 C960,168 1200,24 1440,96 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:7s">
<div class="bga-07__layer" style="--bga-07-t:31s;--bga-07-o:.46;--bga-07-y:26px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,110 C180,50 420,170 720,110 C1020,50 1260,170 1440,110 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,110 C180,50 420,170 720,110 C1020,50 1260,170 1440,110 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:5.5s">
<div class="bga-07__layer bga-07__layer--rev" style="--bga-07-t:25s;--bga-07-o:.62;--bga-07-y:56px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,120 C300,190 540,40 720,120 C900,200 1140,50 1440,120 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,120 C300,190 540,40 720,120 C900,200 1140,50 1440,120 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:4s">
<div class="bga-07__layer" style="--bga-07-t:18s;--bga-07-o:1;--bga-07-y:92px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,140 C220,80 460,196 720,140 C980,84 1220,196 1440,140 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,140 C220,80 460,196 720,140 C980,84 1220,196 1440,140 L1440,200 L0,200 Z"/></svg>
</div>
</div>
</div>
<div class="bga-07__stage">
<div class="bga-07__copy">
<p class="bga-07__eyebrow">Layered motion · 07</p>
<h1 class="bga-07__title">Four waves.<br>Coprime periods.<br>Never repeats.</h1>
<p class="bga-07__sub">18s, 25s, 31s and 43s share no common factor, so the composition you are looking at will not occur again for over four hours.</p>
<div class="bga-07__depths" aria-hidden="true">
<span>43s</span><span>31s</span><span>25s</span><span>18s</span>
</div>
</div>
</div>
</section>
```
## CSS
```css
.bga-07,
.bga-07 *,
.bga-07 *::before,
.bga-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-07 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-07-deep: oklch(0.32 0.1 236);
--bga-07-mid: oklch(0.55 0.13 210);
--bga-07-foam: oklch(0.86 0.09 190);
--bga-07-acc: oklch(0.85 0.14 88);
background: oklch(0.19 0.05 258);
color: oklch(0.98 0.008 220);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-07__sky {
position: absolute;
inset: 0;
background: linear-gradient(180deg,oklch(0.19 0.05 258) 0%,oklch(0.3 0.08 268) 38%,oklch(0.45 0.11 300) 62%,oklch(0.66 0.14 40) 88%,oklch(0.78 0.15 62) 100%);
}
.bga-07__sun {
position: absolute;
left: 50%;
bottom: 26%;
translate: -50% 0;
width: min(38vw,340px);
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle,oklch(0.95 0.12 80) 0 42%,color-mix(in oklab,var(--bga-07-acc) 55%,transparent) 55%,transparent 72%);
filter: blur(2px);
animation: bga-07-rise 16s ease-in-out infinite alternate;
}
@keyframes bga-07-rise {
to {
translate: -50% -22px;
filter: blur(6px);
}
}
.bga-07__sea {
position: absolute;
inset-inline: 0;
inset-block-end: 0;
height: min(62%,520px);
display: grid;
}
.bga-07__wrap {
position: absolute;
inset: 0;
animation: bga-07-swell var(--bga-07-bob,7s) ease-in-out infinite alternate;
}
@keyframes bga-07-swell {
to {
translate: 0 -16px;
}
}
.bga-07__layer {
position: absolute;
inset-block-end: 0;
inset-inline-start: 0;
width: 200%;
display: flex;
translate: 0 var(--bga-07-y,0);
opacity: var(--bga-07-o,1);
animation: bga-07-roll var(--bga-07-t,20s) linear infinite;
}
.bga-07__layer--rev {
animation-direction: reverse;
}
.bga-07__layer svg {
width: 50%;
height: min(38vh,240px);
display: block;
flex: none;
}
.bga-07__layer:nth-of-type(1) path {
fill: var(--bga-07-deep);
}
.bga-07__wrap:nth-child(1) path {
fill: color-mix(in oklab,var(--bga-07-foam) 70%,transparent);
}
.bga-07__wrap:nth-child(2) path {
fill: var(--bga-07-mid);
}
.bga-07__wrap:nth-child(3) path {
fill: color-mix(in oklab,var(--bga-07-deep) 82%,oklch(0.2 0.06 250));
}
.bga-07__wrap:nth-child(4) path {
fill: oklch(0.2 0.06 252);
}
@keyframes bga-07-roll {
from {
translate: 0 var(--bga-07-y,0);
}
to {
translate: -50% var(--bga-07-y,0);
}
}
.bga-07__stage {
position: relative;
z-index: 3;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
padding: clamp(22px,5vw,68px) clamp(22px,5vw,68px) clamp(160px,26vh,300px);
}
.bga-07__copy {
width: min(100%,680px);
display: grid;
gap: 15px;
justify-items: center;
text-align: center;
}
.bga-07__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .22em;
text-transform: uppercase;
color: var(--bga-07-acc);
}
.bga-07__title {
font-size: clamp(30px,6.2vw,64px);
font-weight: 660;
line-height: 1;
letter-spacing: -.05em;
text-wrap: balance;
text-shadow: 0 4px 40px oklch(0.15 0.05 250/.6);
}
.bga-07__sub {
max-width: 46ch;
font-size: clamp(13.5px,1.3vw,16.5px);
line-height: 1.58;
color: oklch(0.96 0.01 220/.82);
text-wrap: pretty;
text-shadow: 0 1px 14px oklch(0.15 0.05 250/.7);
}
.bga-07__depths {
display: flex;
gap: 8px;
margin-top: 8px;
}
.bga-07__depths span {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
padding: 6px 12px;
border-radius: 99px;
background: oklch(1 0 0/.12);
border: 1px solid oklch(1 0 0/.2);
color: oklch(0.98 0 0/.85);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
.bga-07__layer,
.bga-07__wrap,
.bga-07__sun {
animation: none;
}
}
```Here's a working CSS Background Animation from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Animated SVG Wave Layers
Source: https://codefronts.com/motion/css-background-animations/video-mimicking-subtle-wave-overlays/
Four wave bands sliding across each other at different speeds and amplitudes — the layered-ocean hero, built the way it should be. Each wave is one seamlessly tiling SVG path duplicated end to end inside a 200%-wide layer, translated by exactly 50%, so the loop is mathematically invisible. Layers also bob vertically on a longer, coprime period, which is what stops the parallax from ever repeating a composition.
## HTML
```html
<section class="bga-07" aria-label="Layered animated SVG wave background demo">
<div class="bga-07__sky" aria-hidden="true"></div>
<div class="bga-07__sun" aria-hidden="true"></div>
<div class="bga-07__sea" aria-hidden="true">
<div class="bga-07__wrap" style="--bga-07-bob:9s">
<div class="bga-07__layer" style="--bga-07-t:43s;--bga-07-o:.34;--bga-07-y:0px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,96 C240,168 480,24 720,96 C960,168 1200,24 1440,96 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,96 C240,168 480,24 720,96 C960,168 1200,24 1440,96 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:7s">
<div class="bga-07__layer" style="--bga-07-t:31s;--bga-07-o:.46;--bga-07-y:26px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,110 C180,50 420,170 720,110 C1020,50 1260,170 1440,110 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,110 C180,50 420,170 720,110 C1020,50 1260,170 1440,110 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:5.5s">
<div class="bga-07__layer bga-07__layer--rev" style="--bga-07-t:25s;--bga-07-o:.62;--bga-07-y:56px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,120 C300,190 540,40 720,120 C900,200 1140,50 1440,120 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,120 C300,190 540,40 720,120 C900,200 1140,50 1440,120 L1440,200 L0,200 Z"/></svg>
</div>
</div>
<div class="bga-07__wrap" style="--bga-07-bob:4s">
<div class="bga-07__layer" style="--bga-07-t:18s;--bga-07-o:1;--bga-07-y:92px">
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,140 C220,80 460,196 720,140 C980,84 1220,196 1440,140 L1440,200 L0,200 Z"/></svg>
<svg viewBox="0 0 1440 200" preserveAspectRatio="none"><path d="M0,140 C220,80 460,196 720,140 C980,84 1220,196 1440,140 L1440,200 L0,200 Z"/></svg>
</div>
</div>
</div>
<div class="bga-07__stage">
<div class="bga-07__copy">
<p class="bga-07__eyebrow">Layered motion · 07</p>
<h1 class="bga-07__title">Four waves.<br>Coprime periods.<br>Never repeats.</h1>
<p class="bga-07__sub">18s, 25s, 31s and 43s share no common factor, so the composition you are looking at will not occur again for over four hours.</p>
<div class="bga-07__depths" aria-hidden="true">
<span>43s</span><span>31s</span><span>25s</span><span>18s</span>
</div>
</div>
</div>
</section>
```
## CSS
```css
.bga-07,
.bga-07 *,
.bga-07 *::before,
.bga-07 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-07 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-07-deep: oklch(0.32 0.1 236);
--bga-07-mid: oklch(0.55 0.13 210);
--bga-07-foam: oklch(0.86 0.09 190);
--bga-07-acc: oklch(0.85 0.14 88);
background: oklch(0.19 0.05 258);
color: oklch(0.98 0.008 220);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-07__sky {
position: absolute;
inset: 0;
background: linear-gradient(180deg,oklch(0.19 0.05 258) 0%,oklch(0.3 0.08 268) 38%,oklch(0.45 0.11 300) 62%,oklch(0.66 0.14 40) 88%,oklch(0.78 0.15 62) 100%);
}
.bga-07__sun {
position: absolute;
left: 50%;
bottom: 26%;
translate: -50% 0;
width: min(38vw,340px);
aspect-ratio: 1;
border-radius: 50%;
background: radial-gradient(circle,oklch(0.95 0.12 80) 0 42%,color-mix(in oklab,var(--bga-07-acc) 55%,transparent) 55%,transparent 72%);
filter: blur(2px);
animation: bga-07-rise 16s ease-in-out infinite alternate;
}
@keyframes bga-07-rise {
to {
translate: -50% -22px;
filter: blur(6px);
}
}
.bga-07__sea {
position: absolute;
inset-inline: 0;
inset-block-end: 0;
height: min(62%,520px);
display: grid;
}
.bga-07__wrap {
position: absolute;
inset: 0;
animation: bga-07-swell var(--bga-07-bob,7s) ease-in-out infinite alternate;
}
@keyframes bga-07-swell {
to {
translate: 0 -16px;
}
}
.bga-07__layer {
position: absolute;
inset-block-end: 0;
inset-inline-start: 0;
width: 200%;
display: flex;
translate: 0 var(--bga-07-y,0);
opacity: var(--bga-07-o,1);
animation: bga-07-roll var(--bga-07-t,20s) linear infinite;
}
.bga-07__layer--rev {
animation-direction: reverse;
}
.bga-07__layer svg {
width: 50%;
height: min(38vh,240px);
display: block;
flex: none;
}
.bga-07__layer:nth-of-type(1) path {
fill: var(--bga-07-deep);
}
.bga-07__wrap:nth-child(1) path {
fill: color-mix(in oklab,var(--bga-07-foam) 70%,transparent);
}
.bga-07__wrap:nth-child(2) path {
fill: var(--bga-07-mid);
}
.bga-07__wrap:nth-child(3) path {
fill: color-mix(in oklab,var(--bga-07-deep) 82%,oklch(0.2 0.06 250));
}
.bga-07__wrap:nth-child(4) path {
fill: oklch(0.2 0.06 252);
}
@keyframes bga-07-roll {
from {
translate: 0 var(--bga-07-y,0);
}
to {
translate: -50% var(--bga-07-y,0);
}
}
.bga-07__stage {
position: relative;
z-index: 3;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
padding: clamp(22px,5vw,68px) clamp(22px,5vw,68px) clamp(160px,26vh,300px);
}
.bga-07__copy {
width: min(100%,680px);
display: grid;
gap: 15px;
justify-items: center;
text-align: center;
}
.bga-07__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .22em;
text-transform: uppercase;
color: var(--bga-07-acc);
}
.bga-07__title {
font-size: clamp(30px,6.2vw,64px);
font-weight: 660;
line-height: 1;
letter-spacing: -.05em;
text-wrap: balance;
text-shadow: 0 4px 40px oklch(0.15 0.05 250/.6);
}
.bga-07__sub {
max-width: 46ch;
font-size: clamp(13.5px,1.3vw,16.5px);
line-height: 1.58;
color: oklch(0.96 0.01 220/.82);
text-wrap: pretty;
text-shadow: 0 1px 14px oklch(0.15 0.05 250/.7);
}
.bga-07__depths {
display: flex;
gap: 8px;
margin-top: 8px;
}
.bga-07__depths span {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
padding: 6px 12px;
border-radius: 99px;
background: oklch(1 0 0/.12);
border: 1px solid oklch(1 0 0/.2);
color: oklch(0.98 0 0/.85);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
}
@media (prefers-reduced-motion: reduce) {
.bga-07__layer,
.bga-07__wrap,
.bga-07__sun {
animation: none;
}
}
```How this works
The seam is the whole problem. A wave path that ends where it began, drawn twice inside a container twice as wide, can be translated by exactly -50% forever:
<svg viewBox='0 0 1440 180' preserveAspectRatio='none' class='wave'>
<path d='M0,90 C240,150 480,30 720,90 C960,150 1200,30 1440,90 L1440,180 L0,180 Z'/>
</svg>
.layer { width: 200%; display: flex; animation: roll 18s linear infinite; }
@keyframes roll { to { translate: -50% 0; } }Two identical SVGs side by side inside that flex layer means when the first has slid fully off, the second is exactly where the first started. preserveAspectRatio='none' lets each SVG stretch to any viewport width without distorting the illusion, because a wave has no correct aspect ratio.
Speeds are deliberately coprime (18s, 25s, 31s, 43s). If two layers share a period they will visibly sync up every cycle; with coprime periods the combined pattern effectively never repeats.
A second animation on a wrapper adds vertical bob:
.wrap { animation: swell 7s ease-in-out infinite alternate; }
@keyframes swell { to { translate: 0 -14px; } }Because translate is animated on separate elements from the horizontal roll, the two compose without either being overwritten — one of the strongest reasons to prefer the individual translate / rotate / scale properties over the combined transform shorthand.
Make it yours
- Change the
Ccontrol points in the path to change wave character — pull them further apart vertically for a rougher sea, closer for a calm swell. - Set each layer's
fillwithcolor-mix()against one brand colour and vary the percentage per depth; the palette then follows a single token. - Flip the whole stack with
scale: 1 -1to use it as a top divider instead of a bottom one. - Add a foreground layer with
filter: blur(3px)and faster motion for a shallow depth-of-field effect that reads as physical distance.
Gotchas — read before shipping
- Any translation other than exactly -50% of a doubled layer will show a seam. Percentage, not pixels, so it survives resizing.
- SVG paths are not free: keep each wave to a handful of cubic segments. Ten-segment coastlines look better in the editor and cost real paint time on mobile.
preserveAspectRatio='none'stretches stroke widths too — use fills, not strokes, for wave shapes.- Stacking many semi-transparent SVG layers is fill-rate heavy on low-end phones; four is a sensible ceiling for a full-bleed hero.
- Animating
transformon one element andtranslateon another is fine, but animating bothtransformandtranslateon the same element makes one silently win.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by oklch(), color-mix(), backdrop-filter, text-wrap as this demo is written. The core technique itself goes back further — Chrome 104+.
Individual translate/rotate/scale properties: Chrome 104, Safari 14.1, Firefox 72. Replace with transform: translateX() for older engines. Inline SVG and CSS keyframes are universal.