3D 交换卡片(CSS 3D 翻飞换位动效)
3D 交换卡片组件:两个卡堆的顶牌一键互换位置,卡片以 rotateY 翻转飞向对方卡堆,含视距、飞行高度、交换时长参数,纯 CSS 3D 加少量 JS 自研实现零依赖。
效果演示(真实可交互)
左右各有一叠三层堆叠的卡片,点击「交换顶牌」按钮,两张顶牌会以 3D 翻转的姿态飞向对方卡堆并重新落位。交换过程中卡片先向前方升起(translateZ 抬高),再横向飞移并绕自身 Y 轴翻转 180 度,落位后新的顶牌自动成为最上层,视觉上就像两副牌在互相「换牌」。
点击按钮,左右卡堆的顶牌 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 的 perspective | 400-1200px,决定交换时的立体感 |
| 交换时长 | .sc3d-card 的 transition | 300-900ms,配合 cubic-bezier 更跟手 |
| 飞行高度 | .sc3d-swap 的 translate3d 第三维 | 数值越大,卡片越「飘」向观者 |
| 堆叠深度 | 每张卡片的 --i | 增量越大层差越明显,建议 6-15 |
| 交换频率 | 连点保护 busy 标志 | 动画进行中忽略新点击,防止错位 |
兼容性与性能
- 核心依赖
transform-style: preserve-3d与perspective,Chrome 36+ / Safari 9+ / Firefox 49+ / Edge 全支持 - 动画只走 transform 与 opacity 合成路径,GPU 加速,无重排重绘;每张卡片固定 132px 宽,交换过程不触发布局抖动
- 交换逻辑对卡堆内卡片数量无硬性限制,2-6 张表现最佳;更多卡片时调小
--i增量即可 - 演示为点击按钮触发,实际项目中也可改为悬停或定时自动交换,逻辑完全复用
常见问题
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(缓动曲线)等属性即可,代码中有注释说明。