Scroll Glitch
Glitch & Distortion · Animated · pure CSS
Two data-text ghosts — one cyan, one magenta — whose translate offset, vertical shudder and clip-path slices scale with the element's distance from the middle of the viewport, driven by a pure-CSS view() timeline. The text shreds into an RGB tear as it enters and leaves the scrollport, then self-heals to clean, legible type as it crosses the centre.
How it works
Scroll Glitch is an animated glitch & distortion text effect rendered entirely in CSS. A data-text attribute mirrors the word into ::before/::after layers, so copy that attribute together with the CSS. It is driven by a scroll-linked animation timeline.
Controls
Scroll Glitch exposes 3 dedicated controls — Intensity, Hue and Slices — 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
/* Scroll Glitch — made with TEXT-FX · https://text-fx.app
* HTML: the element needs a data-text attribute equal to its text; uses scroll-driven animation.
* Font: 'Archivo Black', sans-serif (load from Google Fonts).
*/
.text-effect {
font-family: 'Archivo Black', sans-serif;
font-weight: 700;
letter-spacing: 0px;
text-transform: none;
}
.text-effect {
position: relative;
color: #f2f4f7;
}
.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.42;
}
.text-effect::before {
color: hsl(300 100% 58%);
transform: translate(-1px, 0);
}
.text-effect::after {
color: hsl(70 100% 58%);
transform: translate(1px, 0);
}
@supports (animation-timeline: view()) {
.text-effect::before {
animation: text-effect-sg1 linear both;
animation-timeline: view();
animation-range: cover 0% cover 100%;
}
.text-effect::after {
animation: text-effect-sg2 linear both;
animation-timeline: view();
animation-range: cover 0% cover 100%;
}
}
@keyframes text-effect-sg1 {
0% { transform: translate(-4.26px, -0.15px); clip-path: inset(24.4% 0 39.5% 0); opacity: 0.9; }
7.14% { transform: translate(-7.88px, 0.1px); clip-path: inset(8.2% 0 24.7% 0); opacity: 0.831; }
14.29% { transform: translate(-2.41px, 0.2px); clip-path: inset(17.2% 0 20% 0); opacity: 0.763; }
21.43% { transform: translate(-4.06px, -0.56px); clip-path: inset(15.6% 0 22.4% 0); opacity: 0.694; }
28.57% { transform: translate(-1.76px, -0.41px); clip-path: inset(15.4% 0 13.4% 0); opacity: 0.626; }
35.71% { transform: translate(-1.65px, -0.44px); clip-path: inset(6.2% 0 9.8% 0); opacity: 0.557; }
42.86% { transform: translate(-1.12px, 0px); clip-path: inset(1.2% 0 1.2% 0); opacity: 0.489; }
50% { transform: translate(0px, 0px); clip-path: inset(0% 0 0% 0); opacity: 0.42; }
57.14% { transform: translate(-1.3px, -0.11px); clip-path: inset(2.6% 0 3.2% 0); opacity: 0.489; }
64.29% { transform: translate(-2.29px, -0.16px); clip-path: inset(11.2% 0 0.3% 0); opacity: 0.557; }
71.43% { transform: translate(-3.26px, 0.02px); clip-path: inset(2% 0 16.9% 0); opacity: 0.626; }
78.57% { transform: translate(-4.44px, -0.11px); clip-path: inset(14.3% 0 9.7% 0); opacity: 0.694; }
85.71% { transform: translate(-5.79px, 0.02px); clip-path: inset(21.4% 0 28.2% 0); opacity: 0.763; }
92.86% { transform: translate(-2.75px, -1.25px); clip-path: inset(12.2% 0 23.2% 0); opacity: 0.831; }
100% { transform: translate(-10.46px, 1.17px); clip-path: inset(26.2% 0 12.5% 0); opacity: 0.9; }
}
@keyframes text-effect-sg2 {
0% { transform: translate(4.35px, -1.28px); clip-path: inset(8.3% 0 30.2% 0); opacity: 0.9; }
7.14% { transform: translate(3.58px, 1px); clip-path: inset(2.3% 0 2.2% 0); opacity: 0.831; }
14.29% { transform: translate(3.45px, 1.13px); clip-path: inset(15.6% 0 14.4% 0); opacity: 0.763; }
21.43% { transform: translate(4.52px, -0.03px); clip-path: inset(1.9% 0 22.6% 0); opacity: 0.694; }
28.57% { transform: translate(2.58px, 0.13px); clip-path: inset(4.1% 0 1.6% 0); opacity: 0.626; }
35.71% { transform: translate(0.9px, -0.41px); clip-path: inset(0.7% 0 0.4% 0); opacity: 0.557; }
42.86% { transform: translate(0.32px, 0.22px); clip-path: inset(3.4% 0 2.4% 0); opacity: 0.489; }
50% { transform: translate(0px, 0px); clip-path: inset(0% 0 0% 0); opacity: 0.42; }
57.14% { transform: translate(1.04px, 0.01px); clip-path: inset(2.2% 0 4.1% 0); opacity: 0.489; }
64.29% { transform: translate(2.25px, 0.11px); clip-path: inset(11.3% 0 1.5% 0); opacity: 0.557; }
71.43% { transform: translate(0.89px, 0.23px); clip-path: inset(14.7% 0 4.6% 0); opacity: 0.626; }
78.57% { transform: translate(5.47px, 0.83px); clip-path: inset(12.8% 0 21.6% 0); opacity: 0.694; }
85.71% { transform: translate(3.54px, -0.72px); clip-path: inset(5.5% 0 12.1% 0); opacity: 0.763; }
92.86% { transform: translate(8.48px, 0.81px); clip-path: inset(16.2% 0 21.5% 0); opacity: 0.831; }
100% { transform: translate(3.22px, -1.3px); clip-path: inset(18.8% 0 14.8% 0); opacity: 0.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: 'Archivo Black', sans-serif;
font-weight: 700;
letter-spacing: 0px;
text-transform: none;
}
.text-effect {
position: relative;
color: #f2f4f7;
}
.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.42;
}
.text-effect::before {
color: hsl(300 100% 58%);
transform: translate(-1px, 0);
}
.text-effect::after {
color: hsl(70 100% 58%);
transform: translate(1px, 0);
}
@supports (animation-timeline: view()) {
.text-effect::before {
animation: text-effect-sg1 linear both;
animation-timeline: view();
animation-range: cover 0% cover 100%;
}
.text-effect::after {
animation: text-effect-sg2 linear both;
animation-timeline: view();
animation-range: cover 0% cover 100%;
}
}
@keyframes text-effect-sg1 {
0% { transform: translate(-4.26px, -0.15px); clip-path: inset(24.4% 0 39.5% 0); opacity: 0.9; }
7.14% { transform: translate(-7.88px, 0.1px); clip-path: inset(8.2% 0 24.7% 0); opacity: 0.831; }
14.29% { transform: translate(-2.41px, 0.2px); clip-path: inset(17.2% 0 20% 0); opacity: 0.763; }
21.43% { transform: translate(-4.06px, -0.56px); clip-path: inset(15.6% 0 22.4% 0); opacity: 0.694; }
28.57% { transform: translate(-1.76px, -0.41px); clip-path: inset(15.4% 0 13.4% 0); opacity: 0.626; }
35.71% { transform: translate(-1.65px, -0.44px); clip-path: inset(6.2% 0 9.8% 0); opacity: 0.557; }
42.86% { transform: translate(-1.12px, 0px); clip-path: inset(1.2% 0 1.2% 0); opacity: 0.489; }
50% { transform: translate(0px, 0px); clip-path: inset(0% 0 0% 0); opacity: 0.42; }
57.14% { transform: translate(-1.3px, -0.11px); clip-path: inset(2.6% 0 3.2% 0); opacity: 0.489; }
64.29% { transform: translate(-2.29px, -0.16px); clip-path: inset(11.2% 0 0.3% 0); opacity: 0.557; }
71.43% { transform: translate(-3.26px, 0.02px); clip-path: inset(2% 0 16.9% 0); opacity: 0.626; }
78.57% { transform: translate(-4.44px, -0.11px); clip-path: inset(14.3% 0 9.7% 0); opacity: 0.694; }
85.71% { transform: translate(-5.79px, 0.02px); clip-path: inset(21.4% 0 28.2% 0); opacity: 0.763; }
92.86% { transform: translate(-2.75px, -1.25px); clip-path: inset(12.2% 0 23.2% 0); opacity: 0.831; }
100% { transform: translate(-10.46px, 1.17px); clip-path: inset(26.2% 0 12.5% 0); opacity: 0.9; }
}
@keyframes text-effect-sg2 {
0% { transform: translate(4.35px, -1.28px); clip-path: inset(8.3% 0 30.2% 0); opacity: 0.9; }
7.14% { transform: translate(3.58px, 1px); clip-path: inset(2.3% 0 2.2% 0); opacity: 0.831; }
14.29% { transform: translate(3.45px, 1.13px); clip-path: inset(15.6% 0 14.4% 0); opacity: 0.763; }
21.43% { transform: translate(4.52px, -0.03px); clip-path: inset(1.9% 0 22.6% 0); opacity: 0.694; }
28.57% { transform: translate(2.58px, 0.13px); clip-path: inset(4.1% 0 1.6% 0); opacity: 0.626; }
35.71% { transform: translate(0.9px, -0.41px); clip-path: inset(0.7% 0 0.4% 0); opacity: 0.557; }
42.86% { transform: translate(0.32px, 0.22px); clip-path: inset(3.4% 0 2.4% 0); opacity: 0.489; }
50% { transform: translate(0px, 0px); clip-path: inset(0% 0 0% 0); opacity: 0.42; }
57.14% { transform: translate(1.04px, 0.01px); clip-path: inset(2.2% 0 4.1% 0); opacity: 0.489; }
64.29% { transform: translate(2.25px, 0.11px); clip-path: inset(11.3% 0 1.5% 0); opacity: 0.557; }
71.43% { transform: translate(0.89px, 0.23px); clip-path: inset(14.7% 0 4.6% 0); opacity: 0.626; }
78.57% { transform: translate(5.47px, 0.83px); clip-path: inset(12.8% 0 21.6% 0); opacity: 0.694; }
85.71% { transform: translate(3.54px, -0.72px); clip-path: inset(5.5% 0 12.1% 0); opacity: 0.763; }
92.86% { transform: translate(8.48px, 0.81px); clip-path: inset(16.2% 0 21.5% 0); opacity: 0.831; }
100% { transform: translate(3.22px, -1.3px); clip-path: inset(18.8% 0 14.8% 0); opacity: 0.9; }
}
</style>
<div data-text="Your text" class="text-effect">Your text</div>
- Category
- Glitch & Distortion
- Type
- Animated
- Browser support
- Scroll-scrubbed in Chromium & Safari; static elsewhere
- Capabilities
- dataText, scroll