Create a parallax effect with background image

by Jordan Sayner

HTML

<div class="spacer">Scroll Down</div>

  <section data-parallax>
    <img src="https://picsum.photos/id/1015/1200/800" alt="Parallax 1">
    <div class="content">Parallax One</div>
  </section>

  <div class="spacer">Spacer</div>

  <section data-parallax>
    <img src="https://picsum.photos/id/1016/1200/800" alt="Parallax 2">
    <div class="content">Parallax Two</div>
  </section>

  <div class="spacer">Another Spacer</div>

  <section data-parallax>
    <img src="https://picsum.photos/id/1018/1200/800" alt="Parallax 3">
    <div class="content">Parallax Three</div>
  </section>

  <div class="spacer">End of Page</div>

CSS

html, body {
      margin: 0;
      padding: 0;
    }

    .spacer {
      height: 100vh;
      display: flex;
      align-items: center;
      justify-content: center;
      font-size: 2rem;
      background: #eee;
    }

    [data-parallax] {
      position: relative;
      height: 100vh;
      overflow: hidden;
    }

    [data-parallax] img {
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 120%;
      object-fit: cover;
      transform: translateY(0);
      will-change: transform;
      z-index: -1;
    }

    .content {
      position: relative;
      z-index: 1;
      padding-top: 40vh;
      text-align: center;
      color: white;
      font-size: 3rem;
      text-shadow: 0 2px 5px rgba(0, 0, 0, 0.6);
    }

JavaScript

const parallaxSections = document.querySelectorAll('[data-parallax]');

    let latestScrollY = 0;
    let ticking = false;

    function updateParallax() {
      parallaxSections.forEach(section => {
        const image = section.querySelector('img');
        const rect = section.getBoundingClientRect();
        const sectionTop = window.scrollY + rect.top;
        const sectionHeight = rect.height;
        const windowHeight = window.innerHeight;

        if (
          latestScrollY + windowHeight > sectionTop &&
          latestScrollY < sectionTop + sectionHeight
        ) {
          const offset = (latestScrollY - sectionTop) * 0.5; // Adjust speed here
          image.style.transform = `translateY(${offset}px)`;
        }
      });

      ticking = false;
    }

    function onScroll() {
      latestScrollY = window.scrollY;

      if (!ticking) {
        requestAnimationFrame(updateParallax);
        ticking = true;
      }
    }

    window.addEventListener('scroll', onScroll);