Shopify Parallax Scroll Section (Copy-Paste Example, No Apps)

Parallax adds depth and motion to your hero/feature sections: foreground content moves at normal speed while background layers drift more slowly, creating a premium, cinematic feel. Below you’ll find a lightweight parallax section that uses pure HTML, CSS, and a tiny JS helper—no apps, no frameworks, no Liquid. Paste it into a Custom HTML/Liquid block, swap in your assets, and tune the speeds. This is an example for learning and quick shipping; you can later convert it into a Liquid section with schema fields if you need editor controls.

Shopify Parallax Scroll Section
Shopify Parallax Scroll Section

Parallax adds depth and motion to your hero/feature sections: foreground content moves at normal speed while background layers drift more slowly, creating a premium, cinematic feel. Below you’ll find alightweight parallax sectionthat usespure HTML, CSS, and a tiny JS helper—no apps, no frameworks, no Liquid. Paste it into a Custom HTML/Liquid block, swap in your assets, and tune the speeds. This is anexamplefor learning and quick shipping; you can later convert it into a Liquid section with schema fields if you need editor controls.

DESIGNED FOR REFERENCE ONLY
<body>


  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Shopify Parallax Scroll Section</title>
  <style>
    /* ===== Brand tokens (tweak for your store) ===== */
    :root{
      --px-bg: #0f1115;
      --px-fg: #e8ecf1;
      --px-accent: #00d084;
      --px-height: 86vh;         /* section height on desktop */
      --px-height-m: 72vh;       /* section height on mobile */
      --px-radius: 18px;
      --px-shadow: 0 24px 60px rgba(0,0,0,.35);
      --px-overlay: rgba(10,12,18,.35); /* darken for text readability */
    }

    html,body{ margin:0; background:var(--px-bg); color:var(--px-fg); font-family: Arial, sans-serif; }
    .px-section{
      position: relative;
      min-height: var(--px-height);
      overflow: clip;           /* keeps transforms neatly clipped */
      display: grid;
      place-items: center;
      isolation: isolate;       /* stack context so z-index behaves */
    }

    /* Parallax stage */
    .px-stage{
      position: absolute; inset: 0;
      perspective: 1200px; /* not required, but helps for subtle depth if you add 3D later */
    }

    /* Generic parallax layer */
    .px-layer{
      position: absolute; inset: -6%; /* bleed to avoid edges during transforms */
      background-size: cover;
      background-position: center;
      will-change: transform;
      transform: translate3d(0,0,0);
      z-index: 0;
    }

    /* Example images: replace with your Shopify CDN URLs */
    .px-far  { background-image: url('https://picsum.photos/id/1056/2000/1200'); filter: saturate(.85) brightness(.9) contrast(1.05); }
    .px-mid  { background-image: url('https://picsum.photos/id/1018/2000/1200'); mix-blend-mode: normal; opacity: .85; }
    .px-near { background-image: url('https://picsum.photos/id/1003/2000/1200'); opacity: .9; }

    /* Overlay for legible text */
    .px-overlay{
      position:absolute; inset:0; background: var(--px-overlay); z-index: 1;
    }

    /* Foreground content */
    .px-content{
      position: relative; z-index: 2;
      max-width: 1100px; width: min(92vw, 1100px);
      margin: 0 auto;
      display: grid;
      grid-template-columns: 1.1fr .9fr;
      gap: clamp(16px, 3vw, 28px);
      align-items: center;
      padding: clamp(16px, 4vw, 30px);
      border-radius: var(--px-radius);
    }

    .px-copy h1{
      margin:0 0 8px; font-size: clamp(28px, 5.6vw, 56px); line-height: 1.04; letter-spacing:.2px; font-weight: 900;
    }
    .px-copy p{
      margin:0 0 18px; font-size: clamp(14px, 2.2vw, 18px); opacity:.95; max-width: 65ch;
    }
    .px-ctas{ display:flex; gap:12px; flex-wrap:wrap; }
    .btn{
      appearance:none; border:0; border-radius:12px; padding:12px 16px; font-weight:800; cursor:pointer;
      background: var(--px-accent); color:#0f1115;
    }
    .btn--ghost{
      background: transparent; color: var(--px-fg); border:1px solid #273149;
    }

    .px-card{
      background: #151924; color:#e8ecf1; border:1px solid #1f2636;
      border-radius: 16px; box-shadow: var(--px-shadow); overflow:hidden;
    }
    .px-card img{ width:100%; height: 220px; object-fit: cover; display:block; }
    .px-card .pad{ padding: 14px; }
    .px-card h3{ margin: 6px 0 8px; font-size: 18px; }
    .px-card p{ margin:0; font-size:14px; opacity:.9; }

    /* Make the whole thing look nice below */
    .px-spacer{ height: 100vh; background: linear-gradient(180deg, #0f1115, #0b0d12); }

    /* Mobile adjustments */
    @media (max-width: 980px){
      :root{ --px-height: var(--px-height-m); }
      .px-content{ grid-template-columns: 1fr; }
      .px-card img{ height: 200px; }
    }

    /* Reduced motion: disable transforms */
    @media (prefers-reduced-motion: reduce){
      .px-layer{ transform: none !important; }
    }
  </style>



  <!-- PARALLAX SECTION -->
  <section class="px-section" id="pxSection" data-parallax data-speed-far="0.15" data-speed-mid="0.35" data-speed-near="0.6" data-mobile-intensity="0.4" multiplier on mobile>
           data-max-shift="120">         <!-- clamp transform in px -->
    <div class="px-stage" aria-hidden="true">
      <div class="px-layer px-far" data-layer="far"></div>
      <div class="px-layer px-mid" data-layer="mid"></div>
      <div class="px-layer px-near" data-layer="near"></div>
    </div>
    <div class="px-overlay" aria-hidden="true"></div>

    <div class="px-content">
      <div class="px-copy">
        <h1>Depth that Moves With You</h1>
        <p>Create cinematic parallax without apps. Lightweight transforms, smooth on modern devices, and safe for users who prefer reduced motion.</p>
        <div class="px-ctas">
          <button class="btn" onclick="window.location.href='/collections/all'">Shop New</button>
          <button class="btn btn--ghost" onclick="window.location.href='/pages/about'">Learn More</button>
        </div>
      </div>
      <div class="px-card">
        <img src="https://picsum.photos/id/1044/1200/800" alt="Feature image">
        <div class="pad">
          <h3>Featured Capsule</h3>
          <p>Textures, layers, and timeless silhouettes designed for movement.</p>
        </div>
      </div>
    </div>
  </section>

  <!-- Just to demonstrate scrolling past the section -->
  <div class="px-spacer"></div>

  <script>
    (function(){
      const section = document.querySelector('[data-parallax]');
      if(!section) return;

      const far  = section.querySelector('[data-layer="far"]');
      const mid  = section.querySelector('[data-layer="mid"]');
      const near = section.querySelector('[data-layer="near"]');

      const speed = {
        far:  parseFloat(section.getAttribute('data-speed-far'))  || 0.15,
        mid:  parseFloat(section.getAttribute('data-speed-mid'))  || 0.35,
        near: parseFloat(section.getAttribute('data-speed-near')) || 0.6,
      };
      const maxShift = parseFloat(section.getAttribute('data-max-shift')) || 120;
      const mobileIntensity = Math.min(Math.max(parseFloat(section.getAttribute('data-mobile-intensity') || '0.4'), 0), 1);

      const prefersReduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
      if(prefersReduced){ return; }

      let width = window.innerWidth;
      function factor(){ return width < 980 ? mobileIntensity : 1; }

      function clamp(n, min, max){ return Math.max(min, Math.min(n, max)); }

      function tick(){
        const rect = section.getBoundingClientRect();
        const vh = window.innerHeight || document.documentElement.clientHeight;

        // progress: how far the section is through the viewport (-1 off top, 0 enters, 1 centered, 2 leaving)
        const center = rect.top + rect.height/2 - vh/2;
        const norm = clamp(center / (vh/2), -2, 2);

        const mult = factor();

        // translate each layer: negative norm moves up as you scroll down
        const tFar  = clamp(-norm * (speed.far  * 100) * mult, -maxShift, maxShift);
        const tMid  = clamp(-norm * (speed.mid  * 100) * mult, -maxShift, maxShift);
        const tNear = clamp(-norm * (speed.near * 100) * mult, -maxShift, maxShift);

        if(far)  far.style.transform  = 'translate3d(0,' + tFar  + 'px,0)';
        if(mid)  mid.style.transform  = 'translate3d(0,' + tMid  + 'px,0)';
        if(near) near.style.transform = 'translate3d(0,' + tNear + 'px,0)';

        // request next frame during scroll
        raf = requestAnimationFrame(tick);
      }

      // Use a tiny rAF loop only while the section is near the viewport
      let raf = null;
      const io = 'IntersectionObserver' in window ? new IntersectionObserver((entries)=>{
        entries.forEach(entry=>{
          if(entry.isIntersecting){
            if(!raf){ raf = requestAnimationFrame(tick); }
          }else{
            if(raf){ cancelAnimationFrame(raf); raf = null; }
          }
        });
      }, { root: null, rootMargin: '200px 0px 200px 0px' }) : null;

      if(io){ io.observe(section); }
      else { raf = requestAnimationFrame(tick); } // fallback

      window.addEventListener('resize', () => { width = window.innerWidth; }, {passive:true});
    })();
  </script>

</body>

How it works (quick)

  • HTML: A .px-stage holds three .px-layer divs—farmid, and near—plus an overlay for readability and a .px-content grid on top.
  • CSS: Each layer is absolutely positioned and slightly “bleeds” beyond the edges so transforms never reveal gaps. Only transform is animated (GPU-friendly).
  • JS: For the section, we compute a normalized scroll value and translate each layer by different amounts. IntersectionObserver starts/stops a small requestAnimationFrame loop only when the section is near the viewport (extra performance). Mobile intensity is reduced via a multiplier, and prefers-reduced-motion disables transforms entirely.

Customize fast

  • Images: replace the three background-image URLs on .px-far, .px-mid, .px-near.
  • Speeds: tune data-speed-far/mid/near (0.1–0.8 is a good range).
  • Max shift: cap motion with data-max-shift (in px; try 80–160).
  • Mobile intensity: data-mobile-intensity="0.3" to tame the effect on phones.
  • Height: set --px-height and --px-height-m.
  • Overlay strength: edit --px-overlay (e.g., rgba(0,0,0,.45)) for text contrast.
  • Layout: change .px-content grid or swap the right card for a product slider, UGC strip, etc.

Best practices

  • Keep transforms modest (≤120px) to avoid motion sickness.
  • Optimize images (JPG/WebP; 1600–2000px wide is usually enough).
  • Respect reduced motion (already baked in).
  • Avoid heavy scroll listeners; this pattern uses a small rAF loop only when visible.
  • Test against sticky headers; if your header overlaps, add top padding or reduce section height.

Turning this into a Liquid Section (later)

This post uses pure HTML/CSS/JS so anyone can paste it. If you want theme-editor controls, create /sections/parallax-scroll.liquid, move the markup there, and add a JSON schema with settings for the three images, speeds, height, overlay, and copy fields. The parallax logic and classes stay the same—only the content source changes.