Thumbnail Carousel only showing small thumbnails on initial product click

Topic summary

Issue: On desktop, the product media uses a thumbnail carousel that initially shows only small thumbnails; the large main image doesn’t appear until a thumbnail is clicked. The requester also prefers arrows beside the large image and wants arrow clicks to advance the main image without clicking thumbnails. Mobile works fine. Priority: fix the initial large image display.

Diagnosis: A modification to the theme’s Javascript prevents the gallery from setting the “is-active” class on the initial slide (this class determines which media is shown). Clicking a thumbnail does set “is-active,” temporarily resolving display. A console error is reported; deeper investigation is needed.

Evidence: The posted Media-gallery.js shows a custom web component (“media-gallery”) with an added function “activateFirstVideoThenAutoplay” called on load. The snippet appears malformed/truncated around a call to setActiveMedia, suggesting a syntax/runtime error. An attached screenshot (console error) is central to identifying the exact fault.

Key terms: “is-active” = current slide indicator; “deferred-media” = lazy-loaded video; “customElements” = browser API for web components.

Status and next steps: Unresolved. Action items include checking browser console for errors, reviewing recent edits in Media-gallery.js (especially the autoplay addition), and comparing against the stock theme to restore correct initialization.

Summarized with AI on December 11. AI used: gpt-5.

For my products, I’m using the thumbnail carousel layout. However, when I initially click on the product, only small thumbnail images show. I want the first picture to be large upon opening the product, but I have to click one of the small thumbnail images to get it started. Also, I’d like the arrows to be on the side of the largest image instead of under it, by the thumbnails. And I want the image to automatically move to the next one when I click the arrow instead of having to click the next thumbnail. This issue is only on desktop, mobile works perfectly. How can I make these changes?

Fixing the first image showing up on initial click is a priority, the arrows are not that big of a deal if that’s a hard fix.

My store link is: Noctis Obscura

This link also goes to the best product to test the issue on, since I have a lot of pictures and videos for that one. My store is still in the process of being set up.

Thanks!

Your theme code is modified. Because of this, there is a Javascript error in console, theme code fails to set is-active class on the initial slide.

When you click thumbs, is-active class is set and selected product media is shown.

To suggest the fix a deeper dig is needed.

Ok, how should I go about checking for that error. I’m in the theme code now but I’m not seeing any errors with the very little coding knowledge I have. Do you have any pointers for things I should check for?

I’m in the Media-gallery.js, here’s the code pasted. Not sure if that’s helpful.

if (!customElements.get(‘media-gallery’)) {

customElements.define(

'media-gallery',

class MediaGallery extends HTMLElement {

  constructor() {

    super();

    this.elements = {

      liveRegion: this.querySelector('\[id^="GalleryStatus"\]'),

      viewer: this.querySelector('\[id^="GalleryViewer"\]'),

      thumbnails: this.querySelector('\[id^="GalleryThumbnails"\]'),

    };

    this.mql = window.matchMedia('(min-width: 750px)');

    if (!this.elements.thumbnails) return;



    this.elements.viewer.addEventListener('slideChanged', debounce(this.onSlideChanged.bind(this), 500));

    this.elements.thumbnails.querySelectorAll('\[data-target\]').forEach((mediaToSwitch) => {

      mediaToSwitch

        .querySelector('button')

        .addEventListener('click', this.setActiveMedia.bind(this, mediaToSwitch.dataset.target, false));

    });

    if (this.dataset.desktopLayout.includes('thumbnail') && this.mql.matches) this.removeListSemantic();



    // NEW: on load, jump to the first video and autoplay it

    this.activateFirstVideoThenAutoplay();

  }



  // ----- helpers we added -----

  activateFirstVideoThenAutoplay() {

    // Prefer an already-active slide if it contains a video; otherwise pick the first slide with .deferred-media

    const current = this.elements.viewer.querySelector('\[data-media-id\].is-active');

    const currentHasVideo = current && current.querySelector('.deferred-media, video');

    const firstVideoItem =

      currentHasVideo ? current :

      (this.elements.viewer.querySelector('\[data-media-id\] .deferred-media') ||

       this.elements.viewer.querySelector('video'))

        ?.closest?.('\[data-media-id\]');



    if (firstVideoItem) {

      // Make that slide active (if not already), then play it.

      if (!firstVideoItem.classList.contains('is-active')) {

        this.setActiveMedia(firstVideoItem.dataset.mediaId, false);

      } else {

        this.playActiveMedia(firstVideoItem, /\*force\*/ true);

      }

    }

  }



  onSlideChanged(event) {

    const thumbnail = this.elements.thumbnails.querySelector(

      \`\[data-target="${event.detail.currentElement.dataset.mediaId}"\]\`

    );

    this.setActiveThumbnail(thumbnail);

  }



  setActiveMedia(mediaId, prepend) {

    const activeMedia =

      this.elements.viewer.querySelector(\`\[data-media-id="${mediaId}"\]\`) ||

      this.elements.viewer.querySelector('\[data-media-id\]');

    if (!activeMedia) return;



    this.elements.viewer.querySelectorAll('\[data-media-id\]').forEach((el) => el.classList.remove('is-active'));

    activeMedia.classList.add('is-active');



    if (prepend) {

      activeMedia.parentElement.firstChild !== activeMedia && activeMedia.parentElement.prepend(activeMedia);



      if (this.elements.thumbnails) {

        const activeThumb = this.elements.thumbnails.querySelector(\`\[data-target="${mediaId}"\]\`);

        activeThumb.parentElement.firstChild !== activeThumb && activeThumb.parentElement.prepend(activeThumb);

      }



      if (this.elements.viewer.slider) this.elements.viewer.resetPages();

    }



    this.preventStickyHeader();

    window.setTimeout(() => {

      if (!this.mql.matches || this.elements.thumbnails) {

        activeMedia.parentElement.scrollTo({ left: activeMedia.offsetLeft });

      }

      const rect = activeMedia.getBoundingClientRect();

      if (rect.top <= -0.5) window.scrollTo({ top: rect.top + window.scrollY, behavior: 'smooth' });

    });



    // Load & autoplay the selected media

    this.playActiveMedia(activeMedia, /\*force\*/ true);



    if (!this.elements.thumbnails) return;

    const activeThumb = this.elements.thumbnails.querySelector(\`\[data-target="${mediaId}"\]\`);

    this.setActiveThumbnail(activeThumb);

    this.announceLiveRegion(activeMedia, activeThumb.dataset.mediaPosition);

  }



  setActiveThumbnail(thumbnail) {

    if (!this.elements.thumbnails || !thumbnail) return;

    this.elements.thumbnails.querySelectorAll('button').forEach((el) => el.removeAttribute('aria-current'));

    thumbnail.querySelector('button').setAttribute('aria-current', true);

    if (this.elements.thumbnails.isSlideVisible(thumbnail, 10)) return;

    this.elements.thumbnails.slider.scrollTo({ left: thumbnail.offsetLeft });

  }



  announceLiveRegion(activeItem, position) {

    const image = activeItem.querySelector('.product__modal-opener--image img');

    if (!image) return;

    image.onload = () => {

      this.elements.liveRegion.setAttribute('aria-hidden', false);

      this.elements.liveRegion.innerHTML = window.accessibilityStrings.imageAvailable.replace('\[index\]', position);

      setTimeout(() => this.elements.liveRegion.setAttribute('aria-hidden', true), 2000);

    };

    image.src = image.src;

  }



  /\*\*

   \* Force-load deferred content and autoplay a native <video>.

   \* (Shopify-uploaded videos.)

   \*/

  playActiveMedia(activeItem, force) {

    if (window.pauseAllMedia) window.pauseAllMedia();



    const deferred = activeItem.querySelector('.deferred-media');

    if (deferred && typeof deferred.loadContent === 'function') {

      if (force || deferred.hasAttribute('data-autoplay') || true) {

        deferred.loadContent(false);          // stamp template

        deferred.setAttribute('loaded', 'true');

      }

      const posterBtn = activeItem.querySelector('.deferred-media__poster');

      if (posterBtn) posterBtn.remove();

    }



    // Try to play as soon as the <video> is present & ready

    let tries = 0;

    const kick = () => {

      const video = activeItem.querySelector('video');

      if (!video) return ++tries < 20 ? setTimeout(kick, 80) : null;



      // Attributes browsers require for autoplay

      video.muted = true;

      video.playsInline = true;

      video.setAttribute('muted', '');

      video.setAttribute('autoplay', '');

      video.setAttribute('playsinline', '');



      const tryPlay = () => {

        const p = video.play && video.play();

        if (p && typeof p.then === 'function') p.catch(() => setTimeout(tryPlay, 120));

      };



      if (video.readyState >= 2) {

        tryPlay();

      } else {

        video.addEventListener('loadeddata', tryPlay, { once: true });

        try { video.load(); } catch (e) {}

      }

    };

    kick();

  }



  preventStickyHeader() {

    this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');

    if (!this.stickyHeader) return;

    this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));

  }



  removeListSemantic() {

    if (!this.elements.viewer.slider) return;

    this.elements.viewer.slider.setAttribute('role', 'presentation');

    this.elements.viewer.sliderItems.forEach((slide) => slide.setAttribute('role', 'presentation'));

  }

}

);

// Re-run when the section hot-reloads in the editor

document.addEventListener(‘shopify:section:load’, () => {

document.querySelectorAll('media-gallery').forEach((g) => {

  if (typeof g.activateFirstVideoThenAutoplay === 'function') g.activateFirstVideoThenAutoplay();

});

});

}

This is the error.