
本文详解如何利用 framer motion 的 animate + initial + transition 机制,替代手动设置 style.transform,实现银行卡片式响应式 3d 倾斜(rotatex/rotatey)动画,并确保过渡自然、离开时复位。
本文详解如何利用 framer motion 的 animate + initial + transition 机制,替代手动设置 style.transform,实现银行卡片式响应式 3d 倾斜(rotatex/rotatey)动画,并确保过渡自然、离开时复位。
在构建交互式 UI(如金融产品卡片、数字名片或沉浸式展示页)时,常需实现「鼠标悬停时卡片随光标位置动态倾斜」的效果——类似夏威夷国家银行官网的 3D 卡片。该效果并非简单绑定 transform 字符串,而是需借助 Framer Motion 的声明式动画系统,实现可缓动、可中断、可复位的物理感过渡。
核心问题在于:原始代码中通过 useMotionValue('rotateX(0deg)') 并手动拼接 CSS 字符串赋值给 style 属性,这绕过了 Framer Motion 的动画引擎,导致所有变化均为即时(1:1 映射),无任何缓动(easing)或过渡(transition)。
✅ 正确做法是:交由 Framer Motion 管理状态与动画,使用 initial 定义初始姿态,animate 动态驱动受控属性(如 rotateX、rotateY),并配合 transition 统一控制动画曲线与时长。
以下是重构后的关键逻辑(精简可运行版本):
import { motion, useMotionValue, useEffect } from "framer-motion";
import { useState, useRef } from "react";
const HoverCard = ({ i }: { i: number }) => {
const [isMaximized, setIsMaximized] = useState(false);
const [mouseX, setMouseX] = useState(0);
const [mouseY, setMouseY] = useState(0);
const cardRef = useRef<htmldivelement>(null);
const angle = 40; // 控制倾斜灵敏度:值越小,倾斜越剧烈
const message = ['1', '2', '3', '4', '5', '6', '!', ''];
const handleClick = () => setIsMaximized(!isMaximized);
const handleMouseMove = (e: React.MouseEvent<htmldivelement>) => {
if (!cardRef.current) return;
const rect = cardRef.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const relX = e.clientX - centerX;
const relY = e.clientY - centerY;
setMouseX(relX);
setMouseY(relY);
};
const handleMouseLeave = () => {
setMouseX(0);
setMouseY(0);
};
// 计算实时旋转角度(单位:deg)
const rx = -mouseY / (cardRef.current?.clientWidth || 1) * angle;
const ry = mouseX / (cardRef.current?.clientHeight || 1) * angle;
return (
<div ref="{cardRef}" classname="rounded-[8px] aspect-[16/9] overflow-hidden">
<motion.div onmousemove="{handleMouseMove}" onmouseleave="{handleMouseLeave}" layout initial="{{" rotatex: rotatey: transformperspective: animate="{{" rx ry transition="{{" type: stiffness: damping: restdelta: classname="bg-black h-full p-4 cursor-pointer" onclick="{handleClick}"><motion.div layout="position" transition="{{" type: stiffness: classname="text-white text-4xl font-bold">
{message[i]}
</motion.div></motion.div>
</div>
);
};
export default HoverCard;</htmldivelement></htmldivelement>
? 关键要点说明:
- ✅ 禁用 style.transform 手动赋值:Framer Motion 的 animate 属性会自动合成 transform,包括 perspective、rotateX、rotateY 等,无需字符串拼接;
- ✅ initial + animate 是声明式动画基石:initial 定义起始状态(如 rotateX: 0),animate 提供目标值,Motion 自动插值并应用 transition;
- ✅ onMouseLeave 必须显式重置状态:否则鼠标移出后角度保持最后值,失去“回弹”效果;
- ✅ 推荐使用 spring 过渡类型:相比 tween,spring 更具物理真实感(弹性阻尼),stiffness 和 damping 可精细调节响应速度与惯性;
- ⚠️ 注意坐标系一致性:clientX/clientY 与 getBoundingClientRect() 均基于视口,避免混用 pageX/pageY(含滚动偏移);
- ? 性能提示:rotateX/rotateY 为 GPU 加速属性,无需额外优化;但若卡片数量多,建议对 handleMouseMove 添加防抖(如 useThrottleCallback)或节流。
最终效果:卡片随鼠标轻盈倾斜,移出区域后自然回正,动画流畅、可中断、符合人机交互直觉——这才是 Framer Motion 声明式动画的正确打开方式。










