Skip to content

Framer Motion高级动画技巧与实战 ​

手势动画进阶 ​

1. 拖拽约束与惯性 ​

tsx
export const DraggableCard = ({ children, onDragEnd }) => {
  const constraintsRef = useRef(null);

  return (
    <motion.div
      ref={constraintsRef}
      style={{
        width: '100%',
        height: '400px',
        background: '#f5f5f5',
        borderRadius: '10px',
        position: 'relative'
      }}
    >
      <motion.div
        drag
        dragConstraints={constraintsRef}
        dragElastic={0.2}
        dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}
        whileDrag={{ scale: 1.1 }}
        onDragEnd={(e, info) => onDragEnd?.(info)}
        style={{
          width: '100px',
          height: '100px',
          background: '#1a73e8',
          borderRadius: '10px',
          cursor: 'grab'
        }}
      >
        {children}
      </motion.div>
    </motion.div>
  );
};

2. 手势动画序列 ​

tsx
export const GestureSequence = ({ onComplete }) => {
  const controls = useAnimationControls();
  const [step, setStep] = useState(1);

  const handlePan = async (e, info) => {
    if (info.offset.x > 100 && step === 1) {
      await controls.start({ x: 100, transition: { type: "spring" } });
      setStep(2);
    } else if (info.offset.y > 100 && step === 2) {
      await controls.start({ y: 100, transition: { type: "spring" } });
      setStep(3);
      onComplete?.();
    }
  };

  return (
    <motion.div
      drag
      dragConstraints={{ left: 0, right: 100, top: 0, bottom: 100 }}
      onPan={handlePan}
      animate={controls}
      style={{
        width: 100,
        height: 100,
        background: step === 3 ? '#34a853' : '#fbbc05',
        borderRadius: 10
      }}
    >
      步骤 {step}/3
    </motion.div>
  );
};

高级动画效果 ​

1. SVG路径动画 ​

tsx
export const AnimatedLogo = ({ pathData, color = "#000", duration = 2 }) => {
  return (
    <motion.svg
      width="100"
      height="100"
      viewBox="0 0 100 100"
      initial="hidden"
      animate="visible"
    >
      <motion.path
        d={pathData}
        stroke={color}
        strokeWidth="2"
        fill="none"
        initial={{ pathLength: 0 }}
        animate={{ pathLength: 1 }}
        transition={{
          duration,
          ease: "easeInOut",
          repeat: Infinity,
          repeatType: "reverse"
        }}
      />
    </motion.svg>
  );
};

2. 3D变换动画 ​

tsx
export const Card3D = ({ children, depth = 50 }) => {
  const [rotateX, setRotateX] = useState(0);
  const [rotateY, setRotateY] = useState(0);

  const handleMouseMove = (e) => {
    const rect = e.currentTarget.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    
    const centerX = rect.width / 2;
    const centerY = rect.height / 2;
    
    setRotateY((x - centerX) / centerX * 10);
    setRotateX((centerY - y) / centerY * 10);
  };

  return (
    <motion.div
      style={{
        perspective: 1000,
        transformStyle: "preserve-3d"
      }}
      onMouseMove={handleMouseMove}
      onMouseLeave={() => {
        setRotateX(0);
        setRotateY(0);
      }}
    >
      <motion.div
        animate={{
          rotateX,
          rotateY,
          z: depth
        }}
        transition={{ type: "spring", stiffness: 300, damping: 30 }}
        style={{
          width: "100%",
          height: "100%",
          backgroundColor: "white",
          borderRadius: 20,
          boxShadow: "0 5px 15px rgba(0,0,0,0.1)"
        }}
      >
        {children}
      </motion.div>
    </motion.div>
  );
};

复杂动画编排 ​

1. 动画序列管理器 ​

tsx
export const SequenceManager = ({ sequences, onComplete }) => {
  const controls = useAnimationControls();
  
  useEffect(() => {
    const runSequences = async () => {
      for (const sequence of sequences) {
        await controls.start({
          ...sequence.animation,
          transition: { duration: sequence.duration }
        });
      }
      onComplete?.();
    };
    
    runSequences();
  }, [sequences]);

  return (
    <motion.div
      animate={controls}
      style={{
        width: 100,
        height: 100,
        background: '#ea4335',
        borderRadius: 10
      }}
    />
  );
};

2. 视差滚动动画 ​

tsx
export const ParallaxSection = ({ layers }) => {
  const [scrollY, setScrollY] = useState(0);

  useEffect(() => {
    const handleScroll = () => {
      setScrollY(window.scrollY);
    };

    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  return (
    <div style={{ height: "200vh", position: "relative" }}>
      {layers.map(layer => (
        <motion.div
          key={layer.id}
          style={{
            position: "fixed",
            top: 0,
            left: 0,
            width: "100%",
            height: "100vh"
          }}
          animate={{
            y: scrollY * layer.speed
          }}
          transition={{
            type: "spring",
            stiffness: 100,
            damping: 30,
            restDelta: 0.001
          }}
        >
          {layer.content}
        </motion.div>
      ))}
    </div>
  );
};

实战案例:交互式产品展示 ​

tsx
export const ProductShowcase = ({ product }) => {
  const [activeFeature, setActiveFeature] = useState(null);
  const controls = useAnimationControls();

  const showFeature = async (index) => {
    setActiveFeature(index);
    await controls.start({
      scale: [1, 1.1, 1],
      rotate: [0, 5, -5, 0],
      transition: { duration: 0.5 }
    });
  };

  return (
    <div style={{ padding: 40 }}>
      <motion.div
        layoutId="product-image"
        style={{
          position: 'relative',
          width: '100%',
          maxWidth: 500,
          margin: '0 auto'
        }}
      >
        <motion.img
          src={product.image}
          alt={product.name}
          animate={controls}
          style={{
            width: '100%',
            height: 'auto',
            borderRadius: 20
          }}
        />
        
        {product.features.map((feature, index) => (
          <motion.div
            key={index}
            initial={{ scale: 0 }}
            animate={{ scale: 1 }}
            transition={{ delay: index * 0.2 }}
            onClick={() => showFeature(index)}
            style={{
              position: 'absolute',
              width: 30,
              height: 30,
              borderRadius: '50%',
              background: activeFeature === index ? '#1a73e8' : '#fff',
              border: '2px solid #1a73e8',
              cursor: 'pointer',
              // 根据需要调整位置
              top: `${20 + index * 20}%`,
              left: `${20 + index * 20}%`
            }}
          />
        ))}
      </motion.div>

      <AnimatePresence mode="wait">
        {activeFeature !== null && (
          <motion.div
            initial={{ opacity: 0, y: 20 }}
            animate={{ opacity: 1, y: 0 }}
            exit={{ opacity: 0, y: -20 }}
            style={{
              marginTop: 20,
              padding: 20,
              background: '#f8f9fa',
              borderRadius: 10
            }}
          >
            {product.features[activeFeature]}
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
};

小结 ​

本文介绍了Framer Motion的高级动画技巧:

  • 复杂手势动画
  • SVG和3D动画
  • 动画序列管理
  • 视差滚动效果
  • 实战案例展示

相关资源 ​