Swiper滑块大小不一致时,切换异常抖动的解决方案

场景描述

页面需求:

  • 横向轮播
  • 中间滑块完整展示
  • 两侧滑块各露出50%宽度
  • 中间滑块高度完整展示
  • 两侧滑块高度是中间的 2/3
  • 切换后,移动到中间的滑块恢复到100%高度
  • 切换后,从中间移出的幻灯片变成2/3高度
  • 支持拖动和无限循环

如图:

enter description here 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 的滑块覆盖了宽度高度,并且动画的原因,导致:

  1. 误以为轮播移动位置过头,实际是根据原先默认位置算,确实是移动这么多;
  2. 动画结束后是才是计算 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 状态变化、尺寸变化后再次修正位置。

于是就会出现:

  1. 先滑到一个临时位置
  2. active slide 高度变化
  3. Swiper 重新计算中心点
  4. 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"centeredSlidesloop 组合

最终结论

Swiper 轮播中,如果需要让中间 slide 和两侧 slide 尺寸不同,不要直接修改 .swiper-slide 的尺寸。

更稳的做法是:

.swiper-slide 负责稳定布局,内部 .slide-card 负责视觉变化。

这样既能实现中间尺寸变大、两侧尺寸缩小的效果,又能避免切换时出现的各种问题(因为 slide 滑块的尺寸变化严重干扰了滑动计算)。


已发布

分类

来自

标签:

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注