React 过渡动画

react.jpg

前言

在开发中, 我们想要给一个组件的 show/hidden 添加某种过渡动画, 可以很好的增加用户体验

当然, 我们可以通过原生的 CSS 来实现这些过渡动画, 但是 React 社区为我们提供了react-transition-group用来完成过渡动画

react-transition-group 介绍

React 曾为开发者提供过动画插件react-addons-css-transition-group, 后由社区维护, 形成了现在的react-transition-group

这个库可以帮助我们方便的实现组件的入场离场动画, 使用时需要进行额外的安装

1
2
3
4
5
# npm
npm install react-transition-group --save

# yarn
yarn add react-transition-group

react-transition-group本身非常小, 不会为我们应用程序增加过多的负担

react-transition-group主要包含四个组件:

  • Transition
    • 该组件是一个和平台无关的组件(不一定要结合 CSS)
    • 在前端开发中, 我们一般是结合 CSS 来完成样式, 所以比较常用的是CSSTransition
  • CSSTransition
    • 在前端开发中, 通常使用CSSTransition来完成过渡动画效果
  • SwitchTransition
    • 两个组件显示和隐藏切换时, 使用该组件
  • TransitionGroup
    • 将多个动画组件包裹在其中, 一般用于列表中元素的动画

react-transition-group 使用

CSSTransition

CSSTransition是基于Transition组件构建的

  • CSSTransition执行过程中, 有三个状态: appearenterexit
  • 它们有三种状态, 需要定义对应的 CSS 样式
    • 第一类: 开始状态, 对应的类是-appear-enterexit
    • 第二类: 执行动画, 对应的类是-appear-active-enter-active-exit-active
    • 第三类: 执行结束, 对应的类是-appear-done-enter-done-exit-done

CSSTransition常见对应的属性

  • in: 触发进入退出状态
    • 如果添加了unmountOnExit={true}, 那么该组件会在执行退出动画结束后被移除掉
    • intrue时, 触发进入状态, 会添加-enter-enter-active的 class 开始执行动画, 当动画执行结束后, 会移除两个 class, 并且添加-enter-done的 class
    • infalse时, 触发退出状态, 会添加-exit-exit-active的 class 开始执行动画, 当动画执行结束后, 会移除两个 class, 并且添加-enter-done 的 class
  • classNames: 动画 class 的名称
    • 决定了在编写 CSS 时, 对应的 class 名称: 比如card-entercard-enter-activecard-enter-done
  • timeout: 过渡动画的时间
  • appear: 是否在初次进入时添加动画(需要和in同时为true)
  • 更多属性

CSSTransition对应的钩子函数: 主要为了检测动画的执行过程, 来完成一些 JavaScript 的操作

  • onEnter: 在进入动画之前被触发
  • onEntering: 在应用进入动画时被触发
  • onEntered: 在应用进入动画结束后被触发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import './App.css'

import { CSSTransition } from 'react-transition-group'

import { Card, Avatar, Button } from 'antd'
import { EditOutlined, EllipsisOutlined, SettingOutlined } from '@ant-design/icons'

const { Meta } = Card

export default class App extends PureComponent {
constructor(props) {
super(props)

this.state = {
isShowCard: true
}
}

render() {
return (
<div>
<Button type="primary" onClick={e => this.setState({ isShowCard: !this.state.isShowCard })}>
显示/隐藏
</Button>
<CSSTransition
in={this.state.isShowCard}
classNames="card"
timeout={1000}
unmountOnExit={true}
onEnter={el => console.log('进入动画前')}
onEntering={el => console.log('进入动画')}
onEntered={el => console.log('进入动画后')}
onExit={el => console.log('退出动画前')}
onExiting={el => console.log('退出动画')}
onExited={el => console.log('退出动画后')}
>
<Card
style={{ width: 300 }}
cover={<img alt="example" src="https://gw.alipayobjects.com/zos/rmsportal/JiqGstEfoWAOHiTxclqi.png" />}
actions={[
<SettingOutlined key="setting" />,
<EditOutlined key="edit" />,
<EllipsisOutlined key="ellipsis" />
]}
>
<Meta
avatar={<Avatar src="https://zos.alipayobjects.com/rmsportal/ODTLcjxAfvqbxHnVXCYX.png" />}
title="Card title"
description="This is the description"
/>
</Card>
</CSSTransition>
</div>
)
}
}

对应的 CSS 样式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.card-enter, .card-appear {
opacity: 0;
transform: scale(.8);
}

.card-enter-active, .card-appear-active {
opacity: 1;
transform: scale(1);
transition: opacity 300ms, transform 300ms;
}

.card-exit {
opacity: 1;
}

.card-exit-active {
opacity: 0;
transform: scale(.8);
transition: opacity 300ms, transform 300ms;
}

SwitchTransition

SwitchTransition可以完成两个组件之间切换的炫酷动画

  • 比如我们有一个按钮需要在onoff之间切换, 我们希望看到on先从左侧退出, off再从右侧进入
  • 这个动画在 Vue 中被称之为 Vue Transition Modes
  • react-transition-group中使用SwitchTransition来实现该动画

SwitchTransition中主要有一个属性: mode, 它有两个值

  1. in-out: 表示新组件先进入, 旧组件再移除
  2. out-in: 表示旧组件先移除, 新组件再进入

如何使用SwitchTransition呢?

  • SwitchTransition组件里面要有CSSTransition或者Transition组件, 不能直接包裹你想要切换的组件
  • SwitchTransition里面的CSSTransitionTransition组件不再像以前那样接受in属性来判断元素是何种状态, 取而代之的是key属性

下面代码为按钮的入场出场效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { SwitchTransition, CSSTransition } from 'react-transition-group'

export default class SwitchAnimation extends PureComponent {
constructor(props) {
super(props)

this.state = {
isOn: true
}
}

render() {
const { isOn } = this.state

return (
<SwitchTransition mode="out-in">
<CSSTransition classNames="btn" timeout={500} key={isOn ? 'on' : 'off'}>
{<button onClick={this.btnClick.bind(this)}>{isOn ? 'on' : 'off'}</button>}
</CSSTransition>
</SwitchTransition>
)
}

btnClick() {
this.setState({ isOn: !this.state.isOn })
}
}

对应的 CSS 样式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.btn-enter {
transform: translate(100%, 0);
opacity: 0;
}

.btn-enter-active {
transform: translate(0, 0);
opacity: 1;
transition: all 500ms;
}

.btn-exit {
transform: translate(0, 0);
opacity: 1;
}

.btn-exit-active {
transform: translate(-100%, 0);
opacity: 0;
transition: all 500ms;
}

TransitionGroup

当我们有一组动画时, 需要将这些CSSTransition放入到一个TransitionGroup中来完成动画

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import React, { PureComponent } from 'react'
import { CSSTransition, TransitionGroup } from 'react-transition-group'

export default class GroupAnimation extends PureComponent {
constructor(props) {
super(props)

this.state = {
friends: []
}
}

render() {
return (
<div>
<TransitionGroup>
{this.state.friends.map((item, index) => {
return (
<CSSTransition classNames="friend" timeout={300} key={index}>
<div>{item}</div>
</CSSTransition>
)
})}
</TransitionGroup>
<button onClick={e => this.addFriend()}>+friend</button>
</div>
)
}

addFriend() {
this.setState({
friends: [...this.state.friends, 'coderlion']
})
}
}
-------------本文结束感谢阅读-------------