Skip to content

Commit de3131f

Browse files
committed
feat: new snapToSlideEdge parameter
fixes #8021 fixes #4780
1 parent 71e9511 commit de3131f

File tree

7 files changed

+187
-2
lines changed

7 files changed

+187
-2
lines changed

demos/125-snap-to-slide-edge.html

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<title>Swiper demo - Snap To Slide Edge</title>
7+
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1">
8+
</head>
9+
10+
<body>
11+
<h2>Default behavior (snapToSlideEdge: false)</h2>
12+
<p>Notice how at the end, the last slide aligns to the right edge</p>
13+
<!-- Swiper without snapToSlideEdge -->
14+
<div class="swiper swiper-default">
15+
<div class="swiper-wrapper">
16+
<div class="swiper-slide">Slide 1</div>
17+
<div class="swiper-slide">Slide 2</div>
18+
<div class="swiper-slide">Slide 3</div>
19+
<div class="swiper-slide">Slide 4</div>
20+
<div class="swiper-slide">Slide 5</div>
21+
<div class="swiper-slide">Slide 6</div>
22+
<div class="swiper-slide">Slide 7</div>
23+
<div class="swiper-slide">Slide 8</div>
24+
</div>
25+
<div class="swiper-pagination"></div>
26+
</div>
27+
28+
<h2>With snapToSlideEdge: true</h2>
29+
<p>Slides always stay aligned to their original position</p>
30+
<!-- Swiper with snapToSlideEdge -->
31+
<div class="swiper swiper-snap-edge">
32+
<div class="swiper-wrapper">
33+
<div class="swiper-slide">Slide 1</div>
34+
<div class="swiper-slide">Slide 2</div>
35+
<div class="swiper-slide">Slide 3</div>
36+
<div class="swiper-slide">Slide 4</div>
37+
<div class="swiper-slide">Slide 5</div>
38+
<div class="swiper-slide">Slide 6</div>
39+
<div class="swiper-slide">Slide 7</div>
40+
<div class="swiper-slide">Slide 8</div>
41+
</div>
42+
<div class="swiper-pagination"></div>
43+
</div>
44+
45+
<!-- Initialize Swiper -->
46+
<script type="module">
47+
import Swiper from 'swiper/swiper-bundle.mjs';
48+
import 'swiper/swiper-bundle.css';
49+
50+
// Default behavior - last slide aligns to edge
51+
var swiperDefault = new Swiper('.swiper-default', {
52+
slidesPerView: 'auto',
53+
spaceBetween: 20,
54+
pagination: {
55+
el: '.swiper-default .swiper-pagination',
56+
clickable: true,
57+
},
58+
});
59+
60+
// With snapToSlideEdge - slides stay aligned
61+
var swiperSnapEdge = new Swiper('.swiper-snap-edge', {
62+
slidesPerView: 'auto',
63+
spaceBetween: 20,
64+
snapToSlideEdge: true,
65+
pagination: {
66+
el: '.swiper-snap-edge .swiper-pagination',
67+
clickable: true,
68+
},
69+
});
70+
</script>
71+
72+
<!-- Demo styles -->
73+
<style>
74+
html,
75+
body {
76+
position: relative;
77+
height: 100%;
78+
}
79+
80+
body {
81+
background: #eee;
82+
font-family: Helvetica Neue, Helvetica, Arial, sans-serif;
83+
font-size: 14px;
84+
color: #000;
85+
margin: 0;
86+
padding: 20px;
87+
}
88+
89+
h2 {
90+
margin: 20px 0 10px;
91+
}
92+
93+
p {
94+
margin: 0 0 10px;
95+
color: #666;
96+
}
97+
98+
.swiper {
99+
width: 100%;
100+
height: 200px;
101+
margin-bottom: 30px;
102+
}
103+
104+
.swiper-slide {
105+
text-align: center;
106+
font-size: 18px;
107+
background: #fff;
108+
border-radius: 8px;
109+
width: 100px;
110+
111+
/* Center slide text vertically */
112+
display: flex;
113+
justify-content: center;
114+
align-items: center;
115+
}
116+
117+
.swiper-default .swiper-slide {
118+
background: #4a90d9;
119+
color: #fff;
120+
}
121+
122+
.swiper-snap-edge .swiper-slide {
123+
background: #5cb85c;
124+
color: #fff;
125+
}
126+
</style>
127+
</body>
128+
129+
</html>

demos/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ <h1>Swiper demos</h1>
2323
<li><a href="/100-space-between.html">100-space-between</a></li>
2424
<li><a href="/110-slides-per-view.html">110-slides-per-view</a></li>
2525
<li><a href="/120-slides-per-view-auto.html">120-slides-per-view-auto</a></li>
26+
<li><a href="/125-snap-to-slide-edge.html">125-snap-to-slide-edge</a></li>
2627
<li><a href="/130-centered.html">130-centered</a></li>
2728
<li><a href="/140-centered-auto.html">140-centered-auto</a></li>
2829
<li><a href="/145-css-mode.html">145-css-mode</a></li>

src/components-shared/params-list.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ const paramsList = [
4444
'_slidesOffsetAfter',
4545
'normalizeSlideIndex',
4646
'_centerInsufficientSlides',
47+
'_snapToSlideEdge',
4748
'_watchOverflow',
4849
'roundLengths',
4950
'touchRatio',

src/core/defaults.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export default {
5858
slidesOffsetAfter: 0, // in px
5959
normalizeSlideIndex: true,
6060
centerInsufficientSlides: false,
61+
snapToSlideEdge: false,
6162

6263
// Disable swiper and hide navigation when container not overflow
6364
watchOverflow: true,

src/core/update/updateSlides.mjs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,48 @@ export default function updateSlides() {
206206

207207
// Remove last grid elements depending on width
208208
if (!params.centeredSlides) {
209+
// Check if snapToSlideEdge should be applied
210+
const isFractionalSlidesPerView =
211+
params.slidesPerView !== 'auto' && params.slidesPerView % 1 !== 0;
212+
const shouldSnapToSlideEdge =
213+
params.snapToSlideEdge &&
214+
!params.loop &&
215+
(params.slidesPerView === 'auto' || isFractionalSlidesPerView);
216+
217+
// Calculate the last allowed snap index when snapToSlideEdge is enabled
218+
// This ensures minimum slides are visible at the end
219+
let lastAllowedSnapIndex = snapGrid.length;
220+
if (shouldSnapToSlideEdge) {
221+
let minVisibleSlides;
222+
if (params.slidesPerView === 'auto') {
223+
// For 'auto' mode, calculate how many slides fit based on actual sizes
224+
minVisibleSlides = 1;
225+
let accumulatedSize = 0;
226+
for (let i = slidesSizesGrid.length - 1; i >= 0; i -= 1) {
227+
accumulatedSize += slidesSizesGrid[i] + (i < slidesSizesGrid.length - 1 ? spaceBetween : 0);
228+
if (accumulatedSize <= swiperSize) {
229+
minVisibleSlides = slidesSizesGrid.length - i;
230+
} else {
231+
break;
232+
}
233+
}
234+
} else {
235+
minVisibleSlides = Math.floor(params.slidesPerView);
236+
}
237+
lastAllowedSnapIndex = Math.max(slidesLength - minVisibleSlides, 0);
238+
}
239+
209240
const newSlidesGrid = [];
210241
for (let i = 0; i < snapGrid.length; i += 1) {
211242
let slidesGridItem = snapGrid[i];
212243
if (params.roundLengths) slidesGridItem = Math.floor(slidesGridItem);
213-
if (snapGrid[i] <= swiper.virtualSize - swiperSize) {
244+
if (shouldSnapToSlideEdge) {
245+
// When snapToSlideEdge is enabled, only keep snaps up to lastAllowedSnapIndex
246+
if (i <= lastAllowedSnapIndex) {
247+
newSlidesGrid.push(slidesGridItem);
248+
}
249+
} else if (snapGrid[i] <= swiper.virtualSize - swiperSize) {
250+
// When snapToSlideEdge is disabled, keep snaps that fit within scrollable area
214251
newSlidesGrid.push(slidesGridItem);
215252
}
216253
}
@@ -220,7 +257,10 @@ export default function updateSlides() {
220257
Math.floor(swiper.virtualSize - swiperSize) - Math.floor(snapGrid[snapGrid.length - 1]) >
221258
1
222259
) {
223-
snapGrid.push(swiper.virtualSize - swiperSize);
260+
// Only add edge-aligned snap if snapToSlideEdge is not enabled
261+
if (!shouldSnapToSlideEdge) {
262+
snapGrid.push(swiper.virtualSize - swiperSize);
263+
}
224264
}
225265
}
226266
if (isVirtual && params.loop) {

src/swiper-vue.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,10 @@ declare const Swiper: DefineComponent<
162162
type: BooleanConstructor;
163163
default: undefined;
164164
};
165+
snapToSlideEdge: {
166+
type: BooleanConstructor;
167+
default: undefined;
168+
};
165169
watchOverflow: {
166170
type: BooleanConstructor;
167171
default: undefined;

src/types/swiper-options.d.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,15 @@ export interface SwiperOptions {
400400
*/
401401
centerInsufficientSlides?: boolean;
402402

403+
/**
404+
* When enabled, the swiper will always snap to slide edges rather than arbitrary positions.
405+
* This prevents partial slides from appearing misaligned at the end of the swiper.
406+
* Only applies when `slidesPerView` is fractional or `'auto'`, and is ignored in `loop` and `centeredSlides` modes.
407+
*
408+
* @default false
409+
*/
410+
snapToSlideEdge?: boolean;
411+
403412
/**
404413
* This option may a little improve desktop usability. If `true`, user will see the "grab" cursor when hover on Swiper
405414
*

0 commit comments

Comments
 (0)