Could we help you? Please click the banners. We are young and desperately need the money
Discover how to properly use Swiper.js with slidesPerView: ‘auto’, centeredSlides, and loop enabled. Learn an easy manual loop workaround to fix common pagination problems.
Swiper.js is one of the most powerful and widely-used JavaScript libraries for creating modern, responsive sliders and carousels. Among its many features, three configuration options are especially popular for building sleek and dynamic user interfaces:
slidesPerView: 'auto' — Automatically adjusts the number of visible slides based on container width, supporting variable slide widths and partially visible slides.
centeredSlides: true — Visually centers the active slide, creating a polished, focused layout.
loop: true — Enables infinite scrolling by duplicating slides at both ends for seamless cycling.
While each of these options is powerful on its own, combining them can lead to unpredictable behavior, particularly with pagination bullets and navigation.
In this post, we’ll walk you through a clean and dependable workaround that manually simulates looping and uses fully custom pagination to ensure smooth and consistent behavior.
At first glance, combining these three Swiper.js options seems like the perfect setup for a sleek, responsive slider. However, in practice, this combination often leads to unexpected navigation behavior, especially when users interact with pagination bullets or click directly on slides.
The core issue lies in how Swiper.js internally handles looping with duplicated slides. When loop: true is enabled, Swiper clones the first and last few slides to create the illusion of infinite scrolling. But when slides have auto widths and are centered, this cloning logic can break pagination consistency.
For example, clicking on a pagination bullet or a partially visible slide may cause Swiper to jump in the shortest direction, which might mean backward, even if the clicked slide is visually positioned to the right.
A robust way to avoid the problems described above is to disable Swiper’s internal loop (loop: false) and manually clone the first and last slides on either end of the slider. This lets you simulate infinite looping with full control over slide transitions and pagination behavior.
In addition, Swiper’s built-in pagination does not account for these manual clones, implementing custom pagination logic ensures that bullet states and slide indices remain perfectly synchronized.
The table below illustrates the differences between native looping and a manual loop simulation that uses cloned slides and custom pagination logic.
Current Slide | Clicked Slide | Navigation Behavior |
---|---|---|
Slide 1 | Slide 5 | With Swiper loop: true: The slider jumps backward to Slide 5, choosing the shortest path, which may confuse users by animating “backwards.” |
Slide 1 | Slide 5 | With manual loop simulation (loop: false + cloned slides): Navigation moves forward through all slides (2 → 3 → 4 → 5) with smooth animation, offering an intuitive experience. |
<?php
$upload_url = wp_get_upload_dir()['baseurl'];
$slides = [
"image1.webp",
"image2.webp",
"image3.webp",
"image4.webp",
];
if (empty($slides)) return;
?>
<div class="swiper" id="custom-swiper">
<div class="swiper-wrapper">
<?php if (count($slides) > 1): ?>
<!-- Clone of last slide at the beginning -->
<div class="swiper-slide">
<img src="<?php echo esc_url($upload_url . '/' . end($slides)); ?>" alt="Cloned Last Slide">
</div>
<?php endif; ?>
<?php foreach ($slides as $img): ?>
<div class="swiper-slide">
<img src="<?php echo esc_url($upload_url . '/' . $img); ?>" alt="Slide Image">
</div>
<?php endforeach; ?>
<?php if (count($slides) > 1): ?>
<!-- Clone of first slide at the end -->
<div class="swiper-slide">
<img src="<?php echo esc_url($upload_url . '/' . reset($slides)); ?>" alt="Cloned First Slide">
</div>
<?php endif; ?>
</div>
</div>
document.addEventListener('DOMContentLoaded', () => {
const sliderEl = document.querySelector('#custom-swiper');
const pagination = document.querySelector('.custom-pagination');
if (!sliderEl || !pagination) return;
const swiper = new Swiper(sliderEl, {
centeredSlides: true,
speed: 600,
loop: false, // Disable built-in loop to use manual clones
slidesPerView: "auto",
initialSlide: 1, // Start at real first slide (index 1) because index 0 is cloned last slide
});
const bullets = pagination.querySelectorAll('.custom-pagination-bullet');
bullets.forEach((btn, index) => {
btn.addEventListener('click', () => {
const targetSlideIndex = index + 1; // Offset by 1 due to cloned slide at index 0
swiper.slideTo(targetSlideIndex, 700);
});
});
});
» Note : The example above demonstrates pagination using bullets only — navigation arrows are not included. If you intend to add navigation arrows or support swipe gestures, additional JavaScript will be required to handle these interactions properly.
When you manually clone slides, Swiper’s built-in pagination does not recognize these clones and requires extra JavaScript to sync the pagination bullets with the real slides. This increases complexity and risk of bugs.
With custom pagination:
You fully control the number and behavior of pagination bullets.
You target only the real slides, ignoring clones.
You avoid syncing issues between active bullets and slides.
You gain full freedom over styling and interaction.
Implementation is simpler and cleaner, especially when simulating loops manually.
Feature | Swiper Built-In Pagination with Clones | Custom Pagination with Clones |
---|---|---|
Additional JavaScript Needed | Required for active state sync | Not required |
Control Over Appearance | Limited (via Swiper configuration) | Full freedom |
Implementation Complexity | Higher (sync logic, offsets) | Simpler & cleaner |
Flexibility | Lower | Higher |
If you’re building responsive, visually dynamic sliders with Swiper.js and want smooth infinite loops without confusing navigation jumps, consider manually simulating the loop and using custom pagination. It adds a bit more setup effort but results in a more intuitive and polished user experience.