组件进入离开动画?试试 react-transition-group

列表是很常见的场景:

f0e89c0794e0006bb343df42744fae02.gif

如果我们想给它加上进入离开的动画效果:

f5cf9e7af39cc463f5e90baf45b85eb3.gif

怎么做呢?

一般我们会用 react-transition-group 来做。

在 npm 官网可以看到,这个包每周有 750w 下载量,还是非常流行的:

05cf7a77f93ecfd3140b9a70dd1d2e0e.png

那这个包怎么用呢?

我们写下代码试一下:

npx create-react-app transition-group-test
61f7bf3955065b42169d554f536cfa75.png

用 create-react-app 创建个项目。

把它跑起来:

npm run start
612d092625b40ca292ac0205a743f137.png

安装 react-transition-group:

npm install --save react-transition-group

先不着急用,先想想如果有一个 div,如何给它加上进入离开的动画效果呢?

import './App.css';

function App() {

  return (
    <div id="box">
    </div>
  );
}

export default App;
#box {
  width: 300px;
  height: 50px;
  background: lightblue; 
  margin: 200px auto;
}

这样一个 div:

f9fedd38d6bf19611ab5e930780f3ea6.png

改变 translate 就可以让它动起来:

import { useEffect, useState } from 'react';
import './App.css';
 
function App() {
  const [style, setStyle] = useState({
    transform: 'translateX(-100%)'
  }, []);

  useEffect(() => {
    setTimeout(() => {
      setStyle({ transform: 'translateX(0%)' })
    }, 1000);
  })

  return (
    <div id="box" style={style}>
    </div>
  );
}

export default App;
d71f7cab4dc6fb40ed9f6f0435a3e323.gif

加上 transition,让它慢慢的动:

22eb90e2b211fcf11d43e30e7847a201.png c081bd51a77a03bbf4e6ae2581190910.gif

然后再加个透明度变化:

9e55de7805a5125fd21865546f39bb13.png d3a10cfb3562205d38c8fda7a5b9453b.gif

这样,进入的效果就完成了。

也就是说,设置 transtion 之后,只要改变 transform 和 opacity 就行了。

当然,我么也可以把这俩 style 封装到 className 里,然后增删 className 就行。

也就是这样:

9369894380fc606de31259090b11650d.png
import { useEffect, useState } from 'react';
import './App.css';
 
function App() {
  const [className, setClassname] = useState('');

  useEffect(() => {
    setClassname('enter');

    setTimeout(() => {
      setClassname('enter-active');
    });
  }, []);

  return (
    <div id="box" className={className}>
    </div>
  );
}

export default App;

效果和之前一样:

45cb24072639f64bf16019285d44daf0.gif

可以看到,className 变了,导致样式改变,触发 transition 动画:

71d5f96d74d0fd9e10a8e2933cc51d9e.gif

我们还可以在 transition 结束的时候,再添加一个 className:

4c379ed173d5bc751c9e1fa037c312da.png
.enter-done {
  border: 5px solid #000;
}

02051722f4348eb183d6b2d60659577c.gif

可以看到,最开始 className 是 enter,后来切换到 enter-active,触发了 transition 动画,最后动画结束切换到了 enter-done。

react-transition-group 也是通过改变 className 来给组件加上的过渡效果。

我们试一下:

import { useEffect, useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import './App.css';
 
function App() {
  const [flag, setFlag] = useState(false);

  useEffect(() => {
    setTimeout(() => {
      setFlag(true);
    }, 3000);
  }, []);

  return <CSSTransition
    in={flag}
    timeout={1000}
  >
    <div id="box"></div>
  </CSSTransition>
}

export default App;
#box {
  width: 300px;
  height: 50px;
  background: lightblue; 
  margin: 200px auto;
}

.enter {
  transform: translateX(-100%);
  opacity: 0;
}

.enter-active {
  transform: translateX(0);
  opacity: 1;

  transition: all 1s ease;
}

.enter-done {
  border: 5px solid #000;
}

样式部分没有变化,还是 enter、enter-active、enter-done 这 3 个。

现在就不用自己改 className了,用 CSSTransition 组件,它会自己给加这些 className。

参数 in 设置为 true 就是触发进入的动画,设置为 false 就是触发离开的动画。

42c7b5b3d8a0cd725e953e5ed95e7417.gif

看起来 enter 和 enter-active 好像是同时设置的,其实不是。

CSSTransition 组件会先设置 enter,再设置 enter-active,这样就触发动画了。

然后到了 timeout 参数的时间,就会设置 enter-done 的 className。

反之,如果 in 的参数改为 false,就会触发离开动画:

f4497109465af1bd3d80c1d2c40a1167.gif

className 会先设置 exit,再设置 exit-active 来触发动画,到了 timeout 的时间会设置为 exit-done。

连起来,就可以实现 enter 和 exit 的动画:

import { useEffect, useState } from 'react';
import { CSSTransition } from 'react-transition-group';
import './App.css';
 
function App() {
  const [flag, setFlag] = useState(false);

  return <div>
    <CSSTransition
      in={flag}
      timeout={1000}
    >
      <div id="box"></div>
    </CSSTransition>
    <button onClick={() => setFlag(!flag)}>{!flag ?  '进入' : '离开'}</button>
  </div>
}

export default App;
#box {
  width: 300px;
  height: 50px;
  background: lightblue; 
  margin: 100px auto;
}

button {
  margin: 0 auto;
  display: block;
}

.enter {
  transform: translateX(-100%);
  opacity: 0;
}

.enter-active {
  transform: translateX(0);
  opacity: 1;

  transition: all 1s ease;
}

.enter-done {
  border: 5px solid #000;
}

.exit {
  transform: translateX(0%);
  opacity: 1;
}

.exit-active {
  transform: translateX(100%);
  opacity: 0;

  transition: all 1s ease;
}

.exit-done {
}
a7061450094e9e8249e31d07f3dabfcf.gif

可以看到,通过 className 从 enter 到 enter-active 到 enter-done 的变化,以及从 exit 到 exit-active 到 exit-done 的变化,就实现了进入和离开的动画。

不知道大家有没有发现,最开始出现的时候是没有动画的,之后后来切换 in 的 props 的时候,才有动画。

如果想最开始出现的时候就做一次动画呢?

这就需要设置 appear 的 props 了:

54442435f0ac5464237b2baf1b4c927e.png
.appear {
  transform: scale(0);
}

.appear-active {
  transform: scale(1);
  transition: all 1s ease;
}

.appear-done {
  
}
2ba82e475a85b6867e8e063afd3e34b5.gif

可以看到,最开始还有一个 appear、appear-active、appear-done 的 className 变化,并且还会添加 enter-done。

这个只会在刚出现的时候设置一次。

也就是一共可以有 appear、enter、exit 3 种动画。

现在是我们自己设置 in 的 props 来触发进入和离开动画的,如果是列表的多个 child,都想加动画呢?

这时候就用 TransitionGrop 组件。

import React, { useState } from "react";
import { CSSTransition, TransitionGroup } from "react-transition-group";
import "./App.css";

export default function App() {
  const [items, setItems] = useState([
    { id: 1, text: "guang" },
    { id: 2, text: "guang" },
  ]);

  return (
    <div>
      <TransitionGroup className="item-box">
        {items.map(({ id, text }) => (
          <CSSTransition key={id} timeout={1000}>
            <div className="item">
              <span
                className="del-btn"
                onClick={() => {
                  setItems(items.filter((item) => item.id !== id));
                }}
              >
                x
              </span>
              {text}
            </div>
          </CSSTransition>
        ))}
      </TransitionGroup>

      <div
        className="btn"
        onClick={() => {
          setItems([...items, { id: Date.now(), text:  'guang' }]);
        }}
      >
        Add
      </div>
    </div>
  );
}

就是用 TransitionGroup 包裹下,

.item-box {
  width: 300px;
  margin: 20px auto;
}

.item {
  margin: 4px 0;
  padding: 10px 0;
  border-radius: 4px;
  background: lightblue;
}

.del-btn {
  padding: 0 10px;
  cursor: pointer;
  user-select: none;
}

.enter {
  opacity: 0;
  transform: translateX(-100%);
  background: lightblue;
}
.enter-active {
  opacity: 1;
  transform: translateX(0%);
  background: lightblue;
  transition: all 1s ease;

}
.enter-done {
}
.exit {
  opacity: 1;
  transform: translateX(0%);
  background: red;
}
.exit-active {
  opacity: 0;
  transform: translateX(100%);
  background: red;
  transition: all 1s ease;
}

.btn {
  color: #fff;
  background-color: #0069d9;
  border-color: #0062cc;
  padding: 10px 20px;
  border-radius: 4px;
  width: fit-content;
  cursor: pointer;
  margin: 20px auto;
}

效果就是文章开头看到的那个:

64d1cf033f8ac92111fbe92fa83421c2.gif

用 CSSTransition 的时候,我们需要自己设置 in 的 props 来触发进入和离开动画。

而现在 TransitionGroup 会在 children 变化的时候对比新旧 item,来自动设置 in,触发动画。

这就是 react-transition-group 的常用功能。

此外,它还有两个组件,Transition 和 SwitchTransition:

把 CSSTransition 换成 Transition,然后打印下 status:

35f1eab9e870caf6da7fe61f3bca65f6.png

可以看到,status 最开始是从 entering 到 entered,从 exiting 到 exited 变化,但是不会设置 className:

1660d8c3a73eeacc741ced535740987f.gif

我们可以根据 status 的变化自己设置 className。

其实,CSSTransition 就是基于 Transition 封装的。

一般我们用 CSSTransition 就好了。

再就是 SwithTransition,先看下效果:

621391735b5f520248098e3253a3cd48.png

包裹一层 SwitchTransition,然后设置下 key。

当 mode 为 in-out 时:

45f0debfb6eec1da81b833ec2526855d.gif

当 mode 为 out-in 时:

2085b4b33486b39d945eb6249daf775c.gif

这个组件就是用来控制两个组件切换时的进入、离开动画的顺序的。

这样,react-transition-group 的 4 个组件: Transition、CSSTransition、TransitionGroup、SwitchTransition 我们就都过了一遍。

那它是怎么实现的呢?

react-transition-group 还是用 class 的方式写的,我们简单看一下就行:

首先,Transition 组件会设置一个 status 的状态,根据 in 和 appear 参数来决定初始值。

98d796e32d4646320a61f5b8dcb935fc.png

然后 in 参数变化的时候,会修改 status:

96d1a43e9dccad10806807d88732e947.png

比如 enter 的时候,会先修改 status 为 entering,然后触发 onEntering 回调,之后修改 status 为 entered,然后触发 onEntered 回调:

2d09cad08bbeae676bfc7399c4605a53.png

CSSTransition 就是在这些回调里设置 className 的:

01f9f0ed2875008eac532d85b325fea6.png

其实也很简单,就是 status 变化,触发回调,回调函数里修改 className。

而 TransitionGroup 则是会在 children 变化的时候,对比下新旧 children,决定如何设置 in 的 props,也就是触发进入离开动画:

6a14cf7761dd0506dc2949880db962e1.png

还有 SwitchTransition 组件,这个就是根据 mode 的设置,确定是先触发旧 children 的 exit,然后在 onExited 回调里触发新 children 的 enter,还是反过来:

11c03d0aa4d5dbc7da0c2cccc5348a38.png

综上,Transition 组件是核心,而 CSSTransition、SwitchTransition、TransitionGroup 都是在它基础上封装的。

这就是 react-transition-group 的实现原理。

总结

当我们想给一个列表的列表项加上进入离开的动画的时候,可以用 react-trasition-group 这个包。

它是 react 官方出的,非常流行的一个包。

常用来做这种效果:

e16b0c9add2ae7edff29ea58714376fe.gif

它有 Transition、CSSTransition、TransitionGroup、SwitchTransition 这 4 个组件。

Transition 会通过 in 和 appear 的 props 来设置 status,并且在 props 变化的时候修改 status,触发不同的回调。

CSSTransition 就是在这些回调里实现了 className 的修改:

  • 进入的时候会触发 enter、enter-active、enter-done 的 className 切换

  • 离开的时候是 exit、exit-active、exit-done 的切换

  • 如果设置了 appear 参数,刚出现的时候,还会有 appear、appear-active、appear-done 的切换。

不过如果是列表,那不用自己设置 in 的 props,直接用 TransitionGroup 包一层就行。

SwitchTransition 则是用来控制两个组件切换的时候,先进入再离开,还是先离开再进入。

它们的实现原理并不复杂,就是 in 的 props 触发 status 变化,而 status 变化会触发回调函数,在回调里实现 className 的修改。

当你需要进入、离开的过渡动画的时候,react-transition-group 还是很好用的。

- END -

关于奇舞团

奇舞团是 360 集团最大的大前端团队,代表集团参与 W3C 和 ECMA 会员(TC39)工作。奇舞团非常重视人才培养,有工程师、讲师、翻译官、业务接口人、团队 Leader 等多种发展方向供员工选择,并辅以提供相应的技术力、专业力、通用力、领导力等培训课程。奇舞团以开放和求贤的心态欢迎各种优秀人才关注和加入奇舞团。

2bf02ad6ffa8e70eda995d33b42a5428.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值