我正在参加「掘金·启航计划」
开发中,想要给组件增加显示/消失的过渡动画以增加用户体验。React社区提供了 react-transition-group 用来完成过渡动画,该库可以帮助我们方便的实现组件进入和离开时的动画,使用时需要额外进行安装:
npm install react-transition-group
#or
yarn add react-transition-group
react-transition-group主要包含以下四个组件:
Transition
- 该组件是一个和平台无关的组件(不一定要结合CSS)
- 一般我们都是结合CSS来完成的,所以常用的是CSSTransition
CSSTransition
- 用来完成过渡动画效果
SwitchTransition
- 用于两个组件之间的显示和隐藏的切换
TransitionGroup
- 将多个动画组件包裹在其中,一般用于列表中元素的动画
一、CSSTransition
CSSTransition是基于Transition组件构建的
CSSTransition的三个状态
CSSTransition在执行过程中,有三个状态:appear、enter、exit
它们都对应三种状态的CSS样式:
开始时 对应的类是 -appear、-enter、-exit
执行动画时 对应的类是 -appear-active、-enter-active、-exit-activ
执行结束时 对应的类是 -appear-done、-enter-done、-exit-done
CSSTransition的常用属性
nodeRef
用于指定需要做过渡效果的组件
比如,使用CSSTransition包裹了想要做过渡动画的组件:
需要通过createRef为该组件创建好ref对象并绑定到该组件上
然后通过nodeRef指定需要过渡的组件为该组件即可
in
触发进入或者退出状态
当in为true时,触发进入状态,会为组件添加-enter、-enter-active的class开始执行动画;当动画执行结束后,会移除两个class,并且添加-enter-done的class
当in为false时,触发退出状态,会添加-exit、-exit-active的class开始执行动画;当动画执行结束后,会移除两个class,并添加-exit-done的class;如果添加了unmountOnExit={true},那么组件会在执行退出动画结束后被移除掉
classNames
指定动画class的名称
- 决定了在编写css时,对应的class名称
- 比如设置classNames="box",那么在指定进入动画时会添加box-enter、box-enter-active类名;在执行进入动画结束后,会添加box-enter-done类名
注意,需要同时添加上timeout(过渡动画时间)才能看到先box-enter、box-enter-active后box-enter-done(或先box-exit、box-exit-active后box-exit-done)的变化
在点击切换时:
隐藏:
显示:
此时,便可以通过编写类名对应的css来添加动画效果了:
.box-enter {
opacity: 0;
}
.box-enter-active {
opacity: 1;
transition: opacity 2s ease;
}
/* 离开动画 */
.box-exit {
opacity: 1;
}
.box-exit-active {
opacity: 0;
transition: opacity 2s ease;
}
timeout
过渡动画的时间,为毫秒值
- 比如设置timeout为2000毫秒,那么在执行进入动画时会立即添加上-enter、-enter-active,在2000毫秒后(执行过渡动画结束)才会移除-enter、-enter-active,添加上-enter-done
- 而与CSS中书写的transition中设置的时间无关,为了保证更好的体验,timeout与css中transition中设置的过渡动画的时间最好保持一致
timeout也可设置为一个对象,来分别控制各个状态下的过渡时间,如:
timeout={{ appear: 500, enter: 300, exit: 500, }}
appear
是否在首次进入时添加动画
- 需要in同时为true
- 注意要添加 -appear 、-appear-active相关css
unmountOnExit
是否要在执行完退出动画后移除组件
如果不不将该属性的值设置为true,则执行完退出动画后该组件会立即再次出现
将该属性值置为true后,在执行完退出动画后会卸载该组件
钩子函数
onEnter:进入动画执行之前被触发
onEntering:进入动画执行时被触发
onEntered:进入动画执行结束后被触发
onExit:离开动画执行之前被触发
onExiting:离开动画执行时被触发
onExited:离开动画执行结束后被触发
组件执行进入动画:
组件执行离开动画:
(想了解更多属性请查看官网)
案例
比如,我们想点击按钮来切换组件内容的展示,如果实现为以下代码,切换起来显得很生硬:
import React, { createRef, PureComponent } from 'react'
export class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
isShow: true
}
}
render() {
const { isShow } = this.state
return (
<div>
<button onClick={e => this.setState({isShow: !isShow})}>切换</button>
{ isShow && <div className="content">我是内容</div> }
</div>
)
}
}
export default App
那么我们就可以通过CSSTransition组件结合CSS来实现组件显示与隐藏的过渡动画
App.jsx:
import React, { createRef, PureComponent } from 'react'
import { CSSTransition } from "react-transition-group"
import "./style.css"
export class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
isShow: true
}
this.contentRef = createRef()
}
render() {
const { isShow } = this.state
return (
<div>
<button onClick={e => this.setState({isShow: !isShow})}>切换</button>
<CSSTransition
nodeRef={this.contentRef}
classNames="box"
in={isShow}
timeout={2000}
appear
unmountOnExit={true}
>
<div ref={this.contentRef} className="content">我是内容</div>
</CSSTransition>
</div>
)
}
}
export default App
style.css:
/* 进入动画 */
.box-appear, .box-enter {
opacity: 0;
}
.box-appear-active, .box-enter-active {
opacity: 1;
transition: opacity 2s ease;
}
/* 离开动画 */
.box-exit {
opacity: 1;
}
.box-exit-active {
opacity: 0;
transition: opacity 2s ease;
}
效果如下:
二、SwitchTransition动画
SwitchTransition可以完成两个组件之间切换的动画
- 比如在实现一个按钮在不同状态之间切换时,想要看到先从左侧移出,再从右侧进入的效果
SwitchTransition主要属性
mode
mode的属性有两个:
in-out:表示新组件先进入,旧组件再移除
out-in:表示旧组件先移除,新组件再进入
SwitchTransition使用方法
在使用SwitchTransition时SwitchTransition组件中要包裹CSSTransition或Transition组件,不能直接包裹想要切换的组件;在CSSTransition或Transition组件上不能够通过添加in属性来判断元素处于哪种状态,而是通过key属性(有没有key不会影响动画效果,只是为了标识不同状态下的组件)
案例
比如想要通过用户是否登录来切换按钮的文本。用户登录时展示为“退出”,用户未登录时展示为“登录”,切换按钮文本时展示按钮切换的动画效果。
App.jsx:
import React, { PureComponent } from 'react'
import { CSSTransition, SwitchTransition } from 'react-transition-group'
import './style.css'
export class App extends PureComponent {
constructor() {
super()
this.state = {
isLogin: false,
}
}
render() {
const { isLogin } = this.state
return (
<div>
<SwitchTransition mode="out-in">
<CSSTransition
classNames="login"
key={isLogin ? 'leave' : 'in'}
timeout={1000}
>
<button onClick={e => this.setState({isLogin: !isLogin})}>{isLogin ? '退出' : '登录'}</button>
</CSSTransition>
</SwitchTransition>
</div>
)
}
}
export default App
style.css:
.login-enter {
transform: translateX(100px);
opacity: 0;
}
.login-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.login-exit {
transform: translateX(0);
opacity: 1;
}
.login-exit-active {
transform: translateX(-100px);
opacity: 0;
transition: all 1s ease;
}
效果如下:
三、TransitionGroup
当想要实现一组动画时,可以将CSSTransition放在TransitionGroup中来完成
- 比如,实现一个列表中列表数据新增、删除时的动画效果
- 可使用TransitionGroup中的component属性指定渲染为哪种标签
需要注意的是,要为CSSTransition绑定唯一的key,不然在离开动画时会有位置错乱的问题
案例
比如为书籍列表增加添加书籍与移除书籍的动画
App.jsx:
import React, { PureComponent } from 'react'
import { CSSTransition, TransitionGroup } from 'react-transition-group'
import './style.css'
export class App extends PureComponent {
constructor() {
super()
this.state = {
books: [
{ id: 1, name: 'bookA', price: 10},
{ id: 2, name: 'bookB', price: 20},
{ id: 3, name: 'bookC', price: 30}
]
}
}
addBook() {
const books = [...this.state.books]
books.push({ id: new Date().getTime(), name: 'newBook', price: 10})
this.setState({books})
}
removeBook(index) {
const books = [...this.state.books]
books.splice(index, 1)
this.setState({books})
}
render() {
const { books } = this.state
return (
<div>
<TransitionGroup component="ul">
{
books.map((book, index) => {
return (
<CSSTransition key={book.id} classNames="book" timeout={1000}>
<li>
{index}-{book.name}-{book.price}
<button onClick={e => this.removeBook(index)}>删除</button>
</li>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={ e => this.addBook()}>添加</button>
</div>
)
}
}
export default App
style.css:
.book-enter {
transform: translateX(150px);
opacity: 0;
}
.book-enter-active {
transform: translateX(0);
opacity: 1;
transition: all 1s ease;
}
.book-exit {
transform: translateX(0);
opacity: 1;
}
.book-exit-active {
transform: translateX(150px);
opacity: 0;
transition: all 1s ease;
}
效果如下: