Parallax Layers

Multi-layer parallax effect creating depth on scroll.

Implementation Guide

# Parallax Layers Effect

> Multi-layer parallax effect creating depth on scroll and mouse movement. Layered elements move at different speeds to create a 3D depth illusion.

## Quick Start

```tsx
import ParallaxLayers from '@/components/effects/parallax-layers';

export default function HeroSection() {
return (
    <div className="relative h-screen">
      <ParallaxLayers />
      <div className="relative z-10 flex h-full items-center justify-center">
        <h1 className="text-5xl font-bold text-white">Parallax Effect</h1>
      </div>
    </div>
);
}
```

## Props

| Prop      | Type                       | Default          | Description                     |
| --------- | -------------------------- | ---------------- | ------------------------------- |
| layers    | Layer[]                    | [default layers] | Array of layer configurations   |
| baseSpeed | number                     | 1                | Multiplier for all layer speeds |
| direction | 'vertical' \| 'horizontal' | 'vertical'       | Direction of parallax movement  |

### Layer Configuration

```tsx
interface Layer {
speed: number; // Movement speed multiplier (0.1 = slow, 1.0 = fast)
content: React.ReactNode; // Layer content (JSX)
className?: string; // Additional CSS classes
}
```

## Full Implementation

```tsx
'use client';

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

interface Layer {
speed: number;
content: React.ReactNode;
className?: string;
}

interface ParallaxLayersProps {
layers?: Layer[];
baseSpeed?: number;
direction?: 'vertical' | 'horizontal';
}

const DEFAULT_LAYERS: Layer[] = [\
{\
    speed: 0.1,\
    content: (\
      <div className="absolute inset-0 opacity-20">\
        {Array.from({ length: 20 }).map((_, i) => (\
          <div\
            key={i}\
            className="absolute h-2 w-2 rounded-full bg-white"\
            style={{\
              left: `${Math.random() * 100}%`,\
              top: `${Math.random() * 100}%`,\
            }}\
          />\
        ))}\
      </div>\
    ),\
},\
{\
    speed: 0.3,\
    content: (\
      <div className="absolute inset-0 opacity-30">\
        {Array.from({ length: 15 }).map((_, i) => (\
          <div\
            key={i}\
            className="absolute h-4 w-4 rounded-full bg-blue-500"\
            style={{\
              left: `${Math.random() * 100}%`,\
              top: `${Math.random() * 100}%`,\
            }}\
          />\
        ))}\
      </div>\
    ),\
},\
{\
    speed: 0.5,\
    content: (\
      <div className="absolute inset-0 opacity-40">\
        {Array.from({ length: 10 }).map((_, i) => (\
          <div\
            key={i}\\n            className="absolute h-8 w-8 rounded-full bg-purple-500"\
            style={{\
              left: `${Math.random() * 100}%`,\
              top: `${Math.random() * 100}%`,\
            }}\
          />\
        ))}\
      </div>\
    ),\
},\
{\
    speed: 0.7,\
    content: (\
      <div className="absolute inset-0 opacity-50">\
        {Array.from({ length: 5 }).map((_, i) => (\
          <div\
            key={i}\
            className="absolute h-16 w-16 rounded-full bg-red-500"\
            style={{\
              left: `${Math.random() * 100}%`,\
              top: `${Math.random() * 100}%`,\
            }}\
          />\
        ))}\
      </div>\
    ),\
},\
];

export default function ParallaxLayers({
layers = DEFAULT_LAYERS,
baseSpeed = 1,
direction = 'vertical',\
}: ParallaxLayersProps) {
const containerRef = useRef<HTMLDivElement>(null);
const [scrollProgress, setScrollProgress] = useState(0);

useEffect(() => {
    const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
    if (prefersReducedMotion) return;

const container = containerRef.current;
    if (!container) return;

const handleScroll = () => {
      const rect = container.getBoundingClientRect();
      const windowHeight = window.innerHeight;

// Calculate scroll progress through viewport
      const scrolled = windowHeight - rect.top;
      const totalScrollable = windowHeight + rect.height;
      const progress = Math.max(0, Math.min(1, scrolled / totalScrollable));

setScrollProgress(progress);
    };

// Also respond to mouse movement within container
    const handleMouseMove = (event: MouseEvent) => {
      const rect = container.getBoundingClientRect();
      const x = (event.clientX - rect.left) / rect.width;
      const y = (event.clientY - rect.top) / rect.height;

setScrollProgress(direction === 'vertical' ? y : x);
    };

window.addEventListener('scroll', handleScroll, { passive: true });
    container.addEventListener('mousemove', handleMouseMove);
    handleScroll();

return () => {
      window.removeEventListener('scroll', handleScroll);
      container.removeEventListener('mousemove', handleMouseMove);
    };
}, [direction]);

const getTransform = (speed: number) => {
    const offset = (scrollProgress - 0.5) * 100 * speed * baseSpeed;

if (direction === 'vertical') {
      return `translateY(${offset}px)`;
    }
    return `translateX(${offset}px)`;
};

return (
    <div
      ref={containerRef}
      className="relative h-full w-full overflow-hidden"
      style={{ background: 'linear-gradient(135deg, #0a0a1a 0%, #1a1a3e 50%, #0a0a1a 100%)' }}
    >
      {layers.map((layer, index) => (
        <div
          key={index}
          className={`absolute inset-0 transition-transform duration-100 ease-out ${layer.className || ''}`} 
          style={{
            transform: getTransform(layer.speed),
            zIndex: index,
          }}
        >
          {layer.content}
        </div>
      ))}
    </div>
);
}
```

## Customization

### Custom Layers

Create your own layer content:

```tsx
const customLayers = [\
{\
    speed: 0.2,\
    content: (\
      <div className="absolute inset-0">\
        <img src="/bg-mountains.png" className="h-full w-full object-cover opacity-50" />\
      </div>\
    ),\
},\
{\
    speed: 0.5,\
    content: (\
      <div className="absolute inset-0">\
        <img src="/bg-trees.png" className="h-full w-full object-cover opacity-70" />\
      </div>\
    ),\
},\
{\
    speed: 0.8,\
    content: (\
      <div className="absolute inset-0">\
        <img src="/bg-foreground.png" className="h-full w-full object-cover" />\
      </div>\
    ),\
},\
];

<ParallaxLayers layers={customLayers} />;
```

### Geometric Shapes

```tsx
const geometricLayers = [\
{\
    speed: 0.1,\
    content: (\
      <div className="absolute inset-0">\
        <div className="absolute top-1/4 left-1/4 h-32 w-32 rotate-45 border border-white/20" />\
        <div className="absolute right-1/3 bottom-1/3 h-24 w-24 rounded-full border border-white/20" />\
      </div>\
    ),\
},\
{\
    speed: 0.4,\
    content: (\
      <div className="absolute inset-0">\
        <div className="absolute top-1/2 left-1/2 h-48 w-48 -translate-x-1/2 -translate-y-1/2 border-2 border-blue-500/30" />\
      </div>\
    ),\
},\
];
```

### Horizontal Parallax

For horizontal scrolling sections:

```tsx
<ParallaxLayers direction="horizontal" />
```

### Subtle Effect

For a more subtle background effect:

```tsx
<ParallaxLayers baseSpeed={0.3} />
```

## Performance Considerations

- Use `transform` instead of `top/left` for GPU acceleration
- Limit number of layers (4-6 max recommended)
- Use `passive: true` on scroll listeners
- Add `will-change: transform` on layer elements for smoother animation
- Consider using `requestAnimationFrame` for very smooth animations

## Accessibility

- Respects `prefers-reduced-motion` - parallax effect is disabled
- Ensure text content has sufficient contrast over all layers
- Don't rely on parallax motion to convey important information

## Advanced: Image-based Layers

For landscape/scene parallax:

```tsx
const sceneLayers = [\
{ speed: 0.1, content: <img src="/sky.png" className="..." /> },\
{ speed: 0.2, content: <img src="/mountains-far.png" className="..." /> },\
{ speed: 0.4, content: <img src="/mountains-mid.png" className="..." /> },\
{ speed: 0.6, content: <img src="/trees.png" className="..." /> },\
{ speed: 0.8, content: <img src="/foreground.png" className="..." /> },\
];
```

Tips for image layers:

- Use transparent PNGs
- Ensure images are wider/taller than container to allow movement
- Order by depth (slowest = furthest back)
- Use `object-cover` and `object-position` for responsive sizing