Shopify 3d scrolling

Hello! I have made a video, and want to use it on the image banner on sh Shopify website. The Question is how can I put it there on the image banner and also when I scroll down it play the video forward and when scrolling up, it plays backwards?

Your requirements require custom code. So you will have to edit the image banner to include video as well.

And to play video back/forth on scroll will need js.

Best

Perfect, and the question is, how do I do that, any solutions eventually?

HEY @Vold you can also use the video in to a GIF format it will auto play with out any scrolling
first convert your video in to a GIF format and then place this GIF in to the image banner side
that’s it

best

As mentioned this will require custom code? What theme are you using?

If it m’s any of the free themes offered by Shopify I can do it on my end and send u images or will need Collab access to the store.

Best

I have coded a simple ‘proof of concept’ solution, but, of course, it’s a relatively complex task and would need a fair bit of testing to be reliable to use on a website. This is a good head-start though.

  1. Go to Shopify Admin > Online Store > Edit theme
  2. Scroll down to the Sections folder and click Add a new section.
  3. Name the file video-play-on-scroll and click Done.
  4. Paste the code & save.
  5. Go to the Theme Editor (Customize), click Add section

{% schema %}
{
  "name": "Scroll-Driven Video",
  "settings": [
    {
      "type": "header",
      "content": "Media Settings"
    },
    {
      "type": "video",
      "id": "video_file",
      "label": "Video",
      "info": "MP4 format recommended. Logic relies on scroll position."
    },
    {
      "type": "image_picker",
      "id": "fallback_image",
      "label": "Cover Image",
      "info": "Shown while video loads or if video fails."
    },
    {
      "type": "select",
      "id": "video_preload",
      "label": "Video Preload",
      "options": [
        { "value": "none", "label": "None" },
        { "value": "metadata", "label": "Metadata Only" },
        { "value": "auto", "label": "Auto" }
      ],
      "default": "metadata"
    },
    {
      "type": "header",
      "content": "Layout & Design"
    },
    {
      "type": "range",
      "id": "section_height",
      "min": 400,
      "max": 1000,
      "step": 50,
      "unit": "px",
      "label": "Desktop Height",
      "default": 600
    },
    {
      "type": "range",
      "id": "mobile_height",
      "min": 300,
      "max": 800,
      "step": 50,
      "unit": "px",
      "label": "Mobile Height",
      "default": 400
    },
    {
      "type": "checkbox",
      "id": "full_width",
      "label": "Full Width Section",
      "default": true
    },
    {
      "type": "header",
      "content": "Scroll Logic"
    },
    {
      "type": "range",
      "id": "sensitivity",
      "min": 0.5,
      "max": 2.0,
      "step": 0.1,
      "label": "Scroll Sensitivity",
      "default": 1.0,
      "info": "Multiplies the scrubbing speed."
    },
    {
      "type": "header",
      "content": "Overlay Content"
    },
    {
      "type": "text",
      "id": "heading",
      "label": "Heading"
    },
    {
      "type": "richtext",
      "id": "text",
      "label": "Text"
    }
  ],
  "presets": [
    {
      "name": "Scroll-Driven Video Banner"
    }
  ]
}
{% endschema %}

{% style %}
  #shopify-section-{{ section.id }} .scroll-video-wrapper {
    position: relative;
    overflow: hidden;
    height: {{ section.settings.section_height }}px;
    width: {% if section.settings.full_width %}100%{% else %}var(--page-width, 1200px){% endif %};
    margin: 0 auto;
    background-color: #000;
  }

  #shopify-section-{{ section.id }} .scroll-video-element {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 1;
    display: block;
    /* Hardware acceleration hints */
    will-change: transform; 
    transform: translateZ(0);
  }

  #shopify-section-{{ section.id }} .scroll-video-fallback {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    z-index: 2; /* Sits on top until video loads */
    background-size: cover;
    background-position: center;
    transition: opacity 0.5s ease-out;
    pointer-events: none;
  }

  /* Class to hide fallback once video is ready */
  #shopify-section-{{ section.id }} .scroll-video-fallback.is-hidden {
    opacity: 0;
    z-index: 0;
  }

  #shopify-section-{{ section.id }} .scroll-video-content {
    position: relative;
    z-index: 3;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    color: #fff;
    padding: 20px;
    background: rgba(0,0,0,0.2);
    pointer-events: none; /* Allow scrolling through text area on touch devices */
  }

  #shopify-section-{{ section.id }} .scroll-video-content h2 {
    color: #fff;
    margin-bottom: 10px;
    font-size: 2.5rem;
  }

  /* Mobile Styles */
  @media screen and (max-width: 767px) {
    #shopify-section-{{ section.id }} .scroll-video-wrapper {
      height: {{ section.settings.mobile_height }}px;
    }
    
    /* Ensure video is visible on mobile */
    #shopify-section-{{ section.id }} .scroll-video-element {
      display: block;
    }
  }
{% endstyle %}

<div class="scroll-video-wrapper" data-sensitivity="{{ section.settings.sensitivity }}" id="ScrollVideo-{{ section.id }}">
  {%- if section.settings.video_file != blank -%}
    {{ section.settings.video_file | video_tag:
      image_size: '1200x',
      class: 'scroll-video-element',
      muted: true,
      playsinline: true,
      loop: true,
      controls: false,
      preload: section.settings.video_preload,
      id: "VideoElement"
    }}
  {%- endif -%}

  {%- if section.settings.fallback_image != blank -%}
    <div class="scroll-video-fallback" style="background-image: url('{{ section.settings.fallback_image | image_url: width: 1600 }}');"></div>
  {%- endif -%}

  {%- if section.settings.heading != blank or section.settings.text != blank -%}
    <div class="scroll-video-content">
      {%- if section.settings.heading != blank -%}
        <h2>{{ section.settings.heading }}</h2>
      {%- endif -%}
      {%- if section.settings.text != blank -%}
        <div class="rte">{{ section.settings.text }}</div>
      {%- endif -%}
    </div>
  {%- endif -%}
</div>

<script>
  (function() {
    const container = document.getElementById('ScrollVideo-{{ section.id }}');
    const video = container.querySelector('video');
    const fallback = container.querySelector('.scroll-video-fallback');
    
    // Check if video exists
    if (!video) return;

    // Config
    const sensitivity = parseFloat(container.dataset.sensitivity) || 1.0;
    let isVideoLoaded = false;
    let ticking = false;

    // Handle Metadata Load (Ready to scrub)
    video.addEventListener('loadedmetadata', () => {
      isVideoLoaded = true;
      if (fallback) fallback.classList.add('is-hidden');
      updateVideoScroll();
    });

    // Immediate check if cached
    if (video.readyState >= 1) {
      isVideoLoaded = true;
      if (fallback) fallback.classList.add('is-hidden');
    }

    // Handle Errors
    video.addEventListener('error', () => {
      if (fallback) {
        fallback.classList.remove('is-hidden');
        fallback.style.opacity = '1';
        fallback.style.zIndex = '2';
      }
      video.style.display = 'none';
    });

    // --- Core Scrubbing Logic ---
    function updateVideoScroll() {
      if (!isVideoLoaded) return;

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

      // Optimization: Stop calc if out of view
      if (rect.bottom < 0 || rect.top > windowHeight) return;

      // Calculate progress (0.0 to 1.0)
      let progress = (windowHeight - rect.top) / (windowHeight + rect.height);
      progress = Math.max(0, Math.min(1, progress));

      if (video.duration) {
        let targetTime = progress * video.duration * sensitivity;
        
        // Sanity checks
        if (targetTime > video.duration) targetTime = video.duration;
        if (targetTime < 0) targetTime = 0;
        
        // Scrub
        if (Number.isFinite(targetTime)) {
             video.currentTime = targetTime;
        }
      }
    }

    // --- Event Listeners ---
    function onScroll() {
      if (!ticking) {
        window.requestAnimationFrame(() => {
          updateVideoScroll();
          ticking = false;
        });
        ticking = true;
      }
    }

    // Intersection Observer to enable/disable scroll listener for performance
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          window.addEventListener('scroll', onScroll, { passive: true });
          window.addEventListener('resize', onScroll, { passive: true }); // Recalc on resize
          requestAnimationFrame(updateVideoScroll); 
        } else {
          window.removeEventListener('scroll', onScroll);
          window.removeEventListener('resize', onScroll);
        }
      });
    }, { rootMargin: '50px' });

    observer.observe(container);
  })();
</script>

I am using this retouch/moto theme: Ecommerce Website Templates - Free and Premium Themes for Your Online Store.

I don’t think it didn’t work, but I am using motto/retouch Shopify theme, is that why or is it a another solution to that?

Thank you so much, it did actually work when I used this code and told ai function on Shopify, it was really useful, thank you so much!