---
name: glitch-image-effect
description: Use when you want "Cyber, edgy, futuristic" - Applies digital distortion, offset channels, or broken-signal animation.
---

# Glitch Image Effect

> **Category:** Image / Media  -  **Personality:** Cyber, edgy, futuristic
>
> **Best use:** Cybersecurity, gaming, AI
>
> **Ratings:** Professional 2/5 - Casual 3/5 - Exotic 5/5 - Visual impact 5/5 - Difficulty 4/5 - Performance cost 4/5

## What it is

A digital-glitch / signal-corruption treatment for a **photo** (not text). The image is stacked three times: two copies have their colour channels isolated (red vs. cyan) and are nudged apart horizontally for a chromatic-aberration "RGB split", while a third copy is clipped into thin horizontal bands and shoved sideways for "torn signal" displacement. Add scanlines and it reads as a corrupted CCTV / broken video feed. Use it for cyber, gaming and AI contexts — surveillance HUDs, security dashboards, hacker/streamer aesthetics — where you want an image to feel unstable and live. (This is the image-based sibling of "Glitch Text"; the technique here is channel separation on pixels, not on letterforms.)

## Dependencies / CDN

**None — pure CSS + a tiny inline SVG filter + vanilla JS.** Two `<filter>` elements (channel isolation) live inline in the markup; no library is loaded. The demo uses two display/body fonts (optional):

```html
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Archivo+Expanded:wght@700;800&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
```

## HTML

Inline the two channel-isolation filters once, then stack three copies of the **same** image over a black backing. The slice copy is the one that tears.

```html
<!-- channel-isolation matrices: keep R only / keep G+B only -->
<svg width="0" height="0" aria-hidden="true" focusable="false">
  <filter id="gie-red"  color-interpolation-filters="sRGB">
    <feColorMatrix type="matrix" values="1 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 1 0"/>
  </filter>
  <filter id="gie-cyan" color-interpolation-filters="sRGB">
    <feColorMatrix type="matrix" values="0 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 1 0"/>
  </filter>
</svg>

<figure class="gie-monitor" id="gieMonitor" tabindex="0"
        aria-label="Surveillance still under AI analysis. Hover or focus to corrupt the signal.">
  <div class="gie-img" aria-hidden="true">          <!-- black backing inside -->
    <img class="gie-layer gie-cyan"  src="/img/street-01.jpg" alt="" draggable="false">
    <img class="gie-layer gie-red"   src="/img/street-01.jpg" alt="" draggable="false">
    <img class="gie-layer gie-slice" src="/img/street-01.jpg" alt="" draggable="false">
  </div>
  <div class="gie-scan" aria-hidden="true"></div>   <!-- scanlines + sweep -->
  <figcaption class="gie-sr">City-street surveillance still breaking up into split RGB channels and torn scan-lines.</figcaption>
</figure>
```

## CSS

```css
/* container creates an isolated stacking context so the screen-blend
   only mixes the layers + the black backing, never the page behind. */
.gie-monitor{position:relative;border-radius:14px;overflow:hidden;aspect-ratio:16/10;
  background:#02040a;isolation:isolate;cursor:crosshair}
.gie-img{position:absolute;inset:0;background:#000}            /* the backing screen blends onto */
.gie-layer{position:absolute;inset:0;width:100%;height:100%;object-fit:cover}

/* 1) RGB SPLIT — isolate channels, then recombine with screen blend.
   Aligned: red(R,0,0) screen cyan(0,G,B) over black == original colour. */
.gie-cyan{filter:url(#gie-cyan) contrast(1.06);mix-blend-mode:screen;
  animation:gie-cyan-amb 8s steps(1,end) infinite}
.gie-red {filter:url(#gie-red)  contrast(1.06);mix-blend-mode:screen;
  animation:gie-red-amb  8s steps(1,end) infinite}

/* 2) SLICE DISPLACEMENT — a full-colour copy, hidden until torn. */
.gie-slice{opacity:0;filter:saturate(1.25) contrast(1.12);
  animation:gie-slice-amb 9s steps(1,end) infinite}

/* ambient: clean ~90% of the time, brief jolts so it "lives" at rest */
@keyframes gie-red-amb {0%,90%,100%{transform:translate(0,0)}
  93%{transform:translate(3px,-1px)}95%{transform:translate(-3px,1px)}}
@keyframes gie-cyan-amb{0%,90%,100%{transform:translate(0,0)}
  93%{transform:translate(-3px,1px)}95%{transform:translate(3px,-1px)}}
@keyframes gie-slice-amb{0%,82%,100%{opacity:0;clip-path:inset(50% 0 50% 0);transform:translateX(0)}
  84%{opacity:.92;clip-path:inset(26% 0 64% 0);transform:translateX(-11px)}
  87%{opacity:.92;clip-path:inset(62% 0 26% 0);transform:translateX(13px)}}

/* burst: while hovered/focused (.is-glitching) — fast, hard-cut, intense */
.gie-monitor.is-glitching .gie-red  {animation:gie-red-burst  .42s steps(1,end) infinite}
.gie-monitor.is-glitching .gie-cyan {animation:gie-cyan-burst .42s steps(1,end) infinite}
.gie-monitor.is-glitching .gie-slice{opacity:1;animation:gie-slice-burst .42s steps(1,end) infinite}
@keyframes gie-red-burst {0%{transform:translate(-7px,0)}40%{transform:translate(-9px,1px)}
  60%{transform:translate(4px,0)}100%{transform:translate(8px,-1px)}}
@keyframes gie-cyan-burst{0%{transform:translate(7px,0)}40%{transform:translate(9px,-1px)}
  60%{transform:translate(-4px,0)}100%{transform:translate(-8px,1px)}}
@keyframes gie-slice-burst{0%{clip-path:inset(8% 0 80% 0);transform:translateX(-14px)}
  36%{clip-path:inset(70% 0 12% 0);transform:translateX(-9px)}
  54%{clip-path:inset(26% 0 60% 0);transform:translateX(20px)}
  100%{clip-path:inset(40% 0 46% 0);transform:translateX(-6px)}}

/* scanlines + moving sweep */
.gie-scan{position:absolute;inset:0;pointer-events:none;opacity:.6;
  background:repeating-linear-gradient(180deg,transparent 0 2px,rgba(0,0,0,.34) 2px 3px)}
.gie-scan::after{content:"";position:absolute;left:0;right:0;top:-24%;height:24%;
  background:linear-gradient(180deg,transparent,rgba(61,240,255,.16),transparent);
  animation:gie-sweep 5.2s linear infinite}
@keyframes gie-sweep{to{top:104%}}

/* reduced motion = clean static still: kill animation, drop the torn slice */
@media (prefers-reduced-motion: reduce){
  .gie-red,.gie-cyan,.gie-slice,.gie-scan::after{animation:none!important}
  .gie-red,.gie-cyan{transform:none!important}
  .gie-slice,.gie-scan::after{display:none!important}
}
```

## JavaScript

Optional — the CSS already glitches on its own and on `:hover`. JS only adds organic random bursts, a sustained burst on hover/focus, and live HUD numbers. **Everything is gated behind `prefers-reduced-motion`.**

```js
var monitor=document.getElementById('gieMonitor');
var reduce=matchMedia('(prefers-reduced-motion: reduce)').matches;
var hold;
function on(){ monitor.classList.add('is-glitching'); }
function off(){ monitor.classList.remove('is-glitching'); }
function burst(ms){ on(); clearTimeout(hold); hold=setTimeout(off,ms); }

if(monitor && !reduce){
  monitor.addEventListener('pointerenter',on);
  monitor.addEventListener('pointerleave',function(){ clearTimeout(hold); off(); });
  monitor.addEventListener('focus',on);
  monitor.addEventListener('blur',off);
  (function loop(){ setTimeout(function(){            // random idle bursts
    if(!monitor.matches(':hover')) burst(160+Math.random()*220);
    loop();
  },2200+Math.random()*3200); })();
}
```

## How it works

- **Channel isolation:** `feColorMatrix` zeroes two of the three channels. `gie-red` keeps R; `gie-cyan` keeps G+B (green+blue = cyan). `color-interpolation-filters="sRGB"` stops the browser's default linear-RGB pass from shifting brightness.
- **Recombination:** both isolated layers use `mix-blend-mode:screen` over a **black** backing. Screen of `(R,0,0)` and `(0,G,B)` is exactly `(R,G,B)` — so when the layers are **aligned** you see the original photo. Offset them a few pixels in opposite directions and the channels fringe apart: that is the RGB split / chromatic aberration.
- **Slice displacement:** a third full-colour copy is normally `opacity:0`. Keyframes set `clip-path: inset(top 0 bottom 0)` to expose one thin horizontal band at a time and `translateX()` to shove it — the band looks "torn" out of the frame and slid sideways.
- **Two intensities:** gentle ambient keyframes run forever (mostly at rest); adding `.is-glitching` swaps in faster, larger `*-burst` keyframes with `steps(1,end)` so the motion hard-cuts between frames instead of easing — that staccato is what sells "broken signal".
- **Scanlines** are a `repeating-linear-gradient`; a translucent gradient band sweeps top-to-bottom for the CRT roll.

## Customization

- **Pick the image:** any photo works; high-contrast scenes (street, portrait) show the split best. Swap the `src` on all three layers (keep them identical).
- **Split strength:** scale the `translate()` values in the `*-burst` keyframes (±4px subtle → ±14px violent). Add a small `translateY` for vertical jitter.
- **Different channels:** swap which matrix keeps which channel for magenta/green or yellow/blue fringing instead of red/cyan.
- **Slice character:** more keyframe stops = busier tearing; widen the visible band by shrinking the inset top+bottom sum.
- **Trigger:** drop the JS and the effect still fires purely on `:hover` (`.gie-monitor:hover` instead of `.is-glitching`), or trigger `.is-glitching` from a button / IntersectionObserver / audio beat.
- **Speed/feel:** keep `steps(1,end)` for a digital stutter; switch to `linear` for a smoother smear.

## Accessibility & performance

- **Reduced motion:** the `@media (prefers-reduced-motion: reduce)` block stops every animation, removes the offsets and hides the torn slice, leaving the recombined image perfectly clean. The JS also bails early, so no loops or HUD churn run.
- **Alt text:** the three stacked `<img>` are decorative duplicates (`alt=""`) inside an `aria-hidden` wrapper; the real description lives in a visually-hidden `<figcaption>` (or an `aria-label` on the figure) so a screen reader hears the scene once, not three times.
- **GPU:** animate only `transform` / `clip-path` / `opacity` (compositor-friendly); never animate the `filter` radius. Three layers of one cached image decode is cheap. Keep the effect to a few elements, not a whole gallery at once.
- **Flashing:** keep the cadence calm and offsets modest — rapid high-contrast strobing can trigger photosensitivity. The ambient loop here sits at rest most of the time by design.

## Gotchas

- **Needs a black backing.** `screen` over a non-black background washes the colours out; put the layers over `#000` (here `.gie-img{background:#000}`) and recombination is exact.
- **`isolation:isolate` is mandatory** on the container, or `mix-blend-mode:screen` will blend the layers with the page behind the demo instead of just each other.
- **`color-interpolation-filters="sRGB"`** — without it the SVG filter runs in linear RGB and the recombined image looks too bright/desaturated.
- **All three `src` must be identical** (same file, same `object-fit`/size) or the channels won't line up at rest.
- **`steps(1,end)` is intentional.** A normal eased animation makes the split glide smoothly and looks like motion blur, not a glitch.
- This is the **image** glitch — for headline/logo text use the `::before/::after` `data-text` + `clip-path` "Glitch Text" recipe instead; channel-isolating a photo and clip-slicing a word are different mechanisms.
