如何实现触摸滑动轮播时卡片的旋转与位置缩放效果
支持滑动交互的旋转缩放轮播实现
核心计算逻辑
你需要的缩放规则可以通过线性插值直接计算,不需要复杂的自定义缓动函数,先定义固定常量:
- 单卡片宽度:
CARD_WIDTH = 300(和现有代码的卡片宽度保持一致) - 侧边卡片X轴偏移量:
SIDE_OFFSET = 100(初始状态左右卡片距离中心的水平距离) - 最大缩放值(居中卡片):
MAX_SCALE = 1 - 最小缩放值(侧边卡片):
MIN_SCALE = 0.8 - 侧边卡片最大Y轴旋转角度:
MAX_ROTATE = 30(数值越大3D倾斜效果越明显,设为0则无旋转效果)
滑动过程中,以当前居中卡片的位置为0基准,计算每张卡片相对于中心位置的偏移系数offsetRatio(卡片偏移距离 / 单卡片宽度),所有动画参数都基于这个系数计算:
- 缩放值:
scale = MAX_SCALE - Math.abs(offsetRatio) * (MAX_SCALE - MIN_SCALE) - Y轴旋转角度:
rotateY = -offsetRatio * MAX_ROTATE(左侧卡片向右倾斜、右侧卡片向左倾斜,符合3D透视逻辑) - X轴位移:
translateX = offsetRatio * (CARD_WIDTH - SIDE_OFFSET * 2) - 层级z-index:居中卡片层级最高,越往两侧层级越低,避免侧边卡片遮挡中心卡片
现有代码问题修正
你当前的代码存在两个核心问题:
- 仅修改了中间卡片的transform属性,左右两侧卡片没有跟随滑动更新状态
- touchend事件中偏移量累加逻辑错误,没有做滑动吸附对齐(滑动半卡不会自动对齐到最近卡片位置)
完整可运行代码
JS部分
const CONTAINER_FLEX = document.querySelector('.container-flex'); const items = document.querySelectorAll('.item'); // 配置常量 const CARD_WIDTH = 300; const SIDE_OFFSET = 100; const MAX_SCALE = 1; const MIN_SCALE = 0.8; const MAX_ROTATE = 30; const SWIPE_THRESHOLD = CARD_WIDTH / 3; // 滑动切页阈值 let touchStartX = 0; let touchMoveX = 0; let currentIndex = 1; // 初始居中卡片索引 let currentTranslate = 0; let lastTranslate = 0; // 更新所有卡片状态 function updateCards(translate, isAnimate = true) { // 切换动画开关 CONTAINER_FLEX.style.transition = isAnimate ? 'transform 400ms cubic-bezier(0.165, 0.84, 0.44, 1)' : 'none'; items.forEach((item, index) => { // 计算当前卡片相对于中心的偏移系数 const offsetRatio = (index * CARD_WIDTH + translate) / CARD_WIDTH; const absRatio = Math.abs(offsetRatio); // 超出左右两侧范围的卡片直接隐藏,减少性能消耗 if (absRatio > 1.5) { item.style.opacity = '0'; return; } item.style.opacity = '1'; // 计算动画属性 const scale = MAX_SCALE - absRatio * (MAX_SCALE - MIN_SCALE); const rotateY = -offsetRatio * MAX_ROTATE; const translateX = offsetRatio * (CARD_WIDTH - SIDE_OFFSET * 2); const zIndex = 10 - Math.round(absRatio); // 应用样式 item.style.transform = `translateX(${translateX}px) rotateY(${rotateY}deg) scale(${scale})`; item.style.zIndex = zIndex; }); } // 初始化状态 updateCards(0, false); CONTAINER_FLEX.addEventListener('touchstart', (event) => { touchStartX = event.touches[0].pageX; // 滑动开始时关闭动画,保证跟手 CONTAINER_FLEX.style.transition = 'none'; }); CONTAINER_FLEX.addEventListener('touchmove', (event) => { touchMoveX = event.touches[0].pageX; currentTranslate = lastTranslate + (touchMoveX - touchStartX); updateCards(currentTranslate, false); }); CONTAINER_FLEX.addEventListener('touchend', () => { // 计算滑动距离,判断切页还是回弹 const moveDistance = touchMoveX - touchStartX; if (moveDistance < -SWIPE_THRESHOLD && currentIndex < items.length - 1) { currentIndex++; } else if (moveDistance > SWIPE_THRESHOLD && currentIndex > 0) { currentIndex--; } // 对齐到当前卡片位置 lastTranslate = -currentIndex * CARD_WIDTH; updateCards(lastTranslate, true); });
CSS部分
*, ::before, ::after { padding: 0; margin: 0; box-sizing: border-box; } body { overflow: hidden; display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100vh; font-family: Arial, Helvetica, sans-serif; line-height: 1.5; background-color: #131b24; } .main-container { padding: 30px 0; height: 300px; width: 900px; border-top: 1px solid #444; border-bottom: 1px solid #444; overflow: hidden; background-color: white; /* 加3D透视,旋转效果更真实 */ perspective: 1000px; } .container-flex { height: 100%; display: flex; justify-content: center; align-items: center; position: relative; } .item { height: 100%; display: flex; justify-content: center; align-items: center; min-width: 300px; max-width: 300px; position: absolute; /* 3D变换必备属性 */ transform-style: preserve-3d; backface-visibility: hidden; } .item h1 { font-size: 40px; color: white; } /* 卡片背景色,去掉原来写死的transform和z-index */ .item-1 { background-color: #2c3e50; } .item-2 { background-color: #3498db; } .item-3 { background-color: #1abc9c; }
HTML部分
和原有结构完全一致,不需要修改:
<div class="main-container"> <div class="container-flex" id="container-flex"> <div class="item item-1"> <h1>1</h1> </div> <div class="item item-2"> <h1>2</h1> </div> <div class="item item-3"> <h1>3</h1> </div> </div> </div>
参数调整说明
- 要调整旋转倾斜幅度:修改
MAX_ROTATE数值,设为0就是纯平移缩放效果,没有3D旋转 - 要调整左右卡片和中心卡片的距离:修改
SIDE_OFFSET数值,数值越大两侧卡片离中心越远 - 要调整缩放差异:修改
MIN_SCALE数值,数值越小两侧卡片缩小越明显 - 要调整滑动灵敏度:修改
SWIPE_THRESHOLD数值,数值越小越容易触发切页
内容的提问来源于stack exchange,提问作者Vince
相关产品推荐
相关产品推荐

