You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Framer Motion实现基于鼠标移动的平滑Transform动画?

解决Framer Motion中卡片3D旋转的平滑过渡问题

核心问题分析

你当前直接通过transform.set()赋值旋转参数,属于瞬时更新,不会触发Framer Motion的过渡动画。要实现平滑效果,需要利用Framer Motion的**弹簧动画(Spring)**来驱动旋转值的渐进变化,而非直接设置静态值。

修改后的完整代码

import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";
import { useState, useRef } from "react";

const HoverCard = ({ i }: { i: number }) => {
  const [isMaximized, setIsMaximized] = useState<boolean>(false);
  const cardRef = useRef<HTMLDivElement>(null);
  // 用弹簧动画控制X/Y轴旋转角度,参数可调整动画质感
  const rotateX = useSpring(0, { stiffness: 200, damping: 25 });
  const rotateY = useSpring(0, { stiffness: 200, damping: 25 });
  // 将两个旋转值组合成最终的transform字符串
  const transform = useTransform([rotateX, rotateY], ([x, y]) => 
    `perspective(1000px) rotateX(${x}deg) rotateY(${y}deg)`
  );
  const maxRotation = 18; // 限制最大旋转角度,避免过度倾斜

  const message = ['1', '2', '3', "4", '5', '6', '!', ''];

  const handleClick = () => {
    setIsMaximized(!isMaximized);
  };

  const handleMouseMove = (e: MouseEvent<HTMLDivElement>) => {
    if (!cardRef.current) return;

    const rect = cardRef.current.getBoundingClientRect();
    // 计算鼠标在卡片内的相对位置(0-1范围)
    const xPercent = (e.clientX - rect.left) / rect.width;
    const yPercent = (e.clientY - rect.top) / rect.height;
    // 转换为相对于中心的偏移,映射到设定的旋转角度范围
    const targetRotateY = (xPercent - 0.5) * maxRotation * 2;
    const targetRotateX = -(yPercent - 0.5) * maxRotation * 2;

    // 更新弹簧动画目标值,自动触发平滑过渡
    rotateX.set(targetRotateX);
    rotateY.set(targetRotateY);
  };

  const handleMouseLeave = () => {
    // 鼠标离开时复位到初始角度
    rotateX.set(0);
    rotateY.set(0);
  };

  return (
    <>
      <div ref={cardRef} className={`rounded-[8px] aspect-[16/9]`}>
        <motion.div
          onMouseMove={handleMouseMove}
          onMouseLeave={handleMouseLeave}
          layout={true}
          style={{ transform }}
          className={`bg-black h-full p-4`}
          onClick={handleClick}
        >
          <motion.div
            layout="position"
            className="text-white"
          >
            {message[i]}
          </motion.div>
        </motion.div>
      </div>
    </>
  );
};

export default HoverCard;

关键改动说明

  • 用useSpring实现平滑过渡:useSpring会自动处理值的渐进变化,stiffness(刚度)和damping(阻尼)参数可调整动画的弹性和缓动速度,贴近目标交互的质感。
  • 简化角度计算逻辑:直接在鼠标移动事件中计算相对位置并映射到旋转角度,避免依赖state更新,减少不必要的组件重渲染。
  • useTransform组合旋转值:将两个独立的弹簧旋转值合并为最终的transform字符串,Framer Motion会自动处理该值的动画更新。
  • 新增鼠标离开复位:当鼠标移出卡片时,触发弹簧动画复位到初始角度,完善交互闭环。
  • 移除冗余状态:删掉isHovered、mouseX、mouseY等非必要状态变量,优化性能。

额外优化建议

  • 调整useSpring的stiffness和damping参数,比如将stiffness设为150、damping设为20,可让动画更柔和。
  • 若需完全匹配目标网站的旋转范围,可修改maxRotation值(原网站旋转角度约在±20度区间)。

内容的提问来源于stack exchange,提问作者Ashbury

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 14:06:02