@manufosela/animation-paralax

@manufosela/animation-paralax

Parallax scrolling effects web component built with Lit 3

Scroll Y: 0px
Sensitivity: 1

Parallax Effect

Scroll down to see the layers move at different speeds

Scroll

About Parallax Scrolling

Parallax scrolling is a web design technique where background images move slower than foreground images, creating an illusion of depth and immersion.

This component supports multiple layers with configurable speeds, vertical and horizontal scrolling directions, and optional mouse-based movement tracking.

Move your mouse over this area

Features

The animation-paralax component offers CSS custom properties for easy theming, respects the prefers-reduced-motion media query for accessibility, and uses IntersectionObserver to pause updates when not visible.

You can add layers dynamically, set custom scroll targets, and combine scroll and mouse tracking for rich interactive experiences.

End of Demo

Thanks for scrolling!

Demo code (CodePen-ready HTML, CSS, JS)
HTML (html)
<div class="page-content">
      <div class="info-box">
        <div>Scroll Y: <span id="scrollY">0</span>px</div>
        <div>Sensitivity: <span id="sensitivityValue">1</span></div>
      </div>

      <div class="controls">
        <label>
          Sensitivity
          <input type="range" id="sensitivitySlider" min="0.1" max="3" step="0.1" value="1" />
        </label>
        <label>
          <input type="checkbox" id="disableToggle" />
          Disable Effect
        </label>
        <label>
          Direction
          <select id="directionSelect">
            <option value="vertical">Vertical</option>
            <option value="horizontal">Horizontal</option>
            <option value="both">Both</option>
          </select>
        </label>
      </div>

      <animation-paralax
        id="mainParalax"
        class="main-paralax"
        direction="vertical"
      >
        <div class="paralax-overlay">
          <h1>Parallax Effect</h1>
          <p>Scroll down to see the layers move at different speeds</p>
        </div>
      </animation-paralax>

      <div class="scroll-indicator">
        <svg viewBox="0 0 24 24">
          <path d="M7.41 8.59L12 13.17l4.59-4.58L18 10l-6 6-6-6 1.41-1.41z"/>
        </svg>
        <span>Scroll</span>
      </div>

      <div class="section">
        <h2>About Parallax Scrolling</h2>
        <p>
          Parallax scrolling is a web design technique where background images move slower
          than foreground images, creating an illusion of depth and immersion.
        </p>
        <p>
          This component supports multiple layers with configurable speeds, vertical and
          horizontal scrolling directions, and optional mouse-based movement tracking.
        </p>
      </div>

      <animation-paralax
        class="mouse-demo"
        mouse-enabled
        direction="both"
        sensitivity="1.5"
      >
        <div class="mouse-demo-content">
          <p>Move your mouse over this area</p>
        </div>
      </animation-paralax>

      <div class="section">
        <h2>Features</h2>
        <p>
          The animation-paralax component offers CSS custom properties for easy theming,
          respects the prefers-reduced-motion media query for accessibility, and uses
          IntersectionObserver to pause updates when not visible.
        </p>
        <p>
          You can add layers dynamically, set custom scroll targets, and combine scroll
          and mouse tracking for rich interactive experiences.
        </p>
      </div>

      <div class="section" style="height: 50vh;">
        <h2>End of Demo</h2>
        <p>Thanks for scrolling!</p>
      </div>
    </div>
CSS (css)
:root {
  --bg: #0c0f14;
  --bg-elevated: #141923;
  --bg-panel: #171d28;
  --border: #262f3f;
  --text: #f4f6fb;
  --text-muted: #a7b0c2;
  --text-dim: #7d879b;
  --accent: #ff8a3d;
  --accent-strong: #ff6a00;
  --accent-soft: rgba(255, 138, 61, 0.16);
  --shadow: 0 20px 50px rgba(5, 8, 14, 0.45);
  --radius-lg: 22px;
  --radius-md: 14px;
  --radius-sm: 10px;
  --max-width: 1160px;
}

<script type="module" src="../src/animation-paralax.js"></script>
    
      * {
        box-sizing: border-box;
        margin: 0;
        padding: 0;
      }

      .page-content {
        min-height: 300vh;
      }

      h1 {
        text-align: center;
        padding: 2rem;
        font-weight: 300;
        letter-spacing: 2px;
        background: rgba(0, 0, 0, 0.5);
      }

      .section {
        padding: 4rem 2rem;
        max-width: 800px;
        margin: 0 auto;
      }

      .section h2 {
        margin-bottom: 1rem;
        font-weight: 400;
      }

      .section p {
        line-height: 1.8;
        color: #ccc;
        margin-bottom: 1rem;
      }

      /* Main parallax section */
      .main-paralax {
        --paralax-height: 100vh;
      }

      .paralax-overlay {
        display: flex;
        flex-direction: column;
        justify-content: center;
        align-items: center;
        height: 100%;
        text-align: center;
        padding: 2rem;
      }

      .paralax-overlay h1 {
        font-size: 4rem;
        font-weight: 200;
        margin-bottom: 1rem;
        text-shadow: 2px 2px 10px rgba(0, 0, 0, 0.5);
        background: transparent;
      }

      .paralax-overlay p {
        font-size: 1.5rem;
        opacity: 0.9;
        max-width: 600px;
        text-shadow: 1px 1px 5px rgba(0, 0, 0, 0.5);
      }

      /* Mouse-enabled demo */
      .mouse-demo {
        --paralax-height: 60vh;
        margin: 2rem 0;
      }

      .mouse-demo-content {
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100%;
        font-size: 2rem;
        background: rgba(0, 0, 0, 0.3);
      }

      /* Controls */
      .controls {
        position: fixed;
        bottom: 20px;
        right: 20px;
        z-index: 100;
        display: flex;
        flex-direction: column;
        gap: 0.5rem;
        padding: 1rem;
        background: rgba(255, 255, 255, 0.1);
        backdrop-filter: blur(10px);
        border-radius: 12px;
      }

      .controls label {
        display: flex;
        align-items: center;
        gap: 0.5rem;
        font-size: 0.9rem;
        cursor: pointer;
      }

      .controls input[type='range'] {
        width: 100px;
        accent-color: #667eea;
      }

      .controls input[type='checkbox'] {
        accent-color: #667eea;
      }

      /* Info box */
      .info-box {
        position: fixed;
        top: 20px;
        left: 20px;
        z-index: 100;
        padding: 1rem;
        background: rgba(0, 0, 0, 0.7);
        border-radius: 8px;
        font-family: monospace;
        font-size: 0.85rem;
      }

      .scroll-indicator {
        position: fixed;
        bottom: 40px;
        left: 50%;
        transform: translateX(-50%);
        display: flex;
        flex-direction: column;
        align-items: center;
        gap: 0.5rem;
        animation: bounce 2s infinite;
      }

      .scroll-indicator svg {
        width: 30px;
        height: 30px;
        fill: white;
      }

      @keyframes bounce {
        0%, 20%, 50%, 80%, 100% {
          transform: translateX(-50%) translateY(0);
        }
        40% {
          transform: translateX(-50%) translateY(-10px);
        }
        60% {
          transform: translateX(-50%) translateY(-5px);
        }
      }

      /* Placeholder images using gradients */
      .layer-bg {
        background: linear-gradient(180deg, var(--bg-panel) 0%, var(--bg-elevated) 50%, #0f3460 100%);
        width: 100%;
        height: 120%;
      }

      .layer-mid {
        background:
          radial-gradient(circle at 20% 30%, rgba(255, 255, 255, 0.1) 0%, transparent 20%),
          radial-gradient(circle at 80% 70%, rgba(255, 255, 255, 0.08) 0%, transparent 15%),
          radial-gradient(circle at 50% 50%, rgba(102, 126, 234, 0.1) 0%, transparent 30%);
        width: 100%;
        height: 120%;
      }

      .layer-fg {
        background:
          radial-gradient(circle at 10% 90%, rgba(255, 255, 255, 0.15) 0%, transparent 10%),
          radial-gradient(circle at 90% 10%, rgba(255, 255, 255, 0.12) 0%, transparent 8%),
          radial-gradient(circle at 30% 40%, rgba(255, 255, 255, 0.1) 0%, transparent 5%),
          radial-gradient(circle at 70% 60%, rgba(255, 255, 255, 0.08) 0%, transparent 4%);
        width: 100%;
        height: 120%;
      }
JS (js)
// Create gradient-based layers for demo (since we don't have actual images)
      const createSvgLayer = (color1, color2) => {
        const svg = `
          <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1920 1080">
            <defs>
              <linearGradient id="grad" x1="0%" y1="0%" x2="0%" y2="100%">
                <stop offset="0%" style="stop-color:${color1};stop-opacity:1" />
                <stop offset="100%" style="stop-color:${color2};stop-opacity:1" />
              </linearGradient>
            </defs>
            <rect width="100%" height="100%" fill="url(#grad)"/>
          </svg>
        `;
        return `data:image/svg+xml,${encodeURIComponent(svg)}`;
      };

      const createStarsLayer = (opacity) => {
        let stars = '';
        for (let i = 0; i < 100; i++) {
          const x = Math.random() * 1920;
          const y = Math.random() * 1080;
          const r = Math.random() * 2 + 0.5;
          stars += `<circle cx="${x}" cy="${y}" r="${r}" fill="white" opacity="${Math.random() * 0.5 + 0.3}"/>`;
        }
        const svg = `
          <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1920 1080">
            <rect width="100%" height="100%" fill="transparent"/>
            ${stars}
          </svg>
        `;
        return `data:image/svg+xml,${encodeURIComponent(svg)}`;
      };

      const mainParalax = document.getElementById('mainParalax');
      const sensitivitySlider = document.getElementById('sensitivitySlider');
      const sensitivityValue = document.getElementById('sensitivityValue');
      const disableToggle = document.getElementById('disableToggle');
      const directionSelect = document.getElementById('directionSelect');
      const scrollYDisplay = document.getElementById('scrollY');

      // Set up layers
      mainParalax.layers = [
        { src: createSvgLayer('#1a1a2e', '#0f3460'), speed: 0.1, type: 'background' },
        { src: createStarsLayer(0.3), speed: 0.3, type: 'midground' },
        { src: createStarsLayer(0.6), speed: 0.6, type: 'foreground' },
      ];

      // Set up layers for mouse demo
      const mouseDemo = document.querySelector('.mouse-demo');
      mouseDemo.layers = [
        { src: createSvgLayer('#2d1b4e', '#1a0a2e'), speed: 0.2, type: 'background' },
        { src: createStarsLayer(0.5), speed: 0.5, type: 'midground' },
        { src: createStarsLayer(0.8), speed: 0.8, type: 'foreground' },
      ];

      sensitivitySlider.addEventListener('input', (e) => {
        const value = parseFloat(e.target.value);
        mainParalax.sensitivity = value;
        sensitivityValue.textContent = value.toFixed(1);
      });

      disableToggle.addEventListener('change', (e) => {
        mainParalax.disabled = e.target.checked;
      });

      directionSelect.addEventListener('change', (e) => {
        mainParalax.direction = e.target.value;
      });

      mainParalax.addEventListener('scroll-update', (e) => {
        scrollYDisplay.textContent = Math.round(e.detail.scrollY);
      });

      // Update scroll display on window scroll
      window.addEventListener('scroll', () => {
        scrollYDisplay.textContent = Math.round(window.scrollY);
      });