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.

Scratch Etch

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

Related Glitch & Distortion effects