组件进入离开动画?试试 react-transition-group
列表是很常见的场景:
如果我们想给它加上进入离开的动画效果:
怎么做呢?
一般我们会用 react-transition-group 来做。
在 npm 官网可以看到,这个包每周有 750w 下载量,还是非常流行的:
那这个包怎么用呢?
我们写下代码试一下:
npx create-react-app transition-group-test
用 create-react-app 创建个项目。
把它跑起来:
npm run start
安装 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:
改变 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;
加上 transition,让它慢慢的动:
然后再加个透明度变化:
这样,进入的效果就完成了。
也就是说,设置 transtion 之后,只要改变 transform 和 opacity 就行了。
当然,我么也可以把这俩 style 封装到 className 里,然后增删 className 就行。
也就是这样:
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;
效果和之前一样:
可以看到,className 变了,导致样式改变,触发 transition 动画:
我们还可以在 transition 结束的时候,再添加一个 className:
.enter-done {
border: 5px solid #000;
}
可以看到,最开始 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 就是触发离开的动画。
看起来 enter 和 enter-active 好像是同时设置的,其实不是。
CSSTransition 组件会先设置 enter,再设置 enter-active,这样就触发动画了。
然后到了 timeout 参数的时间,就会设置 enter-done 的 className。
反之,如果 in 的参数改为 false,就会触发离开动画:
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 {
}
可以看到,通过 className 从 enter 到 enter-active 到 enter-done 的变化,以及从 exit 到 exit-active 到 exit-done 的变化,就实现了进入和离开的动画。
不知道大家有没有发现,最开始出现的时候是没有动画的,之后后来切换 in 的 props 的时候,才有动画。
如果想最开始出现的时候就做一次动画呢?
这就需要设置 appear 的 props 了:
.appear {
transform: scale(0);
}
.appear-active {
transform: scale(1);
transition: all 1s ease;
}
.appear-done {
}
可以看到,最开始还有一个 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;
}
效果就是文章开头看到的那个:
用 CSSTransition 的时候,我们需要自己设置 in 的 props 来触发进入和离开动画。
而现在 TransitionGroup 会在 children 变化的时候对比新旧 item,来自动设置 in,触发动画。
这就是 react-transition-group 的常用功能。
此外,它还有两个组件,Transition 和 SwitchTransition:
把 CSSTransition 换成 Transition,然后打印下 status:
可以看到,status 最开始是从 entering 到 entered,从 exiting 到 exited 变化,但是不会设置 className:
我们可以根据 status 的变化自己设置 className。
其实,CSSTransition 就是基于 Transition 封装的。
一般我们用 CSSTransition 就好了。
再就是 SwithTransition,先看下效果:
包裹一层 SwitchTransition,然后设置下 key。
当 mode 为 in-out 时:
当 mode 为 out-in 时:
这个组件就是用来控制两个组件切换时的进入、离开动画的顺序的。
这样,react-transition-group 的 4 个组件: Transition、CSSTransition、TransitionGroup、SwitchTransition 我们就都过了一遍。
那它是怎么实现的呢?
react-transition-group 还是用 class 的方式写的,我们简单看一下就行:
首先,Transition 组件会设置一个 status 的状态,根据 in 和 appear 参数来决定初始值。
然后 in 参数变化的时候,会修改 status:
比如 enter 的时候,会先修改 status 为 entering,然后触发 onEntering 回调,之后修改 status 为 entered,然后触发 onEntered 回调:
CSSTransition 就是在这些回调里设置 className 的:
其实也很简单,就是 status 变化,触发回调,回调函数里修改 className。
而 TransitionGroup 则是会在 children 变化的时候,对比下新旧 children,决定如何设置 in 的 props,也就是触发进入离开动画:
还有 SwitchTransition 组件,这个就是根据 mode 的设置,确定是先触发旧 children 的 exit,然后在 onExited 回调里触发新 children 的 enter,还是反过来:
综上,Transition 组件是核心,而 CSSTransition、SwitchTransition、TransitionGroup 都是在它基础上封装的。
这就是 react-transition-group 的实现原理。
总结
当我们想给一个列表的列表项加上进入离开的动画的时候,可以用 react-trasition-group 这个包。
它是 react 官方出的,非常流行的一个包。
常用来做这种效果:
它有 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 还是很好用的。