3D 交换卡片(CSS 3D 翻飞换位动效)

CSSJS 许可:自研原创

3D 交换卡片组件:两个卡堆的顶牌一键互换位置,卡片以 rotateY 翻转飞向对方卡堆,含视距、飞行高度、交换时长参数,纯 CSS 3D 加少量 JS 自研实现零依赖。

效果演示(真实可交互)

左右各有一叠三层堆叠的卡片,点击「交换顶牌」按钮,两张顶牌会以 3D 翻转的姿态飞向对方卡堆并重新落位。交换过程中卡片先向前方升起(translateZ 抬高),再横向飞移并绕自身 Y 轴翻转 180 度,落位后新的顶牌自动成为最上层,视觉上就像两副牌在互相「换牌」。

⚛️React
🟢Vue
🟦Svelte
🐍Python
🟨TypeScript
🟩Go

点击按钮,左右卡堆的顶牌 3D 翻转飞向对方卡堆

完整代码

HTML(两个卡堆,卡片用 --i 控制堆叠深度):

<div class="sc3d-stage">
  <div class="sc3d-stack">
    <div class="sc3d-card" style="--i:0">React</div>
    <div class="sc3d-card" style="--i:10">Vue</div>
    <div class="sc3d-card" style="--i:20">Svelte</div>
  </div>
  <div class="sc3d-stack"> <!-- 右侧卡堆结构相同 --> </div>
</div>

CSS(堆叠与交换动画的关键样式):

.sc3d-stack {
  position: relative; width: 132px; height: 178px;
  perspective: 900px;          /* 3D 立体感来源 */
  transform-style: preserve-3d;
}
.sc3d-card {
  position: absolute; inset: 0;
  transform: translateZ(calc(var(--i) * 1px));  /* 深度越大越靠前 */
  transition: transform .6s cubic-bezier(.4, .1, .2, 1);
}
.sc3d-card.sc3d-swap {
  transform: translate3d(var(--tx), var(--ty), 80px) rotateY(180deg);
}

JS(测量两个卡堆的距离,驱动交换与落位重排):

const stacks = [left, right];
const tops = stacks.map((s) => [...s.children].at(-1)); // 取每堆顶牌
tops.forEach((card, i) => {
  const from = card.getBoundingClientRect();
  const to = stacks[1 - i].getBoundingClientRect();
  card.style.setProperty('--tx', (to.left - from.left) + 'px');
  card.style.setProperty('--ty', (to.top - from.top) + 'px');
  card.classList.add('sc3d-swap');
  card.addEventListener('transitionend', () => {
    /* 落位:关掉过渡、把卡片 DOM 移动到对方卡堆、重排 --i */
    stacks[1 - i].appendChild(card);
  });
});

实现要点:交换动画完全由 transform 过渡完成,JS 只负责两件事——用 getBoundingClientRect 测量起终点位移量,以及动画结束后把卡片移动到对方的卡堆容器里。--i 重新赋值后,新顶牌的 translateZ 最大,自然盖在最上面。

参数说明

参数位置说明
视距.sc3d-stack 的 perspective400-1200px,决定交换时的立体感
交换时长.sc3d-card 的 transition300-900ms,配合 cubic-bezier 更跟手
飞行高度.sc3d-swap 的 translate3d 第三维数值越大,卡片越「飘」向观者
堆叠深度每张卡片的 --i增量越大层差越明显,建议 6-15
交换频率连点保护 busy 标志动画进行中忽略新点击,防止错位

兼容性与性能

常见问题

Q: 交换后卡片位置错乱了? 最常见的两个原因:一是落位时没有先关掉 transition 就移除 sc3d-swap 类,卡片会「弹」回去;二是忘记重排 --i,新顶牌没有获得最大的 translateZ,看起来叠层顺序不对。

Q: 卡片交换过程中会穿透其他层? 交换中的卡片通过 translateZ(80px) 抬到最前,只要这个值大于堆叠总深度(卡片数 × 增量)就不会被遮挡。若堆叠较深,同步把 80px 调大即可。

Q: 可以改成三堆甚至更多堆循环交换吗? 可以。JS 里的目标堆索引 1 - i 换成 (i + 1) % n 即变成循环轮换,其余逻辑不变,很适合做「轮播展示」场景。

Q: 能商用吗? 可以。本组件为粤喵自研原创实现,免费商用无需署名。

许可与来源

  • 许可证:自研原创 —— 粤喵自研原创,可免费用于个人与商业项目,无需署名。查看来源公示与许可清单
  • 来源:粤喵团队原创自研(从效果思路到代码实现均为独立完成,未直接复制第三方代码)
  • 首次发布:2026-09-01 · 最近更新:2026-09-01
  • 修改日志:2026-09-01 首次发布;2026-09-13 补充许可证与来源标注、复制按钮说明与法务页入口
  • 纠错:联系页面 / min@cactus.asia,2 个工作日内核实处理

常见问题

这个组件可以商用吗?

可以。本组件为粤喵自研原创,可免费用于个人和商业项目,无需署名。

浏览器兼容性如何?

支持所有现代浏览器(Chrome / Firefox / Safari / Edge 最新版本)。旧版 IE 不支持 CSS 动画相关特性。

怎么修改动画参数?

直接调整 CSS 中的 animation-duration(时长)、animation-timing-function(缓动曲线)等属性即可,代码中有注释说明。

✍️ 作者与审核