场景描述
页面需求:
- 横向轮播
- 中间滑块完整展示
- 两侧滑块各露出50%宽度
- 中间滑块高度完整展示
- 两侧滑块高度是中间的
2/3 - 切换后,移动到中间的滑块恢复到100%高度
- 切换后,从中间移出的幻灯片变成2/3高度
- 支持拖动和无限循环
如图:
Swiper 配置大致如下:
const swiper = new Swiper(".swiper", {
loop: true,
centeredSlides: true,
slidesPerView: "auto",
spaceBetween: 0,
grabCursor: true,
speed: 520,
});
.swiper-slide {
width: var(--slide-width);
height: var(--side-slide-height);
transition: height 520ms ease;
}
.swiper-slide-active {
height: var(--slide-height);
}
问题描述及分析
但实际运行时发现,在切换时,滑块会移动过头,然后再闪现到正确的位置。
一开始以为是配置错误,后来查阅 swiper 大致原理,看到 swiper 在切换过程中是会重新计算 slide 的位置,这让我想到了我给 active 的滑块覆盖了宽度高度,并且动画的原因,导致:
- 误以为轮播移动位置过头,实际是根据原先默认位置算,确实是移动这么多;
- 动画结束后是才是计算 active 滑块位置,又按照新宽高计算 slide 位置,和原先不对;
根本原因
问题出在:直接修改了 .swiper-slide 的真实高度和宽度。
.swiper-slide 是 Swiper 直接参与布局计算的元素。
当配置了:
slidesPerView: "auto",
centeredSlides: true,
loop: true
Swiper 会根据每个 .swiper-slide 的实际尺寸计算:
- slide 宽度
- slide 位置
- active slide 的中心点
- wrapper 的
translateX - loop clone 节点的位置
如果切换过程中 .swiper-slide 自己的尺寸发生变化,Swiper 可能会先按照旧尺寸计算一次位置,然后在 active 状态变化、尺寸变化后再次修正位置。
于是就会出现:
- 先滑到一个临时位置
- active slide 高度变化
- Swiper 重新计算中心点
- wrapper 回移到正确位置
视觉上就是“移动过头再回弹”。
正确思路
核心原则是:
不要改变 Swiper 直接计算的
.swiper-slide外层尺寸。
也就是说,.swiper-slide 应该保持稳定宽高,只作为 Swiper 的布局盒子。
真正的视觉高度变化,应该放到 slide 内部的元素上。
示例代码
HTML:
<div class="swiper">
<div class="swiper-wrapper">
<div class="swiper-slide">
<div class="slide-card">Slide 1</div>
</div>
<div class="swiper-slide">
<div class="slide-card">Slide 2</div>
</div>
<div class="swiper-slide">
<div class="slide-card">Slide 3</div>
</div>
</div>
</div>
CSS:
:root {
--content-width: min(100vw, 1200px);
--slide-width: calc(var(--content-width) / 2);
--slide-height: calc(var(--slide-width) * 0.75);
--side-card-height: calc(var(--slide-height) * 0.6667);
}
.swiper {
width: var(--content-width);
overflow: visible;
}
.swiper-wrapper {
align-items: center;
}
.swiper-slide {
width: var(--slide-width);
height: var(--slide-height);
display: flex;
align-items: center;
justify-content: center;
}
.slide-card {
width: 100%;
height: var(--side-card-height);
transition: height 520ms ease;
border-radius: 8px;
overflow: hidden;
}
.swiper-slide-active .slide-card {
height: var(--slide-height);
}
这样做的关键是:
.swiper-slide {
height: var(--slide-height);
}
外层 slide 高度始终不变。
变化的是内部:
.slide-card {
height: var(--side-card-height);
}
.swiper-slide-active .slide-card {
height: var(--slide-height);
}
Swiper 看到的布局盒子没有变,所以不会在切换过程中重新修正横向位置。
对比总结
错误方式:
.swiper-slide {
height: 66.67%;
}
.swiper-slide-active {
height: 100%;
}
问题:
- 修改了 Swiper 的布局元素
- 触发布局重排
- Swiper 可能重新计算位置
- 容易出现过冲、回弹、抖动
正确方式:
.swiper-slide {
height: 100%;
}
.slide-card {
height: 66.67%;
}
.swiper-slide-active .slide-card {
height: 100%;
}
优点:
- Swiper 外层布局稳定
- 横向 translate 不会被高度动画干扰
- 切换动画更平滑
- 更适合
slidesPerView: "auto"、centeredSlides、loop组合
最终结论
Swiper 轮播中,如果需要让中间 slide 和两侧 slide 尺寸不同,不要直接修改 .swiper-slide 的尺寸。
更稳的做法是:
.swiper-slide负责稳定布局,内部.slide-card负责视觉变化。
这样既能实现中间尺寸变大、两侧尺寸缩小的效果,又能避免切换时出现的各种问题(因为 slide 滑块的尺寸变化严重干扰了滑动计算)。
发表回复