React Motion的使用

1,917 阅读2分钟

对于 95% 的动画组件使用案例,我们没有必要用硬编码(把配置写死)式的缓冲曲线和时间过渡来重排序。只需要给你的 UI 设置一个刚度系数和阻尼系数,接下来让神奇的物理原理处理即可。用这种方式,根本无需担心如中断动画等小问题。它也极大的简化了 API 。

使用

  1. 安装
// tsx需使用@types/react-motion
npm i -s react-motion or
yarn add -s react-motion
  1. 引入
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:定义组件挂载时的style
  • willLeave:定义组件卸载时的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;
}