PSX Wobble
Glitch & Distortion · Animated · pure CSS
Per-letter transforms snap between procedurally generated integer-pixel positions on a stepped timing function, the classic PS1 fixed-point vertex jitter. A muddy, hard-banded gradient fills the glyphs and swims independently underneath on its own stepped loop, so the texture visibly slides beneath the snapping outlines.
How it works
PSX Wobble 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.
Controls
PSX Wobble exposes 5 dedicated controls — Steps, Wobble, Jitter Speed, Swim Speed and Palette Hue — 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
/* PSX Wobble — made with TEXT-FX · https://text-fx.app
* HTML: each character is wrapped in a <span> — see the HTML export.
* Font: 'Syne', sans-serif (load from Google Fonts).
*/
.text-effect {
font-family: 'Syne', sans-serif;
font-weight: 700;
letter-spacing: 8px;
text-transform: none;
}
.text-effect {
display: inline-block;
white-space: pre;
transform: skewX(-6deg);
}
.text-effect .fx-ch {
display: inline-block;
background: repeating-linear-gradient(68deg, hsl(171 34% 22%) 0%, hsl(171 34% 22%) 10%, hsl(61 30% 36%) 10%, hsl(61 30% 36%) 26%, hsl(44 38% 56%) 26%, hsl(44 38% 56%) 40%, hsl(48 46% 78%) 40%, hsl(48 46% 78%) 52%, hsl(32 34% 38%) 52%, hsl(32 34% 38%) 70%, hsl(171 34% 22%) 70%, hsl(171 34% 22%) 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
background-size: 220% 260%;
filter: drop-shadow(1.5px 1.5px 0 hsl(140 25% 5%)) drop-shadow(-1px 0 0 hsl(140 25% 5%));
animation:
text-effect-jitter 1.70s steps(1, jump-end) infinite,
text-effect-swim 0.60s steps(1, jump-end) infinite;
animation-delay:
calc(var(--i) * -0.1889s),
calc(var(--i) * -0.126s);
}
@keyframes text-effect-jitter {
0% { transform: translate(2px, 0px) skewX(0deg); }
11.11% { transform: translate(-1px, -2px) skewX(0deg); }
22.22% { transform: translate(-1px, 0px) skewX(1deg); }
33.33% { transform: translate(-1px, 0px) skewX(0deg); }
44.44% { transform: translate(-1px, 2px) skewX(1deg); }
55.56% { transform: translate(0px, -2px) skewX(-1deg); }
66.67% { transform: translate(2px, 1px) skewX(-1deg); }
77.78% { transform: translate(0px, 0px) skewX(-1deg); }
88.89% { transform: translate(0px, -1px) skewX(2deg); }
100% { transform: translate(2px, 0px) skewX(0deg); }
}
@keyframes text-effect-swim {
0% { background-position: 93.3% 64%; }
11.11% { background-position: 76.8% 28%; }
22.22% { background-position: 38.2% 93.4%; }
33.33% { background-position: 64% 10.7%; }
44.44% { background-position: 8.2% 85.5%; }
55.56% { background-position: 71.8% 62.4%; }
66.67% { background-position: 88.4% 7.8%; }
77.78% { background-position: 47.6% 97.5%; }
88.89% { background-position: 95.1% 22.2%; }
100% { background-position: 93.3% 64%; }
}
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: 'Syne', sans-serif;
font-weight: 700;
letter-spacing: 8px;
text-transform: none;
}
.text-effect {
display: inline-block;
white-space: pre;
transform: skewX(-6deg);
}
.text-effect .fx-ch {
display: inline-block;
background: repeating-linear-gradient(68deg, hsl(171 34% 22%) 0%, hsl(171 34% 22%) 10%, hsl(61 30% 36%) 10%, hsl(61 30% 36%) 26%, hsl(44 38% 56%) 26%, hsl(44 38% 56%) 40%, hsl(48 46% 78%) 40%, hsl(48 46% 78%) 52%, hsl(32 34% 38%) 52%, hsl(32 34% 38%) 70%, hsl(171 34% 22%) 70%, hsl(171 34% 22%) 100%);
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
color: transparent;
background-size: 220% 260%;
filter: drop-shadow(1.5px 1.5px 0 hsl(140 25% 5%)) drop-shadow(-1px 0 0 hsl(140 25% 5%));
animation:
text-effect-jitter 1.70s steps(1, jump-end) infinite,
text-effect-swim 0.60s steps(1, jump-end) infinite;
animation-delay:
calc(var(--i) * -0.1889s),
calc(var(--i) * -0.126s);
}
@keyframes text-effect-jitter {
0% { transform: translate(2px, 0px) skewX(0deg); }
11.11% { transform: translate(-1px, -2px) skewX(0deg); }
22.22% { transform: translate(-1px, 0px) skewX(1deg); }
33.33% { transform: translate(-1px, 0px) skewX(0deg); }
44.44% { transform: translate(-1px, 2px) skewX(1deg); }
55.56% { transform: translate(0px, -2px) skewX(-1deg); }
66.67% { transform: translate(2px, 1px) skewX(-1deg); }
77.78% { transform: translate(0px, 0px) skewX(-1deg); }
88.89% { transform: translate(0px, -1px) skewX(2deg); }
100% { transform: translate(2px, 0px) skewX(0deg); }
}
@keyframes text-effect-swim {
0% { background-position: 93.3% 64%; }
11.11% { background-position: 76.8% 28%; }
22.22% { background-position: 38.2% 93.4%; }
33.33% { background-position: 64% 10.7%; }
44.44% { background-position: 8.2% 85.5%; }
55.56% { background-position: 71.8% 62.4%; }
66.67% { background-position: 88.4% 7.8%; }
77.78% { background-position: 47.6% 97.5%; }
88.89% { background-position: 95.1% 22.2%; }
100% { background-position: 93.3% 64%; }
}
</style>
<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
- background-clip:text gradient + stepped transform (all modern, -webkit- prefixed)
- Capabilities
- perLetter