React学习笔记[14]✨~一文掌握如何在React中使用[过渡动画]👻

61 阅读6分钟

我正在参加「掘金·启航计划」

开发中,想要给组件增加显示/消失的过渡动画以增加用户体验。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在执行过程中,有三个状态:appearenterexit

它们都对应三种状态的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;
}

效果如下: