使用react-transition-group制作常用的动画过渡组件

2,873 阅读5分钟

前言:最近在开发中比较注重动画过渡效果了,于是在react的官方仓库中,发现了react-transition-group这个库,在看了一天的文档后,嗯。。还蛮对我胃口的。

官方仓库地址:github.com/reactjs/rea…

官方文档地址:reactcommunity.org/react-trans…

1.  CSSTransition 组件

此Transition组件用于CSS动画过渡,灵感来源于ng-animate库。它将在组件 appear(首次加载), enter(进入阶段), exit(退出阶段) 的时候,给组件添加上对应的类名。

注意:如果你不需要在组件第一次渲染的时候,执行相应的过渡效果的话,请先忘记appear这个状态,不然会搞得你晕头撞向的。

先来看一个超级简单的代码:

function App() {
  const [inProp, setInProp] = useState(false);
  return (
    <div>
      <CSSTransition in={inProp} timeout={200} classNames="my-node">
        <div>
          {"I'll receive my-node-* classes"}
        </div>
      </CSSTransition>
      <button type="button" onClick={() => setInProp(true)}>
        Click to Enter
      </button>
    </div>
  );
}

在上面的代码中,组件被传入了三个参数,它们的意义如下:

in:  参数传布尔值,当in为true时,组件的状态将切换成enter,当in为false时,组件的状态将切换成exit。

timeout: 动画的持续时长(单位:ms)。

classNames: css类名前缀。

在上面的例子中,当in参数为true时,子组件第一时间会添加一个类名 my-node-enter, 然后在下一帧将会把my-node-enter-active 添加到子组件的类名中。在动画结束时,将会把类名切成my-node-enter-done(这里的my-node,就是在classNames中设置的值)。当in参数为false时,子组件的类名的变化过程为:my-node-exit -> my-node-exit-active -> my-node-exit-done。在变化的过程中,无论的enter还是exit状态,它们的动画持续时长将都是200ms,由timeout进行设置。

*-active 类名一般用于设置我们想要的动画效果,于是乎,针对上面的例子,我们可以写下这样的css样式。

.my-node-enter {
  opacity: 0;
}
.my-node-enter-active {
  opacity: 1;
  transition: opacity 200ms;
}
.my-node-exit {
  opacity: 1;
}
.my-node-exit-active {
  opacity: 0;
  transition: opacity 200ms;
}

经常写动画的朋友就能一下看出来了,这其实就是个淡入淡出的动画效果~

但是,上面的代码在第一次加载的时候,就已经把子组件给渲染出来了。有时候我们在做一些动画的时候,不想让组件先渲染出来,只想让他在in参数为true的时候,再进行mount,那该怎么办呢。这个时候,我们就需要加上一个参数,叫做 mountOnEnter

mountOnEnter参数:默认情况子组件与transtion组件一起加载(也就是说即使in属性为false,组件也会先以隐藏状态(exited)正常加载),当mountOnEnter 为true时,会在第一次in属性为true时加载子组件。

那么,我们如果需要在exit动画执行结束后(in为false),去卸载对应的DOM,该怎么做呢。这个时候,我们可以引入另外一个参数,叫做unmountOnExit

然后,我们改写后的组件将长这样:

function App() {
  const [inProp, setInProp] = useState(false);
  return (
    <div>
      <CSSTransition in={inProp} timeout={200} classNames="my-node" mountOnEnter unmountOnExit>
        <div>
          {"I'll receive my-node-* classes"}
        </div>
      </CSSTransition>
      <button type="button" onClick={() => setInProp(true)}>
        Click to Enter
      </button>
    </div>
  );
}

一个简简单单的过渡效果,就这样做好了。

2.  封装基础的过渡组件

好巧不巧,在用这个库的时候,我就开始思考,该如何给团队封装常用的过渡组件。然后我在mui的文档中,看到了transition组件,好家伙,这熟悉的API,不就是基于transition group进行封装的吗?so nice!(我看到了就是我的了)

我们先构建目录结构

fade 和 slide 文件夹是我们需要实现的组件目录。style文件夹用于放置公共的样式/样式变量,demo文件夹用于做用法的举例。然后我们将所有的实现,通过index.tsx进行导出即可。

1. 实现fade(淡入淡出)组件

在fade->index.tsx中,写下如下的代码:

上面的代码十分好理解,我们为子组件创建了一个时长为300ms的动画,并制定动画的类名前缀为fade,in参数由传参进来的visible变量所控制。接下来,在fade->index.less中,我们加上对应的fade类名,代码如下:

我们为 fade-enter-activefade-exit-active写上了对应的样式。这样的话,在in参数变为true/false时,将会执行对应的动画。在代码中,你会发现我运用了 @animation-duration-base@animation-timing-function-enter@animation-timing-function-leave 这样的less变量,它们是在style->common.less中声明的,文件内容如下:

将动画相关的参数抽取成变量,更有利于我们做后续的拓展与维护,抽取的方式参考自 react-vant。 接下来,我们只要在代码中,愉快的使用它就行了。

2. 实现slide滑动组件

常见的slide将支持上下左右四个方向的滑入效果,因此,我们需要设计额外的direction参数来控制它是由哪个方向进行滑入的。在 slide->index.tsx中,我们写下以下的代码:

接着,我们只需要分别去实现slide-up/down/left/right-enter/exit-active的css样式就行了,于是在slide -> index.less中,写下以下的样式:

接下来,我们在demo->index.tsx中,试试刚加上去的组件Slide

至此,我们就先来看看fade和slide的动画效果吧。

3.  增加额外的props参数

在上面简单地完成对应的效果后,我们发现,组件还是存在一些问题的:

1. timeout可以由用户进行决定,而不是写死300ms。

2. 没有相关事件的回调。

于是,我们对代码进行简单的修改后,如下:

由于这些动画比较简单,所以callback函数,我也只提供了onEntered和onExisted两个。复杂的动画在封装成组件时,应该考虑具体的应用场景。

至此,我们一个基本的transition库就完成了,代码已经上传到 codesandbox.io/s/admiring-… 中了,感兴趣的小伙伴可以点进去直接预览对应的效果。