25 CSS Background Animations04 / 25
Grain & Noise Texture Overlay
The texture that makes a flat gradient look printed instead of rendered. An inline SVG feTurbulence filter generates real fractal noise as a data URI — no PNG request, no 200KB tiling image — and a steps() animation jitters it eight times a second for authentic film grain rather than a frozen static layer. Three intensities and three blend modes are switchable so you can dial it against your own art direction.
Published
The code
<section class="bga-04" aria-label="Film grain and noise texture overlay background demo">
<div class="bga-04__base" aria-hidden="true">
<img class="bga-04__photo" src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?q=80&w=1800&auto=format&fit=crop" alt="" width="1800" height="1200" loading="lazy" decoding="async">
</div>
<div class="bga-04__grain" aria-hidden="true"></div>
<div class="bga-04__vig" aria-hidden="true"></div>
<div class="bga-04__stage">
<header class="bga-04__head">
<p class="bga-04__eyebrow">Texture · 04</p>
<h1 class="bga-04__title">Grain is the difference between rendered and printed.</h1>
<p class="bga-04__sub">Fractal noise generated by an inline SVG filter, tiled seamlessly, jittered on a six-step clock. No image request, no canvas loop.</p>
</header>
<div class="bga-04__controls" role="group" aria-label="Grain options">
<div class="bga-04__set">
<span class="bga-04__lab">Intensity</span>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i1">
<label class="bga-04__chip" for="bga-04-i1">Whisper</label>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i2" checked>
<label class="bga-04__chip" for="bga-04-i2">35mm</label>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i3">
<label class="bga-04__chip" for="bga-04-i3">Pushed</label>
</div>
<div class="bga-04__set">
<span class="bga-04__lab">Blend</span>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b1" checked>
<label class="bga-04__chip" for="bga-04-b1">overlay</label>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b2">
<label class="bga-04__chip" for="bga-04-b2">soft-light</label>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b3">
<label class="bga-04__chip" for="bga-04-b3">multiply</label>
</div>
</div>
<p class="bga-04__spec">feTurbulence fractalNoise · baseFrequency .82 · numOctaves 4 · stitchTiles stitch · steps(6) jitter</p>
</div>
</section><section class="bga-04" aria-label="Film grain and noise texture overlay background demo">
<div class="bga-04__base" aria-hidden="true">
<img class="bga-04__photo" src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?q=80&w=1800&auto=format&fit=crop" alt="" width="1800" height="1200" loading="lazy" decoding="async">
</div>
<div class="bga-04__grain" aria-hidden="true"></div>
<div class="bga-04__vig" aria-hidden="true"></div>
<div class="bga-04__stage">
<header class="bga-04__head">
<p class="bga-04__eyebrow">Texture · 04</p>
<h1 class="bga-04__title">Grain is the difference between rendered and printed.</h1>
<p class="bga-04__sub">Fractal noise generated by an inline SVG filter, tiled seamlessly, jittered on a six-step clock. No image request, no canvas loop.</p>
</header>
<div class="bga-04__controls" role="group" aria-label="Grain options">
<div class="bga-04__set">
<span class="bga-04__lab">Intensity</span>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i1">
<label class="bga-04__chip" for="bga-04-i1">Whisper</label>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i2" checked>
<label class="bga-04__chip" for="bga-04-i2">35mm</label>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i3">
<label class="bga-04__chip" for="bga-04-i3">Pushed</label>
</div>
<div class="bga-04__set">
<span class="bga-04__lab">Blend</span>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b1" checked>
<label class="bga-04__chip" for="bga-04-b1">overlay</label>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b2">
<label class="bga-04__chip" for="bga-04-b2">soft-light</label>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b3">
<label class="bga-04__chip" for="bga-04-b3">multiply</label>
</div>
</div>
<p class="bga-04__spec">feTurbulence fractalNoise · baseFrequency .82 · numOctaves 4 · stitchTiles stitch · steps(6) jitter</p>
</div>
</section>.bga-04,
.bga-04 *,
.bga-04 *::before,
.bga-04 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-04 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-04-op: .42;
--bga-04-blend: overlay;
--bga-04-freq: .82;
--bga-04-ink: oklch(0.99 0.005 90);
background: oklch(0.28 0.06 32);
color: var(--bga-04-ink);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-04__base {
position: absolute;
inset: 0;
background: radial-gradient(90% 70% at 12% 8%,oklch(0.68 0.18 44/.9),transparent 62%), radial-gradient(80% 70% at 88% 22%,oklch(0.52 0.17 12/.85),transparent 64%), radial-gradient(100% 90% at 50% 108%,oklch(0.32 0.1 300/.9),transparent 70%), linear-gradient(160deg,oklch(0.34 0.08 40),oklch(0.2 0.05 300));
}
.bga-04__photo {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
opacity: .5;
mix-blend-mode: luminosity;
}
.bga-04__grain {
position: absolute;
inset: -20%;
pointer-events: none;
opacity: var(--bga-04-op);
mix-blend-mode: var(--bga-04-blend);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
background-size: 220px 220px;
animation: bga-04-jitter .72s steps(6) infinite;
}
@keyframes bga-04-jitter {
0% {
translate: 0 0;
}
16% {
translate: -4% -6%;
}
32% {
translate: 5% -3%;
}
48% {
translate: -3% 5%;
}
64% {
translate: 4% 4%;
}
80% {
translate: -5% 2%;
}
100% {
translate: 0 0;
}
}
.bga-04__vig {
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 100% at 50% 45%,transparent 42%,oklch(0.1 0.03 30/.72) 100%);
}
.bga-04__stage {
position: relative;
z-index: 2;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(22px,3.2vw,36px);
padding: clamp(24px,5vw,72px);
text-align: center;
}
.bga-04__head {
display: grid;
gap: 14px;
justify-items: center;
max-width: 860px;
}
.bga-04__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .24em;
text-transform: uppercase;
color: oklch(0.9 0.08 60/.85);
}
.bga-04__title {
font-size: clamp(30px,6vw,64px);
font-weight: 600;
line-height: 1.03;
letter-spacing: -.05em;
text-wrap: balance;
text-shadow: 0 3px 40px oklch(0.1 0.03 30/.5);
}
.bga-04__sub {
max-width: 54ch;
font-size: clamp(14px,1.35vw,17px);
line-height: 1.58;
color: oklch(0.95 0.02 60/.78);
text-wrap: pretty;
}
.bga-04__controls {
display: flex;
flex-wrap: wrap;
gap: 12px 26px;
justify-content: center;
padding: 14px 20px;
border-radius: 18px;
background: oklch(0.16 0.03 30/.42);
border: 1px solid oklch(1 0 0/.14);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
}
.bga-04__set {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 7px;
}
.bga-04__lab {
font-size: 10.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(1 0 0/.55);
margin-inline-end: 3px;
}
.bga-04__sr {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
.bga-04__chip {
display: inline-flex;
align-items: center;
min-height: 34px;
padding: 0 14px;
border-radius: 99px;
cursor: pointer;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
color: oklch(1 0 0/.75);
background: oklch(1 0 0/.07);
border: 1px solid oklch(1 0 0/.14);
transition: background .25s,color .25s;
}
.bga-04__chip:hover {
background: oklch(1 0 0/.15);
}
.bga-04__sr:checked + .bga-04__chip {
background: oklch(0.99 0.005 90);
color: oklch(0.22 0.05 30);
border-color: transparent;
}
.bga-04__sr:focus-visible + .bga-04__chip {
outline: 3px solid oklch(0.99 0.005 90);
outline-offset: 3px;
}
.bga-04__spec {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: oklch(1 0 0/.45);
text-wrap: balance;
max-width: 70ch;
}
.bga-04:has(#bga-04-i1:checked) {
--bga-04-op: .16;
}
.bga-04:has(#bga-04-i3:checked) {
--bga-04-op: .75;
}
.bga-04:has(#bga-04-b2:checked) {
--bga-04-blend: soft-light;
}
.bga-04:has(#bga-04-b3:checked) {
--bga-04-blend: multiply;
}
@media (prefers-reduced-motion: reduce) {
.bga-04__grain {
animation: none;
}
}.bga-04,
.bga-04 *,
.bga-04 *::before,
.bga-04 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-04 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-04-op: .42;
--bga-04-blend: overlay;
--bga-04-freq: .82;
--bga-04-ink: oklch(0.99 0.005 90);
background: oklch(0.28 0.06 32);
color: var(--bga-04-ink);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-04__base {
position: absolute;
inset: 0;
background: radial-gradient(90% 70% at 12% 8%,oklch(0.68 0.18 44/.9),transparent 62%), radial-gradient(80% 70% at 88% 22%,oklch(0.52 0.17 12/.85),transparent 64%), radial-gradient(100% 90% at 50% 108%,oklch(0.32 0.1 300/.9),transparent 70%), linear-gradient(160deg,oklch(0.34 0.08 40),oklch(0.2 0.05 300));
}
.bga-04__photo {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
opacity: .5;
mix-blend-mode: luminosity;
}
.bga-04__grain {
position: absolute;
inset: -20%;
pointer-events: none;
opacity: var(--bga-04-op);
mix-blend-mode: var(--bga-04-blend);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
background-size: 220px 220px;
animation: bga-04-jitter .72s steps(6) infinite;
}
@keyframes bga-04-jitter {
0% {
translate: 0 0;
}
16% {
translate: -4% -6%;
}
32% {
translate: 5% -3%;
}
48% {
translate: -3% 5%;
}
64% {
translate: 4% 4%;
}
80% {
translate: -5% 2%;
}
100% {
translate: 0 0;
}
}
.bga-04__vig {
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 100% at 50% 45%,transparent 42%,oklch(0.1 0.03 30/.72) 100%);
}
.bga-04__stage {
position: relative;
z-index: 2;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(22px,3.2vw,36px);
padding: clamp(24px,5vw,72px);
text-align: center;
}
.bga-04__head {
display: grid;
gap: 14px;
justify-items: center;
max-width: 860px;
}
.bga-04__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .24em;
text-transform: uppercase;
color: oklch(0.9 0.08 60/.85);
}
.bga-04__title {
font-size: clamp(30px,6vw,64px);
font-weight: 600;
line-height: 1.03;
letter-spacing: -.05em;
text-wrap: balance;
text-shadow: 0 3px 40px oklch(0.1 0.03 30/.5);
}
.bga-04__sub {
max-width: 54ch;
font-size: clamp(14px,1.35vw,17px);
line-height: 1.58;
color: oklch(0.95 0.02 60/.78);
text-wrap: pretty;
}
.bga-04__controls {
display: flex;
flex-wrap: wrap;
gap: 12px 26px;
justify-content: center;
padding: 14px 20px;
border-radius: 18px;
background: oklch(0.16 0.03 30/.42);
border: 1px solid oklch(1 0 0/.14);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
}
.bga-04__set {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 7px;
}
.bga-04__lab {
font-size: 10.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(1 0 0/.55);
margin-inline-end: 3px;
}
.bga-04__sr {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
.bga-04__chip {
display: inline-flex;
align-items: center;
min-height: 34px;
padding: 0 14px;
border-radius: 99px;
cursor: pointer;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
color: oklch(1 0 0/.75);
background: oklch(1 0 0/.07);
border: 1px solid oklch(1 0 0/.14);
transition: background .25s,color .25s;
}
.bga-04__chip:hover {
background: oklch(1 0 0/.15);
}
.bga-04__sr:checked + .bga-04__chip {
background: oklch(0.99 0.005 90);
color: oklch(0.22 0.05 30);
border-color: transparent;
}
.bga-04__sr:focus-visible + .bga-04__chip {
outline: 3px solid oklch(0.99 0.005 90);
outline-offset: 3px;
}
.bga-04__spec {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: oklch(1 0 0/.45);
text-wrap: balance;
max-width: 70ch;
}
.bga-04:has(#bga-04-i1:checked) {
--bga-04-op: .16;
}
.bga-04:has(#bga-04-i3:checked) {
--bga-04-op: .75;
}
.bga-04:has(#bga-04-b2:checked) {
--bga-04-blend: soft-light;
}
.bga-04:has(#bga-04-b3:checked) {
--bga-04-blend: multiply;
}
@media (prefers-reduced-motion: reduce) {
.bga-04__grain {
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: Grain & Noise Texture Overlay
Source: https://codefronts.com/motion/css-background-animations/grain-and-noise-texture-overlay/
The texture that makes a flat gradient look printed instead of rendered. An inline SVG feTurbulence filter generates real fractal noise as a data URI — no PNG request, no 200KB tiling image — and a steps() animation jitters it eight times a second for authentic film grain rather than a frozen static layer. Three intensities and three blend modes are switchable so you can dial it against your own art direction.
## HTML
```html
<section class="bga-04" aria-label="Film grain and noise texture overlay background demo">
<div class="bga-04__base" aria-hidden="true">
<img class="bga-04__photo" src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?q=80&w=1800&auto=format&fit=crop" alt="" width="1800" height="1200" loading="lazy" decoding="async">
</div>
<div class="bga-04__grain" aria-hidden="true"></div>
<div class="bga-04__vig" aria-hidden="true"></div>
<div class="bga-04__stage">
<header class="bga-04__head">
<p class="bga-04__eyebrow">Texture · 04</p>
<h1 class="bga-04__title">Grain is the difference between rendered and printed.</h1>
<p class="bga-04__sub">Fractal noise generated by an inline SVG filter, tiled seamlessly, jittered on a six-step clock. No image request, no canvas loop.</p>
</header>
<div class="bga-04__controls" role="group" aria-label="Grain options">
<div class="bga-04__set">
<span class="bga-04__lab">Intensity</span>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i1">
<label class="bga-04__chip" for="bga-04-i1">Whisper</label>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i2" checked>
<label class="bga-04__chip" for="bga-04-i2">35mm</label>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i3">
<label class="bga-04__chip" for="bga-04-i3">Pushed</label>
</div>
<div class="bga-04__set">
<span class="bga-04__lab">Blend</span>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b1" checked>
<label class="bga-04__chip" for="bga-04-b1">overlay</label>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b2">
<label class="bga-04__chip" for="bga-04-b2">soft-light</label>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b3">
<label class="bga-04__chip" for="bga-04-b3">multiply</label>
</div>
</div>
<p class="bga-04__spec">feTurbulence fractalNoise · baseFrequency .82 · numOctaves 4 · stitchTiles stitch · steps(6) jitter</p>
</div>
</section>
```
## CSS
```css
.bga-04,
.bga-04 *,
.bga-04 *::before,
.bga-04 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-04 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-04-op: .42;
--bga-04-blend: overlay;
--bga-04-freq: .82;
--bga-04-ink: oklch(0.99 0.005 90);
background: oklch(0.28 0.06 32);
color: var(--bga-04-ink);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-04__base {
position: absolute;
inset: 0;
background: radial-gradient(90% 70% at 12% 8%,oklch(0.68 0.18 44/.9),transparent 62%), radial-gradient(80% 70% at 88% 22%,oklch(0.52 0.17 12/.85),transparent 64%), radial-gradient(100% 90% at 50% 108%,oklch(0.32 0.1 300/.9),transparent 70%), linear-gradient(160deg,oklch(0.34 0.08 40),oklch(0.2 0.05 300));
}
.bga-04__photo {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
opacity: .5;
mix-blend-mode: luminosity;
}
.bga-04__grain {
position: absolute;
inset: -20%;
pointer-events: none;
opacity: var(--bga-04-op);
mix-blend-mode: var(--bga-04-blend);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
background-size: 220px 220px;
animation: bga-04-jitter .72s steps(6) infinite;
}
@keyframes bga-04-jitter {
0% {
translate: 0 0;
}
16% {
translate: -4% -6%;
}
32% {
translate: 5% -3%;
}
48% {
translate: -3% 5%;
}
64% {
translate: 4% 4%;
}
80% {
translate: -5% 2%;
}
100% {
translate: 0 0;
}
}
.bga-04__vig {
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 100% at 50% 45%,transparent 42%,oklch(0.1 0.03 30/.72) 100%);
}
.bga-04__stage {
position: relative;
z-index: 2;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(22px,3.2vw,36px);
padding: clamp(24px,5vw,72px);
text-align: center;
}
.bga-04__head {
display: grid;
gap: 14px;
justify-items: center;
max-width: 860px;
}
.bga-04__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .24em;
text-transform: uppercase;
color: oklch(0.9 0.08 60/.85);
}
.bga-04__title {
font-size: clamp(30px,6vw,64px);
font-weight: 600;
line-height: 1.03;
letter-spacing: -.05em;
text-wrap: balance;
text-shadow: 0 3px 40px oklch(0.1 0.03 30/.5);
}
.bga-04__sub {
max-width: 54ch;
font-size: clamp(14px,1.35vw,17px);
line-height: 1.58;
color: oklch(0.95 0.02 60/.78);
text-wrap: pretty;
}
.bga-04__controls {
display: flex;
flex-wrap: wrap;
gap: 12px 26px;
justify-content: center;
padding: 14px 20px;
border-radius: 18px;
background: oklch(0.16 0.03 30/.42);
border: 1px solid oklch(1 0 0/.14);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
}
.bga-04__set {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 7px;
}
.bga-04__lab {
font-size: 10.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(1 0 0/.55);
margin-inline-end: 3px;
}
.bga-04__sr {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
.bga-04__chip {
display: inline-flex;
align-items: center;
min-height: 34px;
padding: 0 14px;
border-radius: 99px;
cursor: pointer;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
color: oklch(1 0 0/.75);
background: oklch(1 0 0/.07);
border: 1px solid oklch(1 0 0/.14);
transition: background .25s,color .25s;
}
.bga-04__chip:hover {
background: oklch(1 0 0/.15);
}
.bga-04__sr:checked + .bga-04__chip {
background: oklch(0.99 0.005 90);
color: oklch(0.22 0.05 30);
border-color: transparent;
}
.bga-04__sr:focus-visible + .bga-04__chip {
outline: 3px solid oklch(0.99 0.005 90);
outline-offset: 3px;
}
.bga-04__spec {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: oklch(1 0 0/.45);
text-wrap: balance;
max-width: 70ch;
}
.bga-04:has(#bga-04-i1:checked) {
--bga-04-op: .16;
}
.bga-04:has(#bga-04-i3:checked) {
--bga-04-op: .75;
}
.bga-04:has(#bga-04-b2:checked) {
--bga-04-blend: soft-light;
}
.bga-04:has(#bga-04-b3:checked) {
--bga-04-blend: multiply;
}
@media (prefers-reduced-motion: reduce) {
.bga-04__grain {
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: Grain & Noise Texture Overlay
Source: https://codefronts.com/motion/css-background-animations/grain-and-noise-texture-overlay/
The texture that makes a flat gradient look printed instead of rendered. An inline SVG feTurbulence filter generates real fractal noise as a data URI — no PNG request, no 200KB tiling image — and a steps() animation jitters it eight times a second for authentic film grain rather than a frozen static layer. Three intensities and three blend modes are switchable so you can dial it against your own art direction.
## HTML
```html
<section class="bga-04" aria-label="Film grain and noise texture overlay background demo">
<div class="bga-04__base" aria-hidden="true">
<img class="bga-04__photo" src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?q=80&w=1800&auto=format&fit=crop" alt="" width="1800" height="1200" loading="lazy" decoding="async">
</div>
<div class="bga-04__grain" aria-hidden="true"></div>
<div class="bga-04__vig" aria-hidden="true"></div>
<div class="bga-04__stage">
<header class="bga-04__head">
<p class="bga-04__eyebrow">Texture · 04</p>
<h1 class="bga-04__title">Grain is the difference between rendered and printed.</h1>
<p class="bga-04__sub">Fractal noise generated by an inline SVG filter, tiled seamlessly, jittered on a six-step clock. No image request, no canvas loop.</p>
</header>
<div class="bga-04__controls" role="group" aria-label="Grain options">
<div class="bga-04__set">
<span class="bga-04__lab">Intensity</span>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i1">
<label class="bga-04__chip" for="bga-04-i1">Whisper</label>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i2" checked>
<label class="bga-04__chip" for="bga-04-i2">35mm</label>
<input class="bga-04__sr" type="radio" name="bga-04-i" id="bga-04-i3">
<label class="bga-04__chip" for="bga-04-i3">Pushed</label>
</div>
<div class="bga-04__set">
<span class="bga-04__lab">Blend</span>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b1" checked>
<label class="bga-04__chip" for="bga-04-b1">overlay</label>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b2">
<label class="bga-04__chip" for="bga-04-b2">soft-light</label>
<input class="bga-04__sr" type="radio" name="bga-04-b" id="bga-04-b3">
<label class="bga-04__chip" for="bga-04-b3">multiply</label>
</div>
</div>
<p class="bga-04__spec">feTurbulence fractalNoise · baseFrequency .82 · numOctaves 4 · stitchTiles stitch · steps(6) jitter</p>
</div>
</section>
```
## CSS
```css
.bga-04,
.bga-04 *,
.bga-04 *::before,
.bga-04 *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
.bga-04 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
position: relative;
overflow: hidden;
isolation: isolate;
--bga-04-op: .42;
--bga-04-blend: overlay;
--bga-04-freq: .82;
--bga-04-ink: oklch(0.99 0.005 90);
background: oklch(0.28 0.06 32);
color: var(--bga-04-ink);
font-family: ui-sans-serif,system-ui,-apple-system,'Segoe UI',sans-serif;
-webkit-font-smoothing: antialiased;
}
.bga-04__base {
position: absolute;
inset: 0;
background: radial-gradient(90% 70% at 12% 8%,oklch(0.68 0.18 44/.9),transparent 62%), radial-gradient(80% 70% at 88% 22%,oklch(0.52 0.17 12/.85),transparent 64%), radial-gradient(100% 90% at 50% 108%,oklch(0.32 0.1 300/.9),transparent 70%), linear-gradient(160deg,oklch(0.34 0.08 40),oklch(0.2 0.05 300));
}
.bga-04__photo {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
object-fit: cover;
display: block;
opacity: .5;
mix-blend-mode: luminosity;
}
.bga-04__grain {
position: absolute;
inset: -20%;
pointer-events: none;
opacity: var(--bga-04-op);
mix-blend-mode: var(--bga-04-blend);
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='220' height='220'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='220' height='220' filter='url(%23n)'/%3E%3C/svg%3E");
background-size: 220px 220px;
animation: bga-04-jitter .72s steps(6) infinite;
}
@keyframes bga-04-jitter {
0% {
translate: 0 0;
}
16% {
translate: -4% -6%;
}
32% {
translate: 5% -3%;
}
48% {
translate: -3% 5%;
}
64% {
translate: 4% 4%;
}
80% {
translate: -5% 2%;
}
100% {
translate: 0 0;
}
}
.bga-04__vig {
position: absolute;
inset: 0;
pointer-events: none;
background: radial-gradient(120% 100% at 50% 45%,transparent 42%,oklch(0.1 0.03 30/.72) 100%);
}
.bga-04__stage {
position: relative;
z-index: 2;
min-height: 100vh;
min-height: 100svh;
display: grid;
place-items: center;
align-content: center;
gap: clamp(22px,3.2vw,36px);
padding: clamp(24px,5vw,72px);
text-align: center;
}
.bga-04__head {
display: grid;
gap: 14px;
justify-items: center;
max-width: 860px;
}
.bga-04__eyebrow {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11.5px;
letter-spacing: .24em;
text-transform: uppercase;
color: oklch(0.9 0.08 60/.85);
}
.bga-04__title {
font-size: clamp(30px,6vw,64px);
font-weight: 600;
line-height: 1.03;
letter-spacing: -.05em;
text-wrap: balance;
text-shadow: 0 3px 40px oklch(0.1 0.03 30/.5);
}
.bga-04__sub {
max-width: 54ch;
font-size: clamp(14px,1.35vw,17px);
line-height: 1.58;
color: oklch(0.95 0.02 60/.78);
text-wrap: pretty;
}
.bga-04__controls {
display: flex;
flex-wrap: wrap;
gap: 12px 26px;
justify-content: center;
padding: 14px 20px;
border-radius: 18px;
background: oklch(0.16 0.03 30/.42);
border: 1px solid oklch(1 0 0/.14);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
}
.bga-04__set {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 7px;
}
.bga-04__lab {
font-size: 10.5px;
letter-spacing: .16em;
text-transform: uppercase;
color: oklch(1 0 0/.55);
margin-inline-end: 3px;
}
.bga-04__sr {
position: absolute;
width: 1px;
height: 1px;
opacity: 0;
pointer-events: none;
}
.bga-04__chip {
display: inline-flex;
align-items: center;
min-height: 34px;
padding: 0 14px;
border-radius: 99px;
cursor: pointer;
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 12px;
color: oklch(1 0 0/.75);
background: oklch(1 0 0/.07);
border: 1px solid oklch(1 0 0/.14);
transition: background .25s,color .25s;
}
.bga-04__chip:hover {
background: oklch(1 0 0/.15);
}
.bga-04__sr:checked + .bga-04__chip {
background: oklch(0.99 0.005 90);
color: oklch(0.22 0.05 30);
border-color: transparent;
}
.bga-04__sr:focus-visible + .bga-04__chip {
outline: 3px solid oklch(0.99 0.005 90);
outline-offset: 3px;
}
.bga-04__spec {
font-family: ui-monospace,Menlo,Consolas,monospace;
font-size: 11px;
color: oklch(1 0 0/.45);
text-wrap: balance;
max-width: 70ch;
}
.bga-04:has(#bga-04-i1:checked) {
--bga-04-op: .16;
}
.bga-04:has(#bga-04-i3:checked) {
--bga-04-op: .75;
}
.bga-04:has(#bga-04-b2:checked) {
--bga-04-blend: soft-light;
}
.bga-04:has(#bga-04-b3:checked) {
--bga-04-blend: multiply;
}
@media (prefers-reduced-motion: reduce) {
.bga-04__grain {
animation: none;
}
}
```How this works
The noise is generated, not downloaded. An SVG filter with feTurbulence is inlined as a url("data:image/svg+xml,...") background:
<filter id='n'>
<feTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='4' stitchTiles='stitch'/>
<feColorMatrix type='saturate' values='0'/>
</filter>
<rect width='100%' height='100%' filter='url(%23n)'/>baseFrequency is grain size — lower is coarser, like faster film stock. numOctaves adds fractal detail (2 is cheap and clean, 5 is expensive and organic). stitchTiles='stitch' is the important one: it makes the noise tile seamlessly, so the layer can repeat across any viewport without a visible seam grid.
Static grain looks like a dirty screen. Real grain moves, so the layer is animated with steps() and integer translations — never a smooth transition, which would read as a sliding texture instead of resampled film:
animation: jitter .7s steps(6) infinite;
@keyframes jitter { 10% { translate: -5% -10%; } 30% { translate: 3% -15%; } ... }The plate underneath is a real photograph (blended at luminosity over the gradient, so the gradient still drives the colour and a 404 leaves a perfectly good background). Grain over photography is the actual use case — on a flat fill it reads as screen dirt.
Blending is what integrates it. mix-blend-mode: overlay keeps midtones and adds contrast-aware speckle; soft-light is gentler; multiply darkens like ink on uncoated stock. The layer is scaled to 140% so the translation never exposes an edge.
Because the filter output is cached as an image by the browser, this costs one texture upload and then pure compositing — dramatically cheaper than a canvas noise loop, which many tutorials still recommend.
Make it yours
baseFrequencybetween 0.6 and 0.9 reads as 35mm grain; 0.2-0.4 reads as coarse newsprint; above 1.2 it becomes fine sensor noise.- Add
<feColorMatrix type='saturate' values='1'/>instead of 0 for chromatic colour noise — closer to a pushed high-ISO digital sensor. - Freeze the animation and raise the opacity for print-style paper texture; animate it and drop the opacity for cinema grain.
- Apply the same data URI as a
mask-imageinstead of a background to erode edges of shapes — an instant risograph / letterpress effect.
Gotchas — read before shipping
feTurbulenceis generated on the CPU at paint time. Render it once as a fixed-size tiled layer; never put it on a filter that re-evaluates every frame, and never animatebaseFrequencyin CSS on a full-screen element.- Grain over text reduces legibility at small sizes. Keep the overlay under 20% opacity, or exclude the text container via
isolation: isolate. - Some data-URI SVGs break if the
#in an id is not escaped as%23— this is the most common copy-paste failure of this technique. mix-blend-modeforces a stacking context and can disable GPU rasterisation of what is beneath it in older Safari. Test scroll performance on iOS.- Heavy grain can trigger banding artefacts in video encoders if the page is being screen-recorded — reduce octaves for demo reels.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 114+ | 17.5+ | 121+ | 114+ |
Floor set by :has(), oklch(), backdrop-filter, text-wrap as this demo is written. The core technique itself goes back further — Chrome any modern.
SVG filters as CSS background data URIs, mix-blend-mode and steps() animations are all supported in every engine since 2016. :has() (Chrome 105 / Safari 15.4 / Firefox 121) only powers the option chips; without it the default intensity renders.