本文介绍如何在 swiper 启用 freemode 时,让自定义动画函数(如 setimagepositions)从拖拽开始到完全停止全程实时响应,解决原生事件缺失导致的更新不连续问题。
Swiper.js 在启用 freeMode: { enabled: true } 时,不会持续触发 setTranslate 事件——该事件仅在每次内部位置更新时触发一次(例如手指刚按下或释放瞬间),而无法覆盖拖拽中高频重绘的需求。因此,依赖 setTranslate 会导致动画卡顿、视觉跳变,无法实现平滑的视差/旋转/位移效果。
为实现“拖拽中持续更新”,需采用主动轮询机制:在检测到滑动开始时启动定时器,在滑动结束后自动清理。Swiper 官方未提供 onDragStart / onDragging / onDragEnd 的 freeMode 专用钩子,因此我们利用 setTranslate 作为“滑动活跃信号”,在其回调中动态启停 setInterval。
准终止:Swiper 本身不暴露“是否已静止”的 API,因此依赖超时机制是当前最可靠方案。如需更高精度,可结合 swiper.isEnd + swiper.progress 变化率做二次判断(进阶可选)。let parallaxAmount = 49;
let verticalAmount = 60;
let rotationAmount = 6;
let freeMoveInterval = null;
const swiper = new Swiper('.swiper', {
slidesPerView: 4,
centeredSlides: true,
spaceBetween: 20,
freeMode: { enabled: true },
});
function setImagePositions() {
$('.swiper-slide').each(function () {
const $slide = $(this);
const $inner = $slide.find('.swiper-slider-inner');
const slideWidth = $slide.width();
const swiperEl = $('.swiper')[0];
const swiperRect = swiperEl.getBoundingClientRect();
const slideRect = $slide[0].getBoundingClientRect();
// 计算相对于 swiper 中心的归一化进度(-1 ~ 1)
const centerOffset = slideRect.left + slideWidth / 2 - (swiperRect.left + swiperRect.width / 2);
const progressCenter = centerOffset / (swiperRect.width + slideWidth);
$inner.css(
'transform',
`translateY(${verticalAmount * progressCenter}%) rotate(${rotationAmount * progressCenter}deg)`
);
});
}
// 在 setTranslate 中启动/重置轮询
swiper.on('setTranslate', () => {
if (freeMoveInterval) clearInterval(freeMoveInterval);
let count = 0;
const maxLoop = 300; // 超时保护:最多执行 300 次(3 秒)
freeMoveInterval = setInterval(() => {
if (count++ >= maxLoop) {
clearInterval(freeMoveInterval);
freeMoveInterval = null;
return;
}
setImagePositions();
}, 10);
});
// 初始化渲染
setImagePositions();✅ 最终效果:无论用户快速甩动、缓慢拖拽,还是中途暂停,.swiper-slider-inner 元素均能实时响应位置变化,实现丝滑的 translateY + rotate 动画。
该方案已在 Swiper v9.4+ 环境下验证稳定,是目前兼容性最佳、侵入性最小的 freeMode 实时更新实践。