对于 95% 的动画组件使用案例,我们没有必要用硬编码(把配置写死)式的缓冲曲线和时间过渡来重排序。只需要给你的 UI 设置一个刚度系数和阻尼系数,接下来让神奇的物理原理处理即可。用这种方式,根本无需担心如中断动画等小问题。它也极大的简化了 API 。
使用
- 安装
// tsx需使用@types/react-motion
npm i -s react-motion or
yarn add -s react-motion
- 引入
import { Motion, spring, StaggeredMotion, TransitionMotion, presets } from "react-motion";
API
spring
(弹簧)Motion
StaggeredMotion
TransitionMotion
presets
spring: (val: number, config?: SpringHelperConfig) => OpaqueConfig
val
:终点值,你希望达到的最终效果的数值,number类型config
: 属性配置,可选项:stiffness
: 刚度, 默认值170
.值越大,弹簧回弹速度越快damping
: 阻尼, 默认值26
.可理解为减震性,值越大,弹簧回弹次数越少precision
: 精度, 默认值0.01
. 例如spring(10, {stiffness: 120, damping: 17}),代表弹性值为10,刚度为120,阻尼为17。
Motion 方块移动组件:适合编写单个组件的形变动画
<Motion defaultStyle={{x: 0}} style={{x: spring(10)}}>
{interpolatingStyle => <div style={interpolatingStyle} />}
</Motion>
属性
- style:Object,动画样式
- defaultStyle:Object,初始样式,会根据动画效果马上变为style规定的样式
- children:子元素
- onRest:动画结束时触发回调
下面实现了一个方块平移的效果
import * as React from "react";
import style from "./style.module.css";
import { Button } from "antd";
import { Motion, spring, presets } from "react-motion";
export default function MotionDemo() {
const [left, setLeft] = React.useState(0);
const handleClick = () => {
if (left === 0) {
console.log(left)
setLeft(200);
} else {
console.log(left)
setLeft(0);
}
};
return (
<>
<Motion defaultStyle={{x:0}} style={{x: spring(left, presets.wobbly)}}>
{interpolatingStyle => {
// debugger
return (
<div style={{transform: `translateX(${interpolatingStyle.x}px)`}} className= {style.box}></div>
)
}}
</Motion>
<Button size="large" onClick={handleClick}>
Run
</Button>
</>
);
}
style.module.css
.box{
width: 300px;
height: 300px;
background-color: red;
}
StaggeredMotion 跟随鼠标实例:用于编写一串有相互关联关系的实体的动画
属性:
styles
:Array,动画样式defaultStyles
:Array,初始样式children
:子元素
案例:
const [height, setHeight] = React.useState(100);
let boxes = [{ h: 0 }, { h: 0 }, { h: 0 }, { h: 0 }, { h: 0 }];
const handleClick2 = () => {
if (height === 0) {
setHeight(100);
} else {
setHeight(0);
}
};
return (
<div style={{height:'150px'}}>
<StaggeredMotion
defaultStyles={boxes}
// 返回样式数组
styles={(prevInterpolatedStyles) =>
prevInterpolatedStyles.map((style, i) => {
return i === 0
? { h: spring(height) } //此处根据height来渲染高度
: prevInterpolatedStyles[i - 1];
})
}
>
{(interpolatingStyles) => (
<div>
{interpolatingStyles.map((style, i) => (
<div
key={i}
style={{
border: "1px solid",
display:'inline-block',
height: style.h,
width: style.h,
margin: "20px",
backgroundColor: "blue",
}}
/>
))}
</div>
)}
</StaggeredMotion>
</div>
)
TransitionMotion 用来编写组件mount和unmount的动画
属性:
styles
:Array,动画样式defaultStyles
:Array,初始样式children
:子元素willEnter
:定义组件挂载时的stylewillLeave
:定义组件卸载时的style
案例:
const handleClick3 = () => {
if (show === true) {
setShow(false);
} else {
setShow(true);
}
};
function willEnter() {
return { scale: 0 };
}
function willLeave() {
return { scale: spring(0) };
}
return (
<div>
<TransitionMotion
styles={
show
? [
{
key: "test",
style: { scale: spring(1) },
},
]
: []
}
willEnter={willEnter}
willLeave={willLeave}
>
{(inStyles) =>
inStyles[0] ? (
<div
className={style.box2}
key={inStyles[0].key}
style={{
transform:`scale(${inStyles[0].style.scale},${inStyles[0].style.scale})`,
}}
></div>
) : null
}
</TransitionMotion>
<Button onClick={handleClick3}>Run3</Button>
</div>
)
完整文件
import * as React from "react";
import style from "./style.module.css";
import { Button } from "antd";
import {
Motion,
spring,
StaggeredMotion,
TransitionMotion,
presets,
} from "react-motion";
export default function MotionDemo() {
// 平移效果 相对左侧的距离
const [left, setLeft] = React.useState(20);
// 高度宽度值
const [height, setHeight] = React.useState(100);
// 区分组件挂载与卸载
const [show, setShow] = React.useState(true);
let boxes = [{ h: 0 }, { h: 0 }, { h: 0 }, { h: 0 }, { h: 0 }];
const handleClick = () => {
if (left === 0) {
console.log(left);
setLeft(200);
} else {
console.log(left);
setLeft(0);
}
};
const handleClick2 = () => {
if (height === 0) {
setHeight(100);
} else {
setHeight(0);
}
};
const handleClick3 = () => {
if (show === true) {
setShow(false);
} else {
setShow(true);
}
};
function willEnter() {
return { scale: 0 };
}
function willLeave() {
return { scale: spring(0) };
}
return (
<>
{/* Motion组件 */}
<Motion
defaultStyle={{ x: 20 }}
style={{ x: spring(left, presets.wobbly) }}
>
{(interpolatingStyle) => {
// debugger
return (
<div
style={{ transform: `translateX(${interpolatingStyle.x}px)` }}
className={style.box}
></div>
);
}}
</Motion>
<Button size="large" onClick={handleClick}>
Run
</Button>
{/* StaggeredMotion组件 */}
<div style={{ height: "150px" }}>
<StaggeredMotion
defaultStyles={boxes}
// 返回样式数组
styles={(prevInterpolatedStyles) =>
prevInterpolatedStyles.map((style, i) => {
return i === 0
? { h: spring(height) } //此处根据height来渲染高度
: prevInterpolatedStyles[i - 1];
})
}
>
{(interpolatingStyles) => (
<div>
{interpolatingStyles.map((style, i) => (
<div
key={i}
style={{
border: "1px solid",
display: "inline-block",
height: style.h,
width: style.h,
margin: "20px",
backgroundColor: "blue",
}}
/>
))}
</div>
)}
</StaggeredMotion>
</div>
<Button onClick={handleClick2}>Run2</Button>
{/* TransitionMotion组件 */}
<div>
<TransitionMotion
styles={
show
? [
{
key: "test",
style: { scale: spring(1) },
},
]
: []
}
willEnter={willEnter}
willLeave={willLeave}
>
{(inStyles) =>
inStyles[0] ? (
<div
className={style.box2}
key={inStyles[0].key}
style={{
transform: `scale(${inStyles[0].style.scale},${inStyles[0].style.scale})`,
}}
></div>
) : null
}
</TransitionMotion>
<Button onClick={handleClick3}>Run3</Button>
</div>
</>
);
}
style.module.css
.box{
width: 300px;
height: 300px;
background-color: red;
}
.box2{
float: left;
margin: 20px;
width: 100px;
height: 100px;
background-color: blue;
}