react-spring 是什么
spring 这里是 弹簧 的意思
react-spring 是 基于弹簧物理学(spring-physics based) 的动画库,官网自称能覆盖绝大多数的动画需求。
对比之下,css 是 基于时间(time-based) 的动画体系。
为什么基于弹簧而不基于时间
react-spring 有个弹簧的规则在里面,并不是 css 中的通过 curve 或者 duration 来创建动画。这两者有很大的不同。 我们认为动画是由 time(duration)和 curve 组成的,这本身会带来很大的挑战当我们使 element 在屏幕上自然运动的时候,因为现实世界中没有什么东西是这样运动的。
第一个例子
import { useSpring, animated } from "react-spring";
const Demo1 = () => {
const spring = useSpring({ to: { height: 100 }, from: { height: 0 } });
return (
<animated.div className="bg-blue-300 overflow-hidden" style={spring}>
Demo1 -- 单个高度动画
</animated.div>
);
};
第一步,导入
import { useSpring, animated } from "react-spring";
本动画库在 React 之外进行动画(为了性能的考虑),所以我们的 view 必须怎么处理传给它的animated props
,这正是animated
的作用,它将扩展原生 element 以支持animated values
。
第二步,定义一个弹簧
const spring = useSpring({ to: { height: 100 }, from: { height: 0 } });
一个弹簧简单的把values
从一个状态推向另一个状态。更新是累计的,弹簧会记忆你传给他的所有属性。这些值会自动累计更新,这正是正常的标签不能处理的原因,必须使用animated.div
最后,绑定弹簧和 view
return (
<animated.div className="bg-blue-300 overflow-hidden" style={spring}>
Demo1 -- 单个高度动画
</animated.div>
);
把定义的spring
作为 style 的值传给animated.div
即可
不仅仅对 styles 进行动画
可以把弹簧应用在任何地方,只要你喜欢
对 innerText 动画
function Number() {
const [flip, set] = useState(false);
const { number } = useSpring({
reset: true,
reverse: flip,
from: { number: 0 },
// to: { number: 1 },
number: 1, // 和to: { number: 1 }效果一样
delay: 200,
config: config.molasses,
onRest: () => set(!flip),
});
return <animated.div>{number.to((n) => n.toFixed(2))}</animated.div>;
}
对 scrollTo 动画
function Scrolling() {
const [flip, set] = useState(false)
const words = ['We', 'came.', 'We', 'saw.', 'We', 'kicked', 'its', 'ass.']
const { scroll } = useSpring({
scroll: (words.length - 1) * 50,
from: { scroll: 0 },
reset: true,
reverse: flip,
delay: 200,
config: config.molasses,
onRest: () => set(!flip),
})
return (
<animated.div
style={{
position: 'relative',
width: '100%',
height: 60,
overflow: 'auto',
fontSize: '0.5em',
}}
scrollTop={scroll}>
{words.map((word, i) => (
<div
key={`${word}_${i}`}
style={{ width: '100%', height: 50, textAlign: 'center' }}>
{word}
</div>
))}
</animated.div>
)
}
不仅仅对数字进行动画
以下这些统统可以进行动画
- Colors (names, rgb, rgba, hsl, hsla, gradients)
- CSS Variables (declared on :root) and their fallbacks
- Absolute lengths (cm, mm, in, px, pt, pc)
- Relative lengths (em, ex, ch, rem, vw, vh, vmin, vmax, %)
- Angles (deg, rad, grad, turn)
- Flex and grid units (fr, etc)
- All HTML attributes
- SVG paths (as long as the number of points matches, otherwise use custom interpolation)
- Arrays
- String patterns (transform, border, boxShadow, etc)
- Non-animatable string values (visibility, pointerEvents, etc)
- ScrollTop/scrollLeft
对color进行动画
const Color = () => {
const [flip, set] = useState(false);
const { color } = useSpring({
from: { color: "blue" },
color: "green",
reset: true,
reverse: flip,
delay: 200,
config: config.molasses,
onRest: () => set(!flip),
});
return (
<div>
<animated.div
style={{ background: color, color: "white" }}
className="p-4"
>
hello
</animated.div>
</div>
);
};
对border进行动画
const Border = () => {
const [flip, set] = useState(false);
const { o, xyz, color } = useSpring({
from: { o: 0, xyz: [0, 0, 0], color: "red" },
o: 1,
xyz: [10, 20, 5],
reset: true,
delay: 200,
reverse: flip,
config: config.molasses,
color: "green",
onRest: () => set(!flip),
});
return (
<animated.div
style={{
border: to([o, color], (o, c) => `${o * 10}px solid ${c}`),
}}
>
{o.to((n) => n.toFixed(2)) /* innerText interpolation ... */}
</animated.div>
);
};
几个重要概念
interpolate
interpolate
可以理解为 插值 ,在 react-string 主要通过to
方法或者spring.to
方法来完成。比如下面就是一个interpolate
的例子
style={{
border: to([o, color], (o, c) => `${o * 10}px solid ${c}`),
}}
range
range
是范围的意思,在 react-string 中就是将从from
到to
之间的值进行一个分段,也就是说将动画过程分段,可以理解为 css 中的keyframes
上例子
const Padding = () => {
const [flip, set] = useState(false);
const { color, o, xyz } = useSpring({
from: { color: "blue", o: 0, xyz: [0, 0, 0] },
o: 1,
xyz: [10, 20, 5],
color: "green",
reset: true,
reverse: flip,
delay: 200,
config: config.molasses,
onRest: () => set(!flip),
});
return (
<div>
<animated.div
style={{
// You can also form ranges, even chain multiple interpolations
padding: o
.to({ range: [0, 0.5, 1], output: [0, 0, 10] })
.to((o) => `${o}%`),
}}
className="p-4"
>
{o.to((n) => n.toFixed(2))}
</animated.div>
</div>
);
};
output
就很好理解了,就是到达分界点的时候的to
值应该是多少。
如何对auto进行动画
react-string 不能对auto
进行动画,不过别灰心,我们可以迂回一下,比如借助 react-use-measure 来获取元素的宽高,然后对其进行动画。
const Auto = () => {
const [flip, set] = useState(false);
const [ref, bounds] = useMeasure();
const { height } = useSpring({
from: { height: 0 },
height: bounds.height,
reset: true,
reverse: flip,
delay: 200,
config: config.molasses,
onRest: () => set(!flip),
});
return (
<animated.div
className="bg-lime-500 "
style={{ overflow: "hidden", height }}
>
<div ref={ref} className="p-4" style={{ overflow: "hidden" }}>
豇豆角搜集
<br />
掉绝对是
<br />
d的简欧is解耦司机端
<br />
d的简欧is解耦司机端222
</div>
</animated.div>
);
};
注意ref的位置必须在animated.div
的下一个层级,如果放在animated.div
上,height
是一直变化的,这就没法玩了。
交互与动画
如何将交互和动画结合起来呢?react-with-gesture应该是最好的选择了。
import { useGesture } from "react-with-gesture";
const Gesture = (props) => {
const [{ xy, scale }, set] = useSpring(() => ({ xy: [0, 0], scale: 1 }));
const bind = useGesture(({ down, delta }) => {
set({
xy: down ? delta : [0, 0],
scale: down ? 2 : 1,
});
});
return (
<animated.div
{...bind()}
style={{
transform: to(
[xy, scale],
(xy, s) => `translate3d(${xy[0]}px, ${xy[1]}px, 0) scale(${s})`
),
}}
className="bg-blue-500 w-10 h-10"
></animated.div>
);
};
注意to
的用法,是直接将scale
和xy
的值传入,而不是用scale.to(...)
和xy.to(...)
,因为当transform有多个值的时候采用后者是做不到的。