Swiper incorrectly warns about a wrong number of slides for loop mode
Check that this is really a bug
- [X] I confirm
Reproduction link
https://codesandbox.io/p/devbox/swiper-react-infinite-loop-vfz433?file=%2Fsrc%2FApp.jsx
Bug description
If you attempt to render a basic loop swiper with any number of slides within jest tests the swiper warns about the incorrect number of slides when it shouldn't
The Swiper
import { Autoplay, Navigation, Pagination } from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/react';
const modules = [Navigation, Pagination, Autoplay];
export const App = () => {
return (
<Swiper
loop
slidesPerView={1}
slidesPerGroup={1}
loopAddBlankSlides
modules={modules}
>
<SwiperSlide>test</SwiperSlide>
<SwiperSlide>test</SwiperSlide>
<SwiperSlide>test</SwiperSlide>
<SwiperSlide>test</SwiperSlide>
<SwiperSlide>test</SwiperSlide>
<SwiperSlide>test</SwiperSlide>
</Swiper>
);
};
the test
import { render } from '@testing-library/react';
import { App } from './abc';
describe('App', () => {
it('App', () => {
render(<App />);
});
});
[!CAUTION] Swiper Loop Warning: The number of slides is not enough for loop mode, it will be disabled and not function properly. You need to add more slides (or make duplicates) or lower the values of slidesPerView and slidesPerGroup parameters
Expected Behavior
No warning
Actual Behavior
Warning
Swiper version
11.1.4
Platform/Target and Browser Versions
macOS 14.5
Validations
- [X] Follow our Code of Conduct
- [X] Read the docs.
- [X] Check that there isn't already an issue that request the same feature to avoid creating a duplicate.
- [X] Make sure this is a Swiper issue and not a framework-specific issue
Would you like to open a PR for this bug?
- [x] I'm willing to open a PR
Added the bug to the Swiper element as I believe this is where the issue lays
The 'slides' is an empty list in the loopFix method
There is this loopCreate method that has initSlides. Shouldn't there be a
swiper.slides = slides
at the end?
like
const initSlides = () => {
const slides = elementChildren(slidesEl, `.${params.slideClass}, swiper-slide`);
slides.forEach((el, index) => {
el.setAttribute('data-swiper-slide-index', index);
});
swiper.slides = slides
};
have the same issue
Same issue here
Same issue here
As a side effect, this bug causes slideToLoop not to work.
Same issue here, using swiper as a web component in Angular 18.
encounter the same issue.
encounter the same issue for next js
<section className={"w-full"}>
<Swiper
autoplay={{
delay: 2500,
disableOnInteraction: false,
pauseOnMouseEnter: true
}}
breakpoints={
{
768: {
slidesPerView: 3,
}
}
}
centeredSlides={true}
className={``}
coverflowEffect={{
rotate: 0,
slideShadows: false
}}
effect={"fade"}
grabCursor={true}
loop={products.length >= 4}
slidesPerView={1}
spaceBetween={0}
>
{products.map((item, index) => {
return (
<SwiperSlide
key={index}
className={``}
>
<ProductItem product={item} />
</SwiperSlide>
);
})}
</Swiper>
</section>
my code, this ok
loop={products.length >= 4}
I’ve had the same issue on a couple of projects now.
I'm having this issue as well in vanilla js
having same issue, any update on this?
I made the cyclic slide show work. Swiper version 11.1.14. I used swiper/vue, not swiper/react. But I think you can solve the same thing in React. Two things prevented the work. The first is if the slides are not statically defined, but are added dynamically. The second is that the number of slides must be greater than the slides per view.
In order for Swiper to detect slides, you need to call the update method for the swiper instance after constructing the DOM. I call update for swiper after a second to make sure DOM is built (onMounted). Getting an instance of swiper is also quite a quest. The documentation has a way via useSwiper, but it doesn't work and displays an error. I found a way to get an instance of swiper in the script to call its methods via @swiper. Code example for swiper/vue:
<template>
<ion-page>
<ion-content :fullscreen="true">
<ion-title class="ion-text-center ion-padding-vertical">Популярные события</ion-title>
<swiper :modules="modules" :slides-per-view="2" :space-between="10" :autoplay="true" navigation
@swiper="onSwiper" :pagination="{clickable: true}" :loop="true"
>
<template v-for="slide in slides" :key="slide.ID">
<swiper-slide>
<ion-img :src="'/src/assets/slides/'+slide.BannerImg"></ion-img>
</swiper-slide>
</template>
</swiper>
<Categories />
</ion-content>
</ion-page>
</template>
<script lang="ts">
import { defineComponent, onMounted, ref } from 'vue';
import { Pagination, Navigation, Autoplay } from 'swiper/modules';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { IonPage, IonTitle, IonContent, IonImg } from '@ionic/vue';
import 'swiper/css';
import 'swiper/css/autoplay';
import 'swiper/css/pagination';
import '@ionic/vue/css/ionic-swiper.css';
export default defineComponent({
components: { IonPage, IonContent, IonTitle, IonImg, Swiper, SwiperSlide },
setup() {
const slides = [
{
ID: "ID1",
Title: "Event1",
BannerImg: "madison.jpg",
},
{
ID: "ID2",
Title: "Event2",
BannerImg: "quiz.png",
},
{
ID: "ID3",
Title: "Event3",
BannerImg: "madison.jpg",
},
{
ID: "ID4",
Title: "Event4",
BannerImg: "quiz.png",
}];
const swiper = ref<any>(null);
const onSwiper = (instance: any) => {
swiper.value = instance;
}
onMounted(() => {
setTimeout(() => {
swiper.value.update();
}, 1);
});
return {
slides, onSwiper,
modules: [Autoplay, Navigation, Pagination],
};
},
});
</script>
I made the cyclic slide show work. Swiper version 11.1.14. I used swiper/vue, not swiper/react. But I think you can solve the same thing in React. Two things prevented the work. The first is if the slides are not statically defined, but are added dynamically. The second is that the number of slides must be greater than the slides per view.
In order for Swiper to detect slides, you need to call the update method for the swiper instance after constructing the DOM. I call update for swiper after a second to make sure DOM is built (onMounted). Getting an instance of swiper is also quite a quest. The documentation has a way via useSwiper, but it doesn't work and displays an error. I found a way to get an instance of swiper in the script to call its methods via @swiper. Code example for swiper/vue:
<template> <ion-page> <ion-content :fullscreen="true"> <ion-title class="ion-text-center ion-padding-vertical">Популярные события</ion-title> <swiper :modules="modules" :slides-per-view="2" :space-between="10" :autoplay="true" navigation @swiper="onSwiper" :pagination="{clickable: true}" :loop="true" > <template v-for="slide in slides" :key="slide.ID"> <swiper-slide> <ion-img :src="'/src/assets/slides/'+slide.BannerImg"></ion-img> </swiper-slide> </template> </swiper> <Categories /> </ion-content> </ion-page> </template> <script lang="ts"> import { defineComponent, onMounted, ref } from 'vue'; import { Pagination, Navigation, Autoplay } from 'swiper/modules'; import { Swiper, SwiperSlide } from 'swiper/vue'; import { IonPage, IonTitle, IonContent, IonImg } from '@ionic/vue'; import 'swiper/css'; import 'swiper/css/autoplay'; import 'swiper/css/pagination'; import '@ionic/vue/css/ionic-swiper.css'; export default defineComponent({ components: { IonPage, IonContent, IonTitle, IonImg, Swiper, SwiperSlide }, setup() { const slides = [ { ID: "ID1", Title: "Event1", BannerImg: "madison.jpg", }, { ID: "ID2", Title: "Event2", BannerImg: "quiz.png", }, { ID: "ID3", Title: "Event3", BannerImg: "madison.jpg", }, { ID: "ID4", Title: "Event4", BannerImg: "quiz.png", }]; const swiper = ref<any>(null); const onSwiper = (instance: any) => { swiper.value = instance; } onMounted(() => { setTimeout(() => { swiper.value.update(); }, 1); }); return { slides, onSwiper, modules: [Autoplay, Navigation, Pagination], }; }, }); </script>
Worked thanks! Here's the React version of it:
import { Swiper, SwiperSlide, SwiperRef } from 'swiper/react';
......
const swiperRef = useRef<SwiperRef>(null);
useEffect(() => {
if (swiperRef.current) {
setTimeout(() => {
swiperRef.current?.swiper.autoplay.start();
}, 1000);
}
}, []);
<Swiper
ref={swiperRef}
.....
Angular. Who met this bug, this is my solution:
constructor() { effect(() => { let users = this.users(); if (users.length) { this.initSwiper(); } }); }
I was initing the swiper in ngAfterInit before and got this bug
All I needed was to move the code to effect and add a check
Because of nature of how the loop mode works (it will rearrange slides), total number of slides must be >= slidesPerView * 2
In my experience this warning is triggered when the check for this warning takes place before the hosting app has rendered the slide elements in the DOM, resulting in functional behaviour, but a false positive on the warning.
Didn't find a clean solution yet, but was able to avoid the warning by using a mutation observer in my UI component and only construct the Swiper instance after all slides were made available in the DOM.
I assume the check for this warning should take place somewhere else during Swiper construction.
Any signs for a fix for this? I've tried using an observer to check all slides were made available in the DOM but that still hasn't fixed it.
Same issue.
Apparently, this issue doesn't occur in version 10.x. There must have been a change in how looping works in version 11.x.
Had the same issue - now using swiper element in Vue 3 as swiper/vue is outdated/i just couldn't get it to work correctly.
IT DID NOT WORK IN 11.x. for me without doing it this way.
The key is to use init="false" attribute and then pass the params, loop especially, as html element properties.
<template>
<swiper-container
ref="swiperEl"
v-if="items && items.length > 0"
:init="false"
>
<swiper-slide v-for="(item, idx) in items" :key="idx">
{{ item.Title }}
</swiper-slide>
</swiper-container>
</template>
<script setup>
import { register } from 'swiper/element/bundle';
import { ref, onMounted } from 'vue';
const items = [
{
ID: "ID1",
Title: "Event1",
BannerImg: "madison.jpg",
},
{
ID: "ID2",
Title: "Event2",
BannerImg: "quiz.png",
},
{
ID: "ID3",
Title: "Event3",
BannerImg: "madison.jpg",
},
{
ID: "ID4",
Title: "Event4",
BannerImg: "quiz.png",
},
];
// Ref attached to swiper
const swiperEl = ref('swiperEl');
const mods = [EffectFade];
const spaceBetween = 16;
const swiperParams = {
modules: mods,
slidesPerView: 1,
spaceBetween: spaceBetween,
breakpoints: {
640: {
slidesPerView: 2,
},
1024: {
slidesPerView: 3,
},
},
on: {
init() {
console.log('Swiper initialized!');
this.params.loop = true;
},
},
};
onMounted(() => {
// Swiper initialization
register();
// Ensure it's ready
if (!swiperEl.value) {
return;
}
// Assign parameters to swiper element and then init
Object.assign(swiperEl.value, swiperParams);
swiperEl.value.initialize();
});
</script>
Warning is now gone, swiper loops and can be swiped correctly.
DOCS HERE
Any sign of a fix for this? Downgrading to v10 didn't fix this for me, or using the init="false" attribute
Well, it took me days to solve this problem... In the end, the solution seems simple, you just need to pay attention to the value of loop and his reactivity
The fact is that having Swiper warns makes sense, but after mounted of the component, and depending on the cases and reactivity of loop, the error may or may not be fixed.
Now Swiper will not publish the error and there will be no problem.
I hope this is useful for everyone
- Swiper Element (WebComponent) with vue
<script lang="ts" setup>
import { PhotoService } from '@/service/PhotoService';
const swiperEl = ref<any>(null);
const images = ref();
const swiperParams = {
// ....
navigation: true,
pagination: true,
// ....
};
onMounted(() => {
PhotoService.getImages()
.then(data => (images.value = data))
.then(() => {
Object.assign(swiperEl.value, swiperParams);
swiperEl.value.initialize();
});
});
</script>
<template>
<swiper-container
ref="swiperEl"
:init="false"
:loop="images?.length > 1"
>
<swiper-slide v-for="image in images" :key="image.alt">
<img
:src="image.src"
:alt="image.alt"
/>
</swiper-slide>
</swiper-container>
</template>
- Swiper Vue.js Components
<script setup lang="ts">
import { PhotoService } from '@/service/PhotoService';
import { Swiper, SwiperSlide } from 'swiper/vue';
import { Navigation, Pagination } from 'swiper/modules';
const images = ref();
const swiperOptions = computed(() => {
return {
// .....
loop: images.value?.length > 1,
// .....
navigation: true,
pagination: true,
modules: [Navigation, Pagination],
};
});
onMounted(() => {
PhotoService.getImages().then((data) => (images.value = data));
});
</script>
<template>
<Swiper
v-bind="swiperOptions"
>
<SwiperSlide v-for="image in images" :key="image.alt">
<img
:src="image.src"
:alt="image.alt"
/>
</SwiperSlide>
</Swiper>
</template>
If you're using jest with jsdom and haven't explicitly set a width on <Swiper> it will try and pull from element.clientWidth, which is set explicitly to 0 in jsdom. This leads to Swiper not setting swiper.size and downstream will not initialize swiper.slides.
Can check out this issue over on jsdom as to why: https://github.com/jsdom/jsdom/issues/1322
This helped me. loop: true, loopedSlidesLimit: null,
Just want to add my 5 cents:
You need to explicitly set width="img.width" height="img.height" inside a slide, otherwise image are not loaded yet during swiper initialization and it thinks that their total width = 0 and gives a warning.
by providing width and height values to DOM element swiper will calculate properly before images are loaded.
We have the same warning in the console with a react integration, but the slides + loop are working.
Tracing swiper/shared/swiper-core.mjs:
loopCreate()
- set const shouldFillGroup using swiper.slides (empty)
- set const shouldFillGrid using swiper.slides (empty)
- if (shouldFillGroup || shouldFillGrid) + params.loopAddBlankSlides
--> call swiper.recalcSlides() # this will reset swiper.slides
- call swiper.loopFix()
loopFix()
- check swiper.length (empty)
-- show warning
- call swiper.recalcSlides() # this will reset swiper.slides with the correct number of slides
Other way to calculate the swiper.slides
update() / or init()
- if (param.breakpoint) call swiper.setBreakpoint()
setBreakpoint()
(this check betweens the default swiper value and params value)
- check params.loop, slidesPerView != params.slidesPerView...
- if (initialized) and if (needsReLoop) swiper.loopDestroy()
- or if (initialized) + wasLoop && !hasLoop -> swiper.loopDestroy()
loopDestroy()
- call recalcSlides (2 times)
or by calling destroy(), but that's not very useful at this point.
I'm not sure why recalcSlides()/setting swiper.slides is never called before loopCreate.
I have solved it
There is no other way, force it, roll one, and then roll back
i think i was crazy, lol
const onInit = (swiper: any) => {
swiper.update()
swiper.slideToLoop(1, 0)
setTimeout(() => {
swiper.slideToLoop(0, 0)
}, 10)
}