卡片悬停展开交互

悬停展开三张游戏卡片,点击置前、再次轻跳,让内容选择更有层次。

复刻提示词

使用我提供的三张图片,用 HTML、CSS、JavaScript 实现一个可直接运行的卡片堆叠交互:悬停展开、指向抬起、点击置前、移开收拢。仅由用户操作触发,不是自动轮播。请按以下完整规则实现,不自行改变布局或添加其他动效。

输入与交付
将三张图片按提供顺序绑定为稳定 ID A、B、C,初始排序为 [A,B,C]。图片路径与标题集中放在可编辑的数据数组中;优先使用我提供的标题,没有标题时使用「图片 1」「图片 2」「图片 3」,不要依赖图片识别生成名称。例如本例标题为 GTA6、影之刃零、Ocarina of Time,也可以替换为任意主题。
每个卡片始终绑定自己的图片和标题,排序只改变槽位,不替换图片、不重建卡片元素。图片直接使用提供的文件或可用路径,不用占位图,不主动另找素材。
输出完整的单文件 index.html,包含 HTML、CSS、JavaScript,无需构建和第三方动画库;与三张图片放在对应路径后可直接在浏览器打开。不要只给伪代码、片段或实现思路。

1. 布局与初始状态
背景 #1c1c19,卡片组居中。使用固定 500×400px 逻辑画布,透视距离 900px,透视原点 (250,200),卡片的布局中心为 (250,224)。以下尺寸均为逻辑画布内的 CSS px,位移相对卡片布局中心,向右、向下为正。卡片中心作为变换原点。小屏仅整体等比例缩放,给标题和抬起动作留足空间,不分别缩放卡片或推动周围布局。
初始三张卡片沿中心叠放;槽位 0、1、2 从前到后分别为:256×194px、y=0;230×180px、y=-24;208×160px、y=-44,x 都为 0。层级按当前排序从前到后为 3、2、1。尺寸和位置属于槽位,不属于某张固定图片,换位后按新槽位计算。圆角 10px,图片 object-fit:cover 居中裁切,并在独立圆角容器内裁切,标题不参与图片裁切。后两层亮度分别为 92%、84%。设置明确的图片与卡片尺寸,图片加载前后布局不变。

2. 展开与透视
鼠标进入卡片组后展开:三张卡片尺寸均变为 240×240px,x 分别为 -88、0、88px,y 归零,亮度恢复 100%。
每张卡片变换顺序为平移 → rotateX(10deg) → rotateY(-36deg),共用父容器的 perspective:900px。保持相互重叠;单纯悬停不改变排序,不改成普通平铺网格。
首次加载直接显示叠放状态,不播放入场动画。设置一个固定的卡片组交互区域覆盖三张卡片的运动范围,不用正在移动的单张卡片边界判断是否离开整组。无轮播定时器;鼠标离开卡片组时收拢。

3. 选中反馈
鼠标指向某张卡片时,该卡片上抬 64px。选中首张时,中间张跟随上抬 32px,末张保持基线,形成 y=-64、-32、0px 的阶梯;选中末张时对称处理。选中中间张时,仅它上抬 64px。跟随卡片不显示标题,尺寸、角度、横向间距和层级不变。选中卡片上方显示对应标题,标签距图片顶边约 18px,浅色底、深色字、12px/600 字重、圆角 6px。
跟随位移由现有抬起进度推导,不增加定时器或重启动画:middleWeight=max(0,1-|当前槽位索引-1|),outerLift=Σ min(1,|各卡片当前槽位索引-1|)×clamp(各自抬起进度,0,1.015),额外 y=-32×middleWeight×outerLift。使用当前连续槽位索引计算,点击换位期间保持连续;标题仍只跟随自身选中进度,轻跳不带动其他卡片。
标题随抬起进度从透明到不透明,并从下方 6px 回到原位。切换目标时旧卡片回落、标题淡出,新卡片同时抬起、标题淡入。
卡片移开鼠标后不要立刻取消选中。使用真实 pointermove 改变悬停选择,不因卡片移动产生的 pointerenter/pointerleave 自动换选;进入卡片组只展开并保留当前选择。跨越卡片间隙时保留当前选择,直到鼠标移向另一张卡片或离开整个卡片组。

4. 连续弹簧
使用十个独立弹簧通道:一个展开通道,以及三张卡片各自的抬起、槽位索引与轻跳通道。频率参数 ω=18,阻尼比 ζ=0.86,约 500ms 收敛,轻微回弹,不夸张弹跳。
每次改变目标都保留当前值和速度,不重新从 0 开始。帧更新使用真实时间差,不按帧数固定累加。
精确采样:a=ωζ,b=ω√(1-ζ²),d=当前值-目标,c=(当前速度+a×d)/b。
经过 dt 秒:值=目标+exp(-a×dt)×(d×cos(b×dt)+c×sin(b×dt));速度=exp(-a×dt)×[-a×(d×cos(b×dt)+c×sin(b×dt))-d×b×sin(b×dt)+c×b×cos(b×dt)]。
展开进度用于尺寸、位置、角度和亮度插值;抬起进度用于纵向位移和标题显隐。值误差小于 0.0001 且速度小于 0.001 时停到精确目标,停止刷新。

5. 点按、键盘与减少动态效果
点击非第一张时,把其稳定 ID 移到第一位,其他两张保持相对顺序。例如 [A,B,C] 点击 C 后变为 [C,A,B]。第一位就是叠放最前层,也是展开后最左槽位。被点击的卡片保持抬起反馈;槽位索引从当前值和速度连续移向新目标,尺寸、位移和亮度按索引插值,层级按目标排序更新。不要重建卡片 DOM、替换图片或跳转游戏页面。
点击已经是第一张的卡片,不改排序、展开状态或基础位置,只在原位轻跳:轻跳通道目标保持 0,点击赋予初速度 9,复用相同弹簧;附加 y=-64×max(0,轻跳值),约上跳 13px 再回到原位。保留当前位移,不闪回首帧。减少动态效果时只提示已在最前,不跳动。
点击反馈不永久锁定展开状态:鼠标离开卡片组后自动回落、隐藏标题并收拢,保留点击后的排序。若置前或轻跳尚未完成,先等弹簧收敛再收拢;期间鼠标重新进入则取消待收拢状态。不使用固定延迟定时器。鼠标点击留下的按钮焦点不阻止收拢;键盘焦点在组内时保持可操作,焦点离开则收拢。触屏没有鼠标离开事件,点击空白收拢。
点击空白或按 Escape 收拢但保留排序;重置按钮恢复初始排序并收拢。触屏与鼠标执行相同点击规则。指针点击引起的 focus 不额外展开首张,键盘 Tab 聚焦才展开对应卡片。使用真实 button,Enter/空格执行点击行为,左右箭头按当前排序切换,Home/End 到当前首尾。
减少动态效果开启时立即更新布局和标题,不使用弹簧、倾斜或抬起。进入后台冻结动画时间,返回后继续;离开页面清理事件、ResizeObserver 和帧回调。

6. 验收
检查初始叠放、三张展开、分别悬停、点击置前、首张原位轻跳、点击后鼠标移开自动收拢、运动中移开与重新进入、快速连续置前、收拢后保留排序、重置、移动端点按、键盘、减少动态效果,以及图片未加载/窄屏时的稳定尺寸。图片不超出圆角,标题不裁切,周围文章布局不跳动。
最终交付必须按这套参数运行;先核对 A、B、C 在换位前后的身份与排序,再检查三种选中姿态和移开后的收拢。不要把效果替换成普通图片轮播、翻页或平铺网格。

动画源代码

会员解锁后可查看并复制完整 Web / Swift 源码。

<!-- # 保存为 index.html,用浏览器打开;三张演示图已内嵌,可离线运行。 -->
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>卡片悬停展开交互</title>
<!-- Hover to expand; select a card to lift it and show its title.
     Clicking moves a card to the front; clicking the front card gives it a small jump.
     No autoplay, carousel timer, URL navigation, or recreated image nodes.
     Current value and velocity are retained when the spring target changes.
     Reduced motion updates the layout immediately without tilt or lift.
     Artwork belongs to the game rights holders; replace with licensed images before publishing. -->
<style>
:root { font-family:system-ui,sans-serif; }
* { box-sizing:border-box; }
body { margin:0; padding:20px; background:#f7f6f2; }
button { font:inherit; }
main { max-width:760px; margin:auto; }
.motion-preview { margin:20px 0; border:1px solid #ddd8cc; border-radius:16px; overflow:hidden; }