react强大且灵活的动画库——react-spring

发布于:2025-07-03 ⋅ 阅读:(21) ⋅ 点赞:(0)

简介

react-spring 是一个强大且灵活的 React 动画库,基于弹性物理模型,能够帮助开发者轻松实现高性能、流畅自然的动画效果。无论是简单的过渡动画,还是复杂的交互场景,react-spring 都能提供优雅的解决方案。

主要特性

  • 物理驱动动画:基于弹性物理模型,动画更自然、真实。
  • 声明式 API:与 React 风格一致,易于理解和维护。
  • 高性能:底层优化,适合移动端和桌面端。
  • 支持多种动画类型:支持 spring、trail、transition、keyframes 等多种动画方式。
  • 与 React 生态无缝集成:支持函数组件和 hooks,兼容 React 18。

适用场景

  • 页面元素的进出场动画
  • 列表项的动态增删
  • 拖拽、缩放等交互动画
  • 数据可视化中的动态效果
  • 复杂 UI 组件的状态切换

为什么选择 react-spring?

  • 语法简洁,易于上手
  • 动画效果自然,用户体验佳
  • 社区活跃,文档完善
  • 支持 TypeScript

安装方式

npm install @react-spring/web
# 或者
yarn add @react-spring/web

使用示例

useSpring 示例

import { useSpring, animated } from "@react-spring/web";

function MyComponent() {
  const styles = useSpring({
    from: { opacity: 0 },
    to: { opacity: 1 },
  });

  return <animated.div style={styles}>Hello, react-spring!</animated.div>;
}

useSprings 示例

import { useSprings, animated } from "@react-spring/web";

function MyComponent() {
  const items = ["第一个元素", "第二个元素", "第三个元素"];
  const springs = useSprings(
    items.length,
    items.map((item, i) => ({
      from: { opacity: 0, transform: "translateY(30px)" },
      to: { opacity: 1, transform: "translateY(0px)" },
      delay: i * 200,
    }))
  );

  return (
    <div>
      {springs.map((styles, i) => (
        <animated.div key={i} style={styles}>
          {items[i]}
        </animated.div>
      ))}
    </div>
  );
}

useSpringValue 示例

import { useSpringValue, animated } from "@react-spring/web";

function MyComponent() {
  const value = useSpringValue(0);

  const handleAdd = () => {
    value.start(value.get() + 100);
  };

  return (
    <div className="max-w-sm mx-auto mt-20 bg-white rounded-2xl shadow-xl p-10 flex flex-col items-center space-y-8 border border-gray-100">
      <animated.div
        className="text-6xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 select-none drop-shadow-lg"
        style={{
          // @ts-ignore
          innerText: value.to((v) => Math.round(v)),
        }}
      >
        {value.to((v) => Math.round(v))}
      </animated.div>
      <button
        className="px-8 py-3 bg-gradient-to-r from-black via-gray-800 to-gray-900 text-white rounded-full shadow-lg hover:scale-105 hover:shadow-2xl transition-all duration-200 text-lg font-semibold tracking-wide focus:outline-none focus:ring-2 focus:ring-black/40"
        onClick={handleAdd}
      >
        增加100
      </button>
    </div>
  );
}

useTransition 示例

import { useTransition, animated } from "@react-spring/web";

function MyComponent() {
  const [items, setItems] = React.useState([
    { id: 1, text: "React" },
    { id: 2, text: "Spring" },
    { id: 3, text: "动画" },
  ]);
  const transitions = useTransition(items, {
    keys: (item) => item.id,
    from: { opacity: 0, transform: "translateY(40px) scale(0.9)" },
    enter: { opacity: 1, transform: "translateY(0px) scale(1)" },
    leave: { opacity: 0, transform: "translateY(-40px) scale(0.9)" },
    config: { tension: 220, friction: 20 },
    trail: 100,
  });

  const addItem = () => {
    const id = Math.max(0, ...items.map((i) => i.id)) + 1;
    setItems([...items, { id, text: `新项${id}` }]);
  };

  const removeItem = (id) => {
    setItems(items.filter((item) => item.id !== id));
  };

  return (
    <div className="max-w-md mx-auto mt-20 bg-white rounded-2xl shadow-xl p-10 flex flex-col items-center space-y-8 border border-gray-100">
      <div className="flex gap-4">
        <button
          className="px-6 py-2 bg-gradient-to-r from-green-400 to-blue-500 text-white rounded-full shadow hover:scale-105 transition-all duration-200 font-semibold"
          onClick={addItem}
        >
          添加项
        </button>
      </div>
      <div className="w-full space-y-3 min-h-[120px]">
        {transitions((style, item) => (
          <animated.div
            key={item.id}
            style={style}
            className="flex items-center justify-between px-6 py-3 bg-gradient-to-r from-blue-100 to-purple-100 rounded-xl shadow group cursor-pointer hover:shadow-lg transition-all"
            onClick={() => removeItem(item.id)}
          >
            <span className="font-medium text-lg text-gray-800">
              {item.text}
            </span>
            <span className="text-xs text-gray-400 opacity-0 group-hover:opacity-100 transition">
              点击删除
            </span>
          </animated.div>
        ))}
      </div>
      <div className="text-gray-400 text-xs">点击列表项可删除</div>
    </div>
  );
}

useChain 示例

import {
  useTransition,
  animated,
  useSpring,
  useSpringRef,
  useChain,
} from "@react-spring/web";

function MyComponent() {
  // 卡片整体淡入
  const cardRef = useSpringRef();
  const cardSpring = useSpring({
    ref: cardRef,
    from: { opacity: 0, transform: "scale(0.9)" },
    to: { opacity: 1, transform: "scale(1)" },
    config: { tension: 180, friction: 18 },
  });

  // 内容依次淡入
  const contentRef = useSpringRef();
  const items = ["useChain", "链式动画", "react-spring"];
  const transitions = useTransition(items, {
    ref: contentRef,
    from: { opacity: 0, transform: "translateY(30px)" },
    enter: { opacity: 1, transform: "translateY(0px)" },
    trail: 200,
  });

  // 链式触发动画
  useChain([cardRef, contentRef], [0, 0.5]);

  return (
    <animated.div
      style={cardSpring}
      className="max-w-md mx-auto mt-20 bg-white rounded-2xl shadow-xl p-10 flex flex-col items-center space-y-8 border border-gray-100"
    >
      <div className="text-3xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-blue-500 via-purple-500 to-pink-500 select-none mb-6">
        useChain 动画示例
      </div>
      <div className="w-full space-y-4">
        {transitions((style, item, _, i) => (
          <animated.div
            key={item}
            style={style}
            className="px-6 py-3 bg-gradient-to-r from-blue-100 to-purple-100 rounded-xl shadow text-lg font-semibold text-gray-800 text-center"
          >
            {item}
          </animated.div>
        ))}
      </div>
      <div className="text-gray-400 text-xs mt-4">卡片先出现,内容依次淡入</div>
    </animated.div>
  );
}

useTrail 示例

import { useTrail, animated } from "@react-spring/web";

function TrailList({ items }) {
  const trail = useTrail(items.length, {
    from: { opacity: 0, transform: "translate3d(0,100px,0)" },
    to: { opacity: 1, transform: "translate3d(0,0px,0)" },
  });

  return (
    <div>
      {trail.map((style, index) => (
        <animated.div key={index} style={style}>
          {items[index]}
        </animated.div>
      ))}
    </div>
  );
}

@react-spring/parallax 插件

import React from "react";
import { Parallax, ParallaxLayer } from "@react-spring/parallax";

const Cloud = ({ style = {}, size = 120 }) => (
  <svg width={size} height={size * 0.6} viewBox="0 0 120 60" style={style}>
    <defs>
      <radialGradient id="cloudSunset" cx="60%" cy="40%" r="80%">
        <stop offset="0%" stopColor="#fffbe7" />
        <stop offset="70%" stopColor="#ffb347" />
        <stop offset="100%" stopColor="#ff9966" />
      </radialGradient>
    </defs>
    <ellipse
      cx="45"
      cy="38"
      rx="32"
      ry="18"
      fill="url(#cloudSunset)"
      stroke="#ffb347"
      strokeWidth="2.5"
    />
    <ellipse
      cx="75"
      cy="32"
      rx="22"
      ry="13"
      fill="url(#cloudSunset)"
      stroke="#ffb347"
      strokeWidth="2.5"
    />
    <ellipse
      cx="60"
      cy="45"
      rx="18"
      ry="10"
      fill="url(#cloudSunset)"
      stroke="#ffb347"
      strokeWidth="2.5"
    />
  </svg>
);

// 柔和日出太阳
const SunriseSun = ({ style = {}, size = 120 }) => (
  <svg width={size} height={size * 0.7} viewBox="0 0 120 80" style={style}>
    {/* 柔和光晕 */}
    <ellipse cx="60" cy="70" rx="50" ry="18" fill="#ffb347" opacity="0.35" />
    <ellipse cx="60" cy="70" rx="70" ry="24" fill="#ff9966" opacity="0.18" />
    {/* 夕阳主体 */}
    <circle
      cx="60"
      cy="60"
      r="22"
      fill="#ff9966"
      stroke="#ffb347"
      strokeWidth="4"
    />
    {/* 射线 */}
    {[...Array(12)].map((_, i) => (
      <rect
        key={i}
        x="58"
        y="30"
        width="4"
        height="14"
        fill="#ffb347"
        stroke="#fffbe7"
        strokeWidth="1.5"
        rx="2"
        opacity="0.6"
        transform={`rotate(${i * 30} 60 60)`}
      />
    ))}
  </svg>
);

const SoftGrass = ({ style = {}, width = 1200, height = 80 }) => (
  <svg
    width={width}
    height={height}
    viewBox={`0 0 ${width} ${height}`}
    style={style}
  >
    <defs>
      <linearGradient id="grassGradient" x1="0" y1="0" x2="0" y2="1">
        <stop offset="0%" stopColor="#ffe066" />
        <stop offset="100%" stopColor="#ffb347" />
      </linearGradient>
    </defs>
    <path
      d={`M0,40 Q150,10 300,40 T600,40 T900,40 T1200,40 V80 H0 Z`}
      fill="url(#grassGradient)"
    />
  </svg>
);

const Balloon = ({ style = {}, size = 50, color = "#ffb347" }) => (
  <svg width={size} height={size * 1.2} viewBox="0 0 50 60" style={style}>
    <defs>
      <radialGradient id="balloonMain" cx="60%" cy="40%" r="80%">
        <stop offset="0%" stopColor="#fffbe7" />
        <stop offset="70%" stopColor={color} />
        <stop offset="100%" stopColor="#ff9966" />
      </radialGradient>
    </defs>
    <ellipse
      cx="25"
      cy="25"
      rx="18"
      ry="22"
      fill="url(#balloonMain)"
      stroke="#ffb347"
      strokeWidth="2"
    />
    {/* 吊篮 */}
    <rect
      x="20"
      y="45"
      width="10"
      height="8"
      fill="#ffe066"
      stroke="#ffb347"
      strokeWidth="1.5"
      rx="2"
    />
    {/* 吊绳 */}
    <line x1="25" y1="38" x2="25" y2="45" stroke="#ffb347" strokeWidth="1.5" />
  </svg>
);

const Birds = ({ style = {}, size = 60 }) => (
  <svg width={size} height={size * 0.4} viewBox="0 0 60 24" style={style}>
    <path
      d="M5,18 Q15,8 25,18"
      stroke="#ffb347"
      strokeWidth="2.5"
      fill="none"
      strokeLinecap="round"
    />
    <path
      d="M35,20 Q45,10 55,20"
      stroke="#ffb347"
      strokeWidth="2.5"
      fill="none"
      strokeLinecap="round"
    />
  </svg>
);

export default function ReactSpring() {
  return (
    <div style={{ width: "100vw", height: "100vh" }}>
      <Parallax pages={3} style={{ top: 0, left: 0 }}>
        {/* 柔和清晨天空渐变背景 */}
        <ParallaxLayer
          offset={0}
          speed={0}
          factor={3}
          style={{
            background:
              "linear-gradient(to bottom, #ffb347 0%, #ff9966 20%, #ff5e62 40%, #f9d29d 60%, #f6e7b4 80%, #ffe066 100%)",
          }}
        />
        {/* 柔和草地(波浪形) */}
        <ParallaxLayer
          offset={2.7}
          speed={0}
          factor={0.3}
          style={{ pointerEvents: "none" }}
        >
          <div
            style={{
              position: "absolute",
              left: 0,
              bottom: 0,
              width: "100vw",
              overflow: "hidden",
            }}
          >
            <SoftGrass width={1200} height={80} />
          </div>
        </ParallaxLayer>

        {/* 柔和日出太阳,靠近地平线 */}
        <ParallaxLayer
          offset={0.95}
          speed={0.1}
          style={{ pointerEvents: "none" }}
        >
          <div
            style={{
              position: "absolute",
              left: "50%",
              top: "78%",
              transform: "translateX(-50%)",
            }}
          >
            <SunriseSun size={140} />
          </div>
        </ParallaxLayer>

        {/* 多层柔和云朵 */}
        <ParallaxLayer offset={0.15} speed={0.22} style={{ opacity: 0.85 }}>
          <div style={{ position: "absolute", left: "12%", top: "20%" }}>
            <Cloud size={120} />
          </div>
          <div style={{ position: "absolute", left: "65%", top: "12%" }}>
            <Cloud size={90} />
          </div>
        </ParallaxLayer>
        <ParallaxLayer offset={0.7} speed={0.32} style={{ opacity: 0.7 }}>
          <div style={{ position: "absolute", left: "30%", top: "38%" }}>
            <Cloud size={100} />
          </div>
        </ParallaxLayer>
        <ParallaxLayer offset={1.2} speed={0.18} style={{ opacity: 0.8 }}>
          <div style={{ position: "absolute", left: "75%", top: "30%" }}>
            <Cloud size={110} />
          </div>
        </ParallaxLayer>
        <ParallaxLayer offset={2.1} speed={0.22} style={{ opacity: 0.6 }}>
          <div style={{ position: "absolute", left: "20%", top: "70%" }}>
            <Cloud size={80} />
          </div>
        </ParallaxLayer>

        {/* 飞鸟 */}
        <ParallaxLayer offset={1.05} speed={0.5} style={{ opacity: 0.7 }}>
          <div style={{ position: "absolute", left: "60%", top: "60%" }}>
            <Birds size={70} />
          </div>
        </ParallaxLayer>
        <ParallaxLayer offset={1.2} speed={0.7} style={{ opacity: 0.6 }}>
          <div style={{ position: "absolute", left: "30%", top: "55%" }}>
            <Birds size={50} />
          </div>
        </ParallaxLayer>

        {/* 热气球(柔和色) */}
        <ParallaxLayer offset={0.5} speed={0.7} style={{ opacity: 0.85 }}>
          <div style={{ position: "absolute", left: "55%", top: "50%" }}>
            <Balloon size={60} color="#ffb347" />
          </div>
        </ParallaxLayer>
        <ParallaxLayer offset={1.7} speed={0.9} style={{ opacity: 0.8 }}>
          <div style={{ position: "absolute", left: "35%", top: "65%" }}>
            <Balloon size={50} color="#b5e0f7" />
          </div>
        </ParallaxLayer>
        <ParallaxLayer offset={1.3} speed={0.8} style={{ opacity: 0.8 }}>
          <div style={{ position: "absolute", left: "70%", top: "40%" }}>
            <Balloon size={40} color="#ffe066" />
          </div>
        </ParallaxLayer>

        {/* 内容层,温暖柔和字体 */}
        <ParallaxLayer
          offset={0}
          speed={1.1}
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
          }}
        >
          <h1
            className="text-5xl font-extrabold"
            style={{
              color: "#ff9966",
              textShadow:
                "0 4px 24px #ffe066, 0 2px 8px #ffb347, 2px 2px 0 #fff",
            }}
          >
            落日余晖视差动画
          </h1>
        </ParallaxLayer>
        <ParallaxLayer
          offset={1}
          speed={1.3}
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
          }}
        >
          <h1
            className="text-5xl font-extrabold"
            style={{
              color: "#ffb347",
              textShadow:
                "0 4px 24px #ff9966, 0 2px 8px #ffe066, 2px 2px 0 #fff",
            }}
          >
            第二页:霞光万丈
          </h1>
        </ParallaxLayer>
        <ParallaxLayer
          offset={2}
          speed={1.5}
          style={{
            display: "flex",
            alignItems: "center",
            justifyContent: "center",
          }}
        >
          <h1
            className="text-5xl font-extrabold"
            style={{
              color: "#ff5e62",
              textShadow:
                "0 4px 24px #ffe066, 0 2px 8px #ffb347, 2px 2px 0 #fff",
            }}
          >
            第三页:温柔余晖
          </h1>
        </ParallaxLayer>
      </Parallax>
    </div>
  );
}

react强大且灵活的动画库——react-spring - 高质量源码分享平台-免费下载各类网站源码与模板及前沿动态资讯


网站公告

今日签到

点亮在社区的每一天
去签到