effects#glitch#cyberpunk

Glitch Distortion

Aggressive chromatic aberration with RGB channel splitting, scanlines, and random displacement bursts

Implementation Guide
# Glitch Distortion Effect

> Aggressive chromatic aberration with RGB channel splitting, scanlines, and random displacement bursts. A cyberpunk/corrupted data aesthetic perfect for edgy landing pages, game sites, or tech brands.

## Quick Start

```tsx
import GlitchDistortion from '@/components/examples/effects/glitch-distortion';

export default function Hero() {
  return (
    <div className="relative h-screen">
      <GlitchDistortion />
      <div className="relative z-10">{/* Your content goes here */}</div>
    </div>
  );
}
```

## Props

| Prop            | Type                                            | Default                                               | Description                                  |
| --------------- | ----------------------------------------------- | ----------------------------------------------------- | -------------------------------------------- |
| colors          | { red?: string; green?: string; blue?: string } | { red: '#FF3B30', green: '#34C759', blue: '#007AFF' } | RGB channel colors for chromatic split       |
| glitchIntensity | number                                          | 1                                                     | Overall glitch intensity multiplier (0-3)    |
| scanlineOpacity | number                                          | 0.08                                                  | CRT scanline overlay opacity (0-1)           |
| burstFrequency  | number                                          | 0.03                                                  | Probability of glitch bursts per frame (0-1) |

## Full Implementation

```tsx
'use client';

import { useEffect, useRef, useCallback } from 'react';

interface GlitchDistortionProps {
  colors?: {
    red?: string;
    green?: string;
    blue?: string;
  };
  glitchIntensity?: number;
  scanlineOpacity?: number;
  burstFrequency?: number;
}

interface GlitchSlice {
  y: number;
  height: number;
  offsetX: number;
  rgbOffset: number;
  duration: number;
  startTime: number;
}

export default function GlitchDistortion({
  colors = {
    red: '#FF3B30',
    green: '#34C759',
    blue: '#007AFF',
  },
  glitchIntensity = 1,
  scanlineOpacity = 0.08,
  burstFrequency = 0.03,
}: GlitchDistortionProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const animationRef = useRef<number>(0);
  const mouseRef = useRef({ x: 0.5, y: 0.5 });
  const glitchSlicesRef = useRef<GlitchSlice[]>([]);
  const timeRef = useRef(0);
  const lastBurstRef = useRef(0);

  const hexToRgb = useCallback((hex: string) => {
    const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result
      ? {
          r: parseInt(result[1], 16),
          g: parseInt(result[2], 16),
          b: parseInt(result[3], 16),
        }
      : { r: 255, g: 255, b: 255 };
  }, []);

  const createGlitchBurst = useCallback(
    (canvasHeight: number, currentTime: number) => {
      const sliceCount = Math.floor(Math.random() * 8) + 3;
      const newSlices: GlitchSlice[] = [];

      for (let i = 0; i < sliceCount; i++) {
        newSlices.push({
          y: Math.random() * canvasHeight,
          height: Math.random() * 30 + 5,
          offsetX: (Math.random() - 0.5) * 50 * glitchIntensity,
          rgbOffset: (Math.random() - 0.5) * 15 * glitchIntensity,
          duration: Math.random() * 150 + 50,
          startTime: currentTime + Math.random() * 100,
        });
      }

      return newSlices;
    },
    [glitchIntensity]
  );

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const ctx = canvas.getContext('2d', { alpha: false });
    if (!ctx) return;

    let prefersReducedMotion = false;
    const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
    prefersReducedMotion = motionQuery.matches;

    const redColor = hexToRgb(colors.red || '#FF3B30');
    const greenColor = hexToRgb(colors.green || '#34C759');
    const blueColor = hexToRgb(colors.blue || '#007AFF');

    const resizeCanvas = () => {
      const rect = canvas.getBoundingClientRect();
      const dpr = window.devicePixelRatio || 1;
      canvas.width = rect.width * dpr;
      canvas.height = rect.height * dpr;
      ctx.scale(dpr, dpr);
    };

    const drawScanlines = (width: number, height: number, time: number) => {
      ctx.save();

      // CRT scanlines
      const scanlineHeight = 2;
      const flickerIntensity = prefersReducedMotion ? 0 : Math.sin(time * 0.01) * 0.02;

      ctx.fillStyle = `rgba(0, 0, 0, ${scanlineOpacity + flickerIntensity})`;

      for (let y = 0; y < height; y += scanlineHeight * 2) {
        ctx.fillRect(0, y, width, scanlineHeight);
      }

      ctx.restore();
    };

    const drawChromaticAberration = (
      width: number,
      height: number,
      mouseX: number,
      mouseY: number,
      time: number
    ) => {
      const baseOffset = 3 * glitchIntensity;
      const mouseInfluence = prefersReducedMotion ? 0 : 1;

      // Calculate offset based on mouse position
      const distanceFromCenter = Math.sqrt(Math.pow(mouseX - 0.5, 2) + Math.pow(mouseY - 0.5, 2));
      const dynamicOffset = baseOffset + distanceFromCenter * 10 * mouseInfluence * glitchIntensity;

      // Time-based jitter
      const jitterX = prefersReducedMotion ? 0 : Math.sin(time * 0.05) * 2 * glitchIntensity;
      const jitterY = prefersReducedMotion ? 0 : Math.cos(time * 0.07) * 2 * glitchIntensity;

      ctx.save();
      ctx.globalCompositeOperation = 'screen';

      // Red channel (offset left-up)
      ctx.fillStyle = `rgba(${redColor.r}, ${redColor.g}, ${redColor.b}, 0.15)`;
      ctx.fillRect(-dynamicOffset + jitterX, -dynamicOffset + jitterY, width, height);

      // Green channel (center)
      ctx.fillStyle = `rgba(${greenColor.r}, ${greenColor.g}, ${greenColor.b}, 0.1)`;
      ctx.fillRect(jitterX * 0.5, jitterY * 0.5, width, height);

      // Blue channel (offset right-down)
      ctx.fillStyle = `rgba(${blueColor.r}, ${blueColor.g}, ${blueColor.b}, 0.15)`;
      ctx.fillRect(dynamicOffset + jitterX, dynamicOffset + jitterY, width, height);

      ctx.restore();
    };

    const drawGlitchSlices = (width: number, height: number, currentTime: number) => {
      // Clean up expired slices
      glitchSlicesRef.current = glitchSlicesRef.current.filter(
        (slice) => currentTime < slice.startTime + slice.duration
      );

      if (prefersReducedMotion) return;

      ctx.save();

      for (const slice of glitchSlicesRef.current) {
        if (currentTime < slice.startTime) continue;

        const progress = (currentTime - slice.startTime) / slice.duration;
        const easeOut = 1 - Math.pow(1 - progress, 3);
        const currentOffset = slice.offsetX * (1 - easeOut);
        const currentRgbOffset = slice.rgbOffset * (1 - easeOut);

        // Draw displaced slice with RGB split
        ctx.globalCompositeOperation = 'screen';

        // Red slice
        ctx.fillStyle = `rgba(${redColor.r}, 0, 0, 0.3)`;
        ctx.fillRect(currentOffset - currentRgbOffset, slice.y, width, slice.height);

        // Cyan slice (opposite)
        ctx.fillStyle = `rgba(0, ${greenColor.g}, ${blueColor.b}, 0.2)`;
        ctx.fillRect(-currentOffset + currentRgbOffset, slice.y, width, slice.height);
      }

      ctx.restore();
    };

    const drawNoiseLines = (width: number, height: number, time: number) => {
      if (prefersReducedMotion) return;

      ctx.save();
      ctx.globalAlpha = 0.05;

      // Random horizontal noise lines
      const lineCount = Math.floor(Math.random() * 3);
      for (let i = 0; i < lineCount; i++) {
        const y = Math.random() * height;
        const lineWidth = Math.random() * width * 0.3;
        const x = Math.random() * (width - lineWidth);

        ctx.fillStyle = `rgba(255, 255, 255, ${Math.random() * 0.5})`;
        ctx.fillRect(x, y, lineWidth, 1);
      }

      // Occasional full-width flash line
      if (Math.random() < 0.02 * glitchIntensity) {
        const y = Math.random() * height;
        ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
        ctx.fillRect(0, y, width, 2);
      }

      ctx.restore();
    };

    const render = (time: number) => {
      const rect = canvas.getBoundingClientRect();
      const width = rect.width;
      const height = rect.height;

      // Dark background
      const gradient = ctx.createLinearGradient(0, 0, width, height);
      gradient.addColorStop(0, '#0a0a0a');
      gradient.addColorStop(1, '#0f0f1a');
      ctx.fillStyle = gradient;
      ctx.fillRect(0, 0, width, height);

      // Chromatic aberration layer
      drawChromaticAberration(width, height, mouseRef.current.x, mouseRef.current.y, time);

      // Glitch slices
      drawGlitchSlices(width, height, time);

      // Noise lines
      drawNoiseLines(width, height, time);

      // Scanlines overlay
      drawScanlines(width, height, time);

      // Random glitch bursts
      if (!prefersReducedMotion && Math.random() < burstFrequency * glitchIntensity) {
        if (time - lastBurstRef.current > 200) {
          const newSlices = createGlitchBurst(height, time);
          glitchSlicesRef.current = [...glitchSlicesRef.current, ...newSlices];
          lastBurstRef.current = time;
        }
      }
    };

    const animate = (timestamp: number) => {
      timeRef.current = timestamp;
      render(timestamp);

      if (!prefersReducedMotion) {
        animationRef.current = requestAnimationFrame(animate);
      }
    };

    const handleMouseMove = (event: MouseEvent) => {
      const rect = canvas.getBoundingClientRect();
      mouseRef.current = {
        x: (event.clientX - rect.left) / rect.width,
        y: (event.clientY - rect.top) / rect.height,
      };
    };

    const handleMouseLeave = () => {
      mouseRef.current = { x: 0.5, y: 0.5 };
    };

    const handleMotionChange = (event: MediaQueryListEvent) => {
      prefersReducedMotion = event.matches;
      if (prefersReducedMotion) {
        cancelAnimationFrame(animationRef.current);
        glitchSlicesRef.current = [];
        render(timeRef.current);
      } else {
        animationRef.current = requestAnimationFrame(animate);
      }
    };

    motionQuery.addEventListener('change', handleMotionChange);
    canvas.addEventListener('mousemove', handleMouseMove);
    canvas.addEventListener('mouseleave', handleMouseLeave);

    resizeCanvas();

    if (prefersReducedMotion) {
      // Render static version
      render(0);
    } else {
      animationRef.current = requestAnimationFrame(animate);
    }

    const handleResize = () => {
      resizeCanvas();
      if (prefersReducedMotion) {
        render(timeRef.current);
      }
    };

    window.addEventListener('resize', handleResize);

    return () => {
      cancelAnimationFrame(animationRef.current);
      window.removeEventListener('resize', handleResize);
      canvas.removeEventListener('mousemove', handleMouseMove);
      canvas.removeEventListener('mouseleave', handleMouseLeave);
      motionQuery.removeEventListener('change', handleMotionChange);
    };
  }, [colors, glitchIntensity, scanlineOpacity, burstFrequency, hexToRgb, createGlitchBurst]);

  return (
    <canvas
      ref={canvasRef}
      className="h-full w-full"
      style={{
        display: 'block',
        cursor: 'crosshair',
      }}
    />
  );
}
```

## Customization

### Cyberpunk Neon

Bright neon colors with heavy glitching:

```tsx
<GlitchDistortion
  colors={{
    red: '#FF0055',
    green: '#00FF88',
    blue: '#00BBFF',
  }}
  glitchIntensity={1.5}
  burstFrequency={0.05}
/>
```

### Subtle Data Corruption

Minimal effect for professional sites:

```tsx
<GlitchDistortion
  colors={{
    red: '#FF6B6B',
    green: '#4ECDC4',
    blue: '#45B7D1',
  }}
  glitchIntensity={0.5}
  scanlineOpacity={0.04}
  burstFrequency={0.01}
/>
```

### Retro CRT Monitor

Heavy scanlines with classic RGB:

```tsx
<GlitchDistortion
  colors={{
    red: '#FF0000',
    green: '#00FF00',
    blue: '#0000FF',
  }}
  scanlineOpacity={0.15}
  glitchIntensity={0.8}
/>
```

### Aggressive Chaos

Maximum glitch effect:

```tsx
<GlitchDistortion glitchIntensity={3} burstFrequency={0.1} scanlineOpacity={0.1} />
```

### Monochrome Glitch

Single color channel emphasis:

```tsx
<GlitchDistortion
  colors={{
    red: '#FF3366',
    green: '#FF3366',
    blue: '#330011',
  }}
  glitchIntensity={1.2}
/>
```

### Brand Colors Example

Using your brand palette:

```tsx
<GlitchDistortion
  colors={{
    red: '#FF3B30', // Your accent
    green: '#5856D6', // Your secondary
    blue: '#007AFF', // Your primary
  }}
  glitchIntensity={0.8}
/>
```

## Performance Considerations

- **Canvas-based rendering** - Uses hardware-accelerated 2D canvas for smooth 60fps
- **Efficient slice cleanup** - Expired glitch slices are automatically removed from memory
- **Minimal allocations** - Reuses refs to avoid garbage collection pauses
- **Mouse throttling** - Mouse position updates on native events without additional throttling needed
- **Burst cooldown** - Prevents burst spam with 200ms minimum interval between bursts
- **Reduced motion support** - Falls back to static rendering with no animation overhead

### Mobile Optimization

For mobile devices, reduce intensity for better battery life:

```tsx
<GlitchDistortion glitchIntensity={0.5} burstFrequency={0.02} scanlineOpacity={0.05} />
```

### Heavy Usage Warning

High glitch intensity with frequent bursts can be visually intense. Consider user comfort:

```tsx
// Accessible default
<GlitchDistortion glitchIntensity={1} burstFrequency={0.03} />

// NOT recommended for extended viewing
<GlitchDistortion glitchIntensity={3} burstFrequency={0.15} />
```

## Accessibility

- **Respects `prefers-reduced-motion`** - All animations stop completely, showing only static chromatic aberration and scanlines
- **Listens for preference changes** - Dynamically responds if user toggles motion preferences during session
- **No flashing content** - Glitch bursts are color shifts, not bright flashes, to avoid seizure triggers
- **Crosshair cursor** - Visual indicator that the element is interactive
- **Purely decorative** - Effect is background enhancement; ensure content above has proper contrast

### Reduced Motion Behavior

When `prefers-reduced-motion` is enabled:

- No glitch bursts or slice animations
- No time-based jitter on RGB channels
- Scanlines display without flicker
- Static chromatic aberration based on center position

### Photosensitivity Considerations

For users who may be sensitive to visual effects:

```tsx
// Gentler version suitable for broader audiences
<GlitchDistortion glitchIntensity={0.3} burstFrequency={0.005} scanlineOpacity={0.03} />
```

## Browser Support

- **Canvas 2D API** - All modern browsers (Chrome, Firefox, Safari, Edge)
- **requestAnimationFrame** - IE10+ and all modern browsers
- **globalCompositeOperation 'screen'** - Full support in all modern browsers
- **matchMedia** - IE10+ for `prefers-reduced-motion` detection
- **devicePixelRatio** - Full support for retina/HiDPI displays

### Fallback Considerations

For browsers without Canvas support, add a CSS gradient fallback:

```tsx
<div className="relative h-full w-full">
  {/* CSS fallback */}
  <div
    className="absolute inset-0"
    style={{
      background: 'linear-gradient(135deg, #0a0a0a 0%, #0f0f1a 100%)',
    }}
  />
  {/* Canvas glitch overlay */}
  <GlitchDistortion />
</div>
```