An old footage look: fine grain that jitters every few frames, a soft vignette, a faint flicker and the odd scratch. The grain is SVG turbulence rendered once, then moved in steps.
Also known as: film grain overlay css, animated noise background, grain texture animation.
<div class="bgx-film-grain" aria-hidden="true"> <span class="bgx-film-grain-noise"></span> <span class="bgx-film-grain-scratch"></span> </div>
.bgx-film-grain {
--c: var(--bgx-color, #3b82f6);
font-size: var(--bgx-size, 16px);
position: absolute;
inset: 0;
overflow: hidden;
container-type: size;
background: radial-gradient(ellipse 85% 80% at 50% 45%, color-mix(in srgb, var(--c) 32%, #1c1d21), color-mix(in srgb, var(--c) 6%, #060607) 85%);
}
.bgx-film-grain-noise {
position: absolute;
inset: -25%;
background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200' preserveAspectRatio='none'%3E%3Cfilter id='g' x='0' y='0' width='100%' height='100%'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 4 0 0 0 -2.1'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23g)'/%3E%3C/svg%3E") 0 0 / 14em 14em;
opacity: 0.4;
animation: bgx-film-grain-jitter 0.7s steps(1) infinite;
}
.bgx-film-grain-scratch {
position: absolute;
top: 0;
bottom: 0;
left: 0;
width: 1px;
background: linear-gradient(transparent 5%, rgb(255 255 255 / 0.4) 25% 70%, transparent 95%);
opacity: 0;
animation: bgx-film-grain-scratch 3.4s steps(1) infinite;
}
.bgx-film-grain::after {
content: "";
position: absolute;
inset: 0;
background: #000;
opacity: 0;
animation: bgx-film-grain-flicker 1.3s steps(1) infinite;
}
@keyframes bgx-film-grain-jitter {
0% { transform: translate(0, 0); }
10% { transform: translate(-3%, -5%); }
20% { transform: translate(-7%, 2%); }
30% { transform: translate(4%, -8%); }
40% { transform: translate(-2%, 6%); }
50% { transform: translate(-6%, 3%); }
60% { transform: translate(7%, 1%); }
70% { transform: translate(1%, 5%); }
80% { transform: translate(3%, 8%); }
90% { transform: translate(-5%, 4%); }
}
@keyframes bgx-film-grain-scratch {
0% { transform: translateX(23cqw); opacity: 0.9; }
6% { transform: translateX(24cqw); opacity: 0.5; }
12% { opacity: 0; }
50% { transform: translateX(68cqw); opacity: 0.7; }
54% { transform: translateX(67cqw); opacity: 0.35; }
60% { opacity: 0; }
100% { transform: translateX(67cqw); opacity: 0; }
}
@keyframes bgx-film-grain-flicker {
0% { opacity: 0.08; }
20% { opacity: 0; }
35% { opacity: 0.12; }
45% { opacity: 0.03; }
60% { opacity: 0.09; }
80% { opacity: 0; }
90% { opacity: 0.05; }
}
@media (prefers-reduced-motion: reduce) {
.bgx-film-grain, .bgx-film-grain *, .bgx-film-grain::before, .bgx-film-grain::after, .bgx-film-grain *::before, .bgx-film-grain *::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.