Scratch Etch
Glitch & Distortion · Animated · pure CSS
Filled glyphs are torn ragged by a two-pass feTurbulence displacement chain and unevenly fattened with feMorphology, so every letter looks hand-scraped into a scratchboard. Each glyph runs the same sub-pixel translate/rotate/skew shiver at a different negative delay for an incoherent nervous jitter, punctuated by an occasional over-scratch brightness flash — a live-etched serial-killer title card.
How it works
Scratch Etch is an animated glitch & distortion text effect rendered entirely in CSS. Each character is wrapped in its own span so it can animate independently — the HTML and JSX exports include that per-letter markup. It relies on an inline SVG <defs> block (filters, gradients or clip-paths), which the HTML export carries alongside the CSS.
Controls
Scratch Etch exposes 5 dedicated controls — Roughness, Jitter, Jitter Speed, Ink Hue and Over-scratch Flash — on top of the shared type controls (font, weight, letter-spacing and case). Open it in the generator to tune every value live, then copy the updated CSS.
CSS
/* Scratch Etch — made with TEXT-FX · https://text-fx.app
* HTML: each character is wrapped in a <span> — see the HTML export; requires the inline <svg> filter defs — see the HTML export.
* Font: 'Space Grotesk', sans-serif (load from Google Fonts).
*/
.text-effect {
font-family: 'Space Grotesk', sans-serif;
font-weight: 400;
letter-spacing: 8px;
text-transform: none;
}
.text-effect {
color: hsl(222 26% 87%);
white-space: pre;
animation: text-effect-overscratch 5.5s steps(1, end) infinite;
}
.text-effect .fx-ch {
display: inline-block;
filter: url(#text-effect-etch);
animation: text-effect-shiver 0.42s steps(6, end) infinite;
animation-delay: calc(var(--i) * -73ms);
will-change: transform;
}
@keyframes text-effect-shiver {
0%, 100% { transform: translate(0, 0) rotate(0deg) skewX(0deg); }
17% { transform: translate(-0.96px, 0.64px) rotate(-0.56deg) skewX(1.12deg); }
34% { transform: translate(0.8px, -0.88px) rotate(0.72deg) skewX(-0.8deg); }
50% { transform: translate(-0.56px, 0.96px) rotate(-0.32deg) skewX(0.64deg); }
67% { transform: translate(1.04px, 0.32px) rotate(0.48deg) skewX(-1.04deg); }
84% { transform: translate(-0.8px, -0.56px) rotate(-0.72deg) skewX(0.8deg); }
}
@keyframes text-effect-overscratch {
0%, 90%, 100% { filter: none; }
92% { filter: brightness(1.7) contrast(2.2); }
94% { filter: brightness(0.6) contrast(2.6); }
96% { filter: brightness(1.5) contrast(1.9); }
}
HTML
This effect needs the markup below (per-letter spans, SVG defs, or a data-text attribute).
<!-- Made with TEXT-FX · https://text-fx.app -->
<style>
.text-effect {
font-family: 'Space Grotesk', sans-serif;
font-weight: 400;
letter-spacing: 8px;
text-transform: none;
}
.text-effect {
color: hsl(222 26% 87%);
white-space: pre;
animation: text-effect-overscratch 5.5s steps(1, end) infinite;
}
.text-effect .fx-ch {
display: inline-block;
filter: url(#text-effect-etch);
animation: text-effect-shiver 0.42s steps(6, end) infinite;
animation-delay: calc(var(--i) * -73ms);
will-change: transform;
}
@keyframes text-effect-shiver {
0%, 100% { transform: translate(0, 0) rotate(0deg) skewX(0deg); }
17% { transform: translate(-0.96px, 0.64px) rotate(-0.56deg) skewX(1.12deg); }
34% { transform: translate(0.8px, -0.88px) rotate(0.72deg) skewX(-0.8deg); }
50% { transform: translate(-0.56px, 0.96px) rotate(-0.32deg) skewX(0.64deg); }
67% { transform: translate(1.04px, 0.32px) rotate(0.48deg) skewX(-1.04deg); }
84% { transform: translate(-0.8px, -0.56px) rotate(-0.72deg) skewX(0.8deg); }
}
@keyframes text-effect-overscratch {
0%, 90%, 100% { filter: none; }
92% { filter: brightness(1.7) contrast(2.2); }
94% { filter: brightness(0.6) contrast(2.6); }
96% { filter: brightness(1.5) contrast(1.9); }
}
</style>
<svg width="0" height="0" style="position:absolute" aria-hidden="true"><defs>
<filter id="text-effect-etch" x="-40%" y="-40%" width="180%" height="180%">
<feTurbulence type="fractalNoise" baseFrequency="0.055 0.07" numOctaves="2" seed="7" result="n1"/>
<feTurbulence type="fractalNoise" baseFrequency="0.35 0.3" numOctaves="1" seed="19" result="n2"/>
<feDisplacementMap in="SourceGraphic" in2="n1" scale="6.7" xChannelSelector="R" yChannelSelector="G" result="d1"/>
<feDisplacementMap in="d1" in2="n2" scale="3.35" xChannelSelector="G" yChannelSelector="B" result="d2"/>
<feMorphology in="d2" operator="dilate" radius="0.77" result="fat"/>
<feDisplacementMap in="fat" in2="n1" scale="2.68" xChannelSelector="B" yChannelSelector="R" result="fatshift"/>
<feMerge>
<feMergeNode in="fatshift"/>
<feMergeNode in="d2"/>
</feMerge>
<feComponentTransfer>
<feFuncA type="linear" slope="4" intercept="-0.9"/>
</feComponentTransfer>
</filter>
</defs></svg>
<div class="text-effect"><span class="fx-ch" style="--i:0;--n:9;--rev:8;--mid:4">Y</span><span class="fx-ch" style="--i:1;--n:9;--rev:7;--mid:4">o</span><span class="fx-ch" style="--i:2;--n:9;--rev:6;--mid:4">u</span><span class="fx-ch" style="--i:3;--n:9;--rev:5;--mid:4">r</span><span class="fx-ch" style="--i:4;--n:9;--rev:4;--mid:4"> </span><span class="fx-ch" style="--i:5;--n:9;--rev:3;--mid:4">t</span><span class="fx-ch" style="--i:6;--n:9;--rev:2;--mid:4">e</span><span class="fx-ch" style="--i:7;--n:9;--rev:1;--mid:4">x</span><span class="fx-ch" style="--i:8;--n:9;--rev:0;--mid:4">t</span></div>
- Category
- Glitch & Distortion
- Type
- Animated
- Browser support
- SVG feTurbulence + feDisplacementMap + feMorphology via filter:url(#…); per-letter jitter
- Capabilities
- perLetter, svgDefs