用途
Stacked Cards 是一种前端界面实现模式,用于创建水平扇形展开或层叠排布的卡片堆栈。卡片按顺序相互重叠,鼠标悬停时卡片在原地向上抬起,同时保持原有的层叠关系不被破坏。该模式适合构建专辑浏览器、卡片扇形展示、堆叠卡片列表等交互场景。
解决的问题
资料指出层叠卡片常出现三个 bug:
- 堆叠顺序错误:数组中靠后的卡片显示在上层。
- 悬停破坏层叠:在悬停时改变 z-index,扰乱了视觉顺序。
- Tooltip 被困在堆叠上下文中:卡片内部的 Tooltip 无法突破父级的 z-index 限制。
该模式声称同时解决这三个问题。
核心概念
- 数组中第一个元素位于最前(z-index 最高)。
- 后续每个元素位于其后并向右偏移。
- 悬停时卡片向上抬起,但不改变 z-index。
核心实现
实现基于 React 与 Next.js(".use client"、useState、next/image),关键点如下:
1. 反转渲染顺序
正确做法是使用 [...cards].reverse().map(),让第一张卡片在 DOM 中最后渲染,从而显示在最上层;并通过 const cardIndex = arr.length - 1 - renderIndex; 将渲染索引转换回实际卡片索引。资料说明原因:当同一父级内 z-index 相同时,DOM 顺序决定堆叠顺序。
2. 悬停时不改变 z-index
z-index 仅由位置决定:const zIndex = arr.length - cardIndex;。若在悬停时修改 z-index,卡片会跳到最前,破坏物理堆叠的视觉错觉;真实卡片的抬起应保持在前面卡片之后。
3. 仅做悬停位移(不缩放)
const translateY = hoveredIndex === cardIndex ? -20 : 0;。资料建议避免使用 scale,因为缩放会让卡片视觉上"弹出"。
4. Tooltip 放在堆叠上下文之外
错误做法是将 Tooltip 放在卡片 div 内部,会被父级 z-index 困住,即使设置 z-50 也无效。正确做法是将 Tooltip 作为兄弟元素渲染在卡片循环之外。原因:子元素无法逃出父级的堆叠上下文。
5. 居中公式
const spacing = 40; // Horizontal offset between cards
const cardSize = 130; // Card width
const stackWidth = (cards.length - 1) * spacing + cardSize;
每张卡片的水平偏移:const translateX = -stackWidth / 2 + cardIndex * spacing + cardSize / 2;
然后配合 left-1/2 与 translateX(calc(-50% + ${translateX}px)) 实现居中。
6. 导航时清除悬停状态
在切换卡片选择与返回列表视图时调用 setHoveredIndex(null);,否则返回列表视图时先前悬停的卡片仍会保持抬起状态。
Tooltip 位置计算
资料给出的计算:容器高度 180px、卡片高度 130px、抬起 20px 时,抬起后卡片顶部 = 中心 - 半卡高 - 抬起 = 90 - 65 - 20 = 5px。Tooltip 位置为 calc(50% - 95px)(即中心 50% - 半卡高 65px - 抬起 20px - 间隙 10px),并用 translateY(-100%) 向上移动自身高度。
尺寸变体
| 场景 | 卡片尺寸 | 间距 | 抬起 | 容器高度 |
|---|---|---|---|---|
| 预览 | 80px | 28px | -12px | 110px |
| 完整 | 130px | 40px | -20px | 180px |
明暗模式变体
| 元素 | 浅色模式 | 深色模式 |
|---|---|---|
| 卡片阴影 | shadow-zinc-400/50 | shadow-black/70 |
| Tooltip 背景 | bg-white/95 | bg-zinc-800/95 |
| Tooltip 边框 | border-zinc-200/80 | border-zinc-700/80 |
| Tooltip 文字 | text-zinc-900 | text-zinc-100 |
检查清单
- 使用
[...array].reverse().map()反转渲染顺序 - 由渲染索引计算卡片索引:
arr.length - 1 - renderIndex - z-index 随卡片索引递减:
arr.length - cardIndex - 悬停时不改变 z-index
- 悬停只应用 translateY,不使用 scale
- Tooltip 作为兄弟元素渲染在卡片循环之外
- Tooltip 设置 pointer-events-none 以避免悬停干扰
- 视图切换时清除悬停状态
- 容器使用 relative 并设置固定高度
- 卡片使用 absolute left-1/2 配合计算出的 translateX
适用场景与限制
资料标明其用于专辑浏览器、卡片扇形、堆叠卡片列表等场景,基于 React 与 Next.js 实现。关于其他框架支持、浏览器兼容性、具体性能数据与授权许可,资料暂未提供。