VHS
Glitch & Distortion · Animated · pure CSS
VHS chroma-bleed: offset color copies, vertical jitter and scanlines for that worn-tape look. Lo-fi nostalgia for titles.
CSS
/* VHS — generated with TEXT-FX
* HTML: the element needs a data-text attribute equal to its text.
* Font: 'Syne', sans-serif (load from Google Fonts).
*/
.text-effect {
font-family: 'Syne', sans-serif;
font-weight: 700;
letter-spacing: 4px;
text-transform: none;
}
.text-effect {
position: relative;
background: repeating-linear-gradient(0deg, #ededed 0px, #ededed 3px, hsl(0 0% 62%) 3px, hsl(0 0% 62%) 4px);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
background-size: 100% 7px;
filter: drop-shadow(0 0 2px hsl(190 90% 60% / 0.5)) drop-shadow(0 0 6px hsl(190 90% 60% / 0.5));
animation:
text-effect-jitter 0.38s steps(2, end) infinite,
text-effect-scan 3.0s linear infinite;
}
.text-effect::before,
.text-effect::after {
content: attr(data-text);
position: absolute;
left: 0;
top: 0;
width: 100%;
pointer-events: none;
mix-blend-mode: screen;
opacity: 0.85;
}
.text-effect::before {
color: #ff3b30;
transform: translate(-2.7px, 0);
animation: text-effect-bleedR 2.49s ease-in-out infinite;
}
.text-effect::after {
color: #2f9bff;
transform: translate(2.7px, 0);
animation: text-effect-bleedB 3.0s ease-in-out infinite;
}
@keyframes text-effect-jitter {
0% { transform: translateY(0); }
50% { transform: translateY(1.35px); }
100% { transform: translateY(0); }
}
@keyframes text-effect-scan {
0% { background-position: 0 0; }
100% { background-position: 0 7px; }
}
@keyframes text-effect-bleedR {
0%, 100% { transform: translate(-2.7px, 0); }
45% { transform: translate(-4.32px, 1.08px); }
70% { transform: translate(-1.35px, -0.81px); }
}
@keyframes text-effect-bleedB {
0%, 100% { transform: translate(2.7px, 0); }
40% { transform: translate(4.32px, -1.08px); }
75% { transform: translate(1.35px, 0.81px); }
}
HTML
This effect needs the markup below (per-letter spans, SVG defs, or a data-text attribute).
<style>
.text-effect {
font-family: 'Syne', sans-serif;
font-weight: 700;
letter-spacing: 4px;
text-transform: none;
}
.text-effect {
position: relative;
background: repeating-linear-gradient(0deg, #ededed 0px, #ededed 3px, hsl(0 0% 62%) 3px, hsl(0 0% 62%) 4px);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
background-size: 100% 7px;
filter: drop-shadow(0 0 2px hsl(190 90% 60% / 0.5)) drop-shadow(0 0 6px hsl(190 90% 60% / 0.5));
animation:
text-effect-jitter 0.38s steps(2, end) infinite,
text-effect-scan 3.0s linear infinite;
}
.text-effect::before,
.text-effect::after {
content: attr(data-text);
position: absolute;
left: 0;
top: 0;
width: 100%;
pointer-events: none;
mix-blend-mode: screen;
opacity: 0.85;
}
.text-effect::before {
color: #ff3b30;
transform: translate(-2.7px, 0);
animation: text-effect-bleedR 2.49s ease-in-out infinite;
}
.text-effect::after {
color: #2f9bff;
transform: translate(2.7px, 0);
animation: text-effect-bleedB 3.0s ease-in-out infinite;
}
@keyframes text-effect-jitter {
0% { transform: translateY(0); }
50% { transform: translateY(1.35px); }
100% { transform: translateY(0); }
}
@keyframes text-effect-scan {
0% { background-position: 0 0; }
100% { background-position: 0 7px; }
}
@keyframes text-effect-bleedR {
0%, 100% { transform: translate(-2.7px, 0); }
45% { transform: translate(-4.32px, 1.08px); }
70% { transform: translate(-1.35px, -0.81px); }
}
@keyframes text-effect-bleedB {
0%, 100% { transform: translate(2.7px, 0); }
40% { transform: translate(4.32px, -1.08px); }
75% { transform: translate(1.35px, 0.81px); }
}
</style>
<div data-text="Your text" class="text-effect">Your text</div>
- Category
- Glitch & Distortion
- Type
- Animated
- Browser support
- background-clip:text scanlines + pseudo-element chroma copies
- Capabilities
- dataText