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.

Scroll Glitch

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

Related Glitch & Distortion effects