You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现触摸滑动轮播时卡片的旋转与位置缩放效果

支持滑动交互的旋转缩放轮播实现

核心计算逻辑

你需要的缩放规则可以通过线性插值直接计算,不需要复杂的自定义缓动函数,先定义固定常量:

  • 单卡片宽度: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:居中卡片层级最高,越往两侧层级越低,避免侧边卡片遮挡中心卡片

现有代码问题修正

你当前的代码存在两个核心问题:

  1. 仅修改了中间卡片的transform属性,左右两侧卡片没有跟随滑动更新状态
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 05:31:08