简介
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 - 高质量源码分享平台-免费下载各类网站源码与模板及前沿动态资讯