Skip to content

fix(core): Don't use data-swiper-slide-index for realIndex when virtual module is enabled#8142

Merged
nolimits4web merged 1 commit intonolimits4web:masterfrom
nWacky:fix-real-index-pr
Jan 28, 2026
Merged

fix(core): Don't use data-swiper-slide-index for realIndex when virtual module is enabled#8142
nolimits4web merged 1 commit intonolimits4web:masterfrom
nWacky:fix-real-index-pr

Conversation

@nWacky
Copy link
Copy Markdown
Contributor

@nWacky nWacky commented Jan 13, 2026

Swiper has virtual and thumbnail modules, which can be used to create an image carousel with thumbnails.

Issue

When both modules are enabled:

  1. thumbnail carousel loads with active slide = 1
  2. click on slide 9.
    Image carousel scrolls to slide 9.
    Thumbnail carousel scrolls to slide 9
  3. click on slide 2.
    Image carousel scrolls to slide 2.
    Currently: Thumbnail carousel scrolls back to slide 9.
    Expected: Thumbnail carousel scrolls to slide 2.

The issue occurs only on the first set of slides.
Slides 6 and after work as expected.

reproduction.mp4
Html playground

Copy to playground/core/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Swiper Playground</title>
    <meta name="viewport" content="width=device-width" />
    <style>
      html,
      body {
        position: relative;
        height: 100%;
      }

      body {
        background: #eee;
        font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
        font-size: 16px;
        color: #000;
        margin: 0;
        padding: 0;

        box-sizing: border-box;
        display: flex;
        flex-flow: column;
        padding: 1rem;
        height: 100dvh;
        gap: 2rem;
      }

      .swiper {
        width: 100%;
        height: 100%;
      }

      .swiper.thumbs {
        box-sizing: border-box;
        padding: 0 calc(var(--swiper-navigation-size) + 1rem);
        height: 30%;
      }

      .image-slide {
        display: flex !important;
        align-items: center;
        justify-content: center;
        background: #fff;
        text-align: center;
        font-size: 18px;
      }

      .thumb-slide.swiper-slide {
        display: flex;
        align-items: center;
        justify-content: center;
        background: #d3d3d3;
      }

      .thumb-slide.swiper-slide-thumb-active {
        background: #76ff8d;
      }
    </style>
  </head>

  <body>
    <div class="images swiper">
      <div class="swiper-wrapper">
        <!-- <div class="swiper-slide image-slide">Slide 1</div> -->
      </div>

      <div class="swiper-button-next"></div>
      <div class="swiper-button-prev"></div>
    </div>

    <div class="thumbs swiper">
      <div class="swiper-wrapper">
        <!-- <div class="swiper-slide thumb-slide">Thumbnail 1</div>  -->
      </div>
      <div class="swiper-button-next"></div>
      <div class="swiper-button-prev"></div>
    </div>

    <script type="module">
      // eslint-disable-next-line
      import "swiper/swiper-bundle.css";

      // eslint-disable-next-line
      import Swiper from "swiper/swiper-bundle.mjs";

      window.swiperThumbs = new Swiper(".thumbs", {
        slidesPerView: 4,
        spaceBetween: 10,
        navigation: {
          nextEl: ".swiper-button-next",
          prevEl: ".swiper-button-prev",
        },
        mousewheel: true,
        freeMode: true,
        virtual: {
          slidesPerView: 7,
          slides: (function () {
            const slides = [];
            for (var i = 0; i < 50; i += 1) {
              slides.push(`Thumbnail ${i + 1}`);
            }
            return slides;
          })(),
          renderSlide: (slide, index) => {
            let e = document.createElement("div");
            e.classList.add("thumb-slide", "swiper-slide");
            e.innerText = slide;
            return e;
          },
          cache: false,
        },
        watchSlidesProgress: true,
      });

      window.swiperImages = new Swiper(".images", {
        slidesPerView: 1,
        spaceBetween: 10,
        thumbs: {
          swiper: window.swiperThumbs,
        },
        navigation: {
          nextEl: ".swiper-button-next",
          prevEl: ".swiper-button-prev",
        },
        virtual: {
          slides: (function () {
            const slides = [];
            for (var i = 0; i < 50; i += 1) {
              slides.push(`Slide ${i + 1}`);
            }
            return slides;
          })(),
          renderSlide: (slide, index) => {
            let e = document.createElement("div");
            e.classList.add("image-slide", "swiper-slide");
            e.innerText = slide;
            return e;
          },
        },
      });
    </script>
  </body>
</html>

Stack trace

When viewing slide 9. Then clicking on slide 2, which has an index of 1:

  1. onThumbClick: slideToIndex = 1

    1. swiper.slideTo() skip = 0, snapIndex = 1, snapGrid.length = 50
  2. slideTo: slideIndex = 1

    1. swiper.setTranslate(translate) -> emits setTranslate
  3. setTranslate listener in virtual.mjs:

    1. update()

    2. swiper.updateActiveIndex()

    3. updateActiveIndex: (in else if (swiper.slides[activeIndex]) {)

      • activeIndex = 1
      • swiper.slides[activeIndex] = <div class="image-slide swiper-slide swiper-slide-active" data-swiper-slide-index="8" style="left: 5460px; width: 770px; margin-right: 10px;">
      • slideIndex = 8.
    4. realIndex = 8, from data-swiper-slide-index
      🔵 Slide dom node and data-swiper-slide-index haven't been updated yet in dom.
      User clicked on slide 2, not 8

    5. updateActiveIndex emits slideChange

  4. thumbs.mjs scrolls the thumbnails carousel to slide with index = 8:

    1. on('slideChange') calls update()
    2. update(): swiper.realIndex = 8, thumbsToActivate = 1
    3. autoScroll(): newThumbsIndex = 8

Swiper reads data-swiper-slide-index after commit f5a0ba8a.

After commit d23e8094 in updateActiveIndex() swiper handles virtual module's loop mode as a special case.

In this PR

In updateActiveIndex set realIndex = activeIndex if virtual module is enabled.

Index in data-swiper-slide-index isn't up to date

@nolimits4web nolimits4web merged commit bd957f8 into nolimits4web:master Jan 28, 2026
3 checks passed
@nolimits4web
Copy link
Copy Markdown
Owner

Merged, thank you!

@nWacky nWacky deleted the fix-real-index-pr branch February 2, 2026 07:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants