查看原文
其他

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

神说要有光zxg 神光的编程秘籍
2024-08-22

列表是很常见的场景:

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

怎么做呢?

一般我们会用 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 {
  width300px;
  height50px;
  background: lightblue; 
  margin200px 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 {
  border5px 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 {
  width300px;
  height50px;
  background: lightblue; 
  margin200px auto;
}

.enter {
  transformtranslateX(-100%);
  opacity0;
}

.enter-active {
  transformtranslateX(0);
  opacity1;

  transition: all 1s ease;
}

.enter-done {
  border5px 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 {
  width300px;
  height50px;
  background: lightblue; 
  margin100px auto;
}

button {
  margin0 auto;
  display: block;
}

.enter {
  transformtranslateX(-100%);
  opacity0;
}

.enter-active {
  transformtranslateX(0);
  opacity1;

  transition: all 1s ease;
}

.enter-done {
  border5px solid #000;
}

.exit {
  transformtranslateX(0%);
  opacity1;
}

.exit-active {
  transformtranslateX(100%);
  opacity0;

  transition: all 1s ease;
}

.exit-done {
}

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

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

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

这就需要设置 appear 的 props 了:

.appear {
  transformscale(0);
}

.appear-active {
  transformscale(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([
    { id1text"guang" },
    { id2text"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 {
  width300px;
  margin20px auto;
}

.item {
  margin4px 0;
  padding10px 0;
  border-radius4px;
  background: lightblue;
}

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

.enter {
  opacity0;
  transformtranslateX(-100%);
  background: lightblue;
}
.enter-active {
  opacity1;
  transformtranslateX(0%);
  background: lightblue;
  transition: all 1s ease;

}
.enter-done {
}
.exit {
  opacity1;
  transformtranslateX(0%);
  background: red;
}
.exit-active {
  opacity0;
  transformtranslateX(100%);
  background: red;
  transition: all 1s ease;
}

.btn {
  color#fff;
  background-color#0069d9;
  border-color#0062cc;
  padding10px 20px;
  border-radius4px;
  width: fit-content;
  cursor: pointer;
  margin20px 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 还是很好用的。

继续滑动看下一个
神光的编程秘籍
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存