一個簡單的構建React元件動畫方案
最近對前端動畫方案做了一些調研,想找到一個簡單且易複製的方案在React元件中使用(也就是用複製貼上解決80%的問題),最終選擇官方的 react-transition-group 加 animate.css
我調研過的方案
- anijs online demo 酷炫是很酷炫但是引用方式比較原始不太適合現在的前端構建流程和React
- velocity.js 用jQuery $.animate() 相同的API 但是可以不依賴 jQuery,這方式並不是我所期望的故放棄
- react-motion 非常酷炫如果要做複雜動畫目前來看非它莫屬,但是於我簡單易複製的期望不符
- animate.css 用css 實現了各種常見的動畫效果,而且還有人封裝了 react-animated-css
- react-transition-group 官方的方案容易上手,但是應對複雜動畫比較無力,可是我並不做複雜的動畫因此這也就是心中的完美方案
關於CSS動畫
本文並不打算介紹 CSS 動畫但是推薦一些資源,如果你對CSS 動畫比較陌生也先閱讀下面的資源
- CSS動畫簡介-阮一峰
- 支援動畫屬性列表-MDN
- animationendEvent監聽動畫是否結束,一個比較實用的功能就是類似fadeout 這種退場動畫完全結束的時候刪除對應的DOM結構
animate.css
animate.css 是一個出色的樣式庫,提供了各種常用的CSS 動畫效果,簡易的例子如下基本見名知意這裡就不做額外的解釋了
<div class="animated bounce delay-2s">Example</div> 複製程式碼
不過大多數的時候我們必然不需要引入全部樣式,甚至我們可能只想copy一個動畫效果,在這裡我fork 了一份 animate.css 然後在其構建的過程中添加了sourcemap方便copy
帶sourcemap的DEMO站點 開啟控制檯開啟複製貼上之旅

簡單介紹一下react-transition-group 中的 CSSTransition
CSSTransiotn 會在動畫的生命週期內為其指定的子元素新增代表其處於指定生命週期的 class
假設有如下DEMO 當 CSSTransition
的 in
屬性值切換時 true
的時候會依次給 chidern
新增 fade-enter, fade-enter-active, fade-enter-done。 當 CSSTransition
的 in
屬性值切換時 false
的時候會依次給 chidern
新增 fade-exit, fade-exit-active, fade-exit-done。
其中 -enter-active
緊隨 -enter
之後新增並且跟 -enter
同時存在,而 -enter-done
在動畫結束時新增並且與 -enter-active
和 enter
互斥, exit
同理。
所以當我們要利用CSSTransition實現動畫效果的時候,只需要定義出對應時間點出現的 class
樣式即可,需要注意的倆點
- 動畫結束的時間根據
timeout
決定所以所寫的樣式during
必須跟與其對應(之後我們會對CSSTransition進行簡單封裝解決這個問題) -
CSSTransition
決定字首的引數是classNames
不是className
<CSSTransition in={fadeIn} timeout={2000} unmountOnExit classNames="fade" onEnter={this.onEnter} onEntered={this.onEntered} onExit={this.onExit} onExited={this.onExited} > <div className="demo" >fade-{fadeIn ? 'in' : 'out'}</div> </CSSTransition> 複製程式碼
雖然在動畫的執行的生命週期內出現了6個關鍵點但是使用 css3 animation
實現動畫效果時我們只需操作倆個時間點 -enter
和 -exit
就ok了,之後要做的就是在 animate.css
copy 對應的程式碼
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } @keyframes fadeOut { from { opacity: 1; } to { opacity: 0; } } .fade { &-enter { animation-name: fadeIn; animation-duration: 2000ms; } &-exit { animation-name: fadeOut; animation-duration: 2000ms; } } 複製程式碼
對CSSTransition 進行簡單封裝
利用 React.cloneElement
對為 CSSTransition
預設新增 animated
class,並且通過設定內聯的樣式的方式讓動畫效果結束的時間跟 timeout
欄位一致
animated
為了為動畫設定一些預設的樣式,比如像下面這樣預設設定動畫時長為 1s
animation-fill-mode
為 both
.animated { animation-duration: 1s; animation-fill-mode: both; } @media (print), (prefers-reduced-motion) { .animated { animation: unset !important; transition: none !important; } } 複製程式碼
封裝程式碼示意
import React from 'react' import { CSSTransition } from 'react-transition-group' let count = 0 export default class Animation extends React.Component { static defaultProps = { in: true, timeout: 1000,// 與 .animate 中設定的預設時間對應 unmountOnExit: true, classNames: '', onEnter () {}, onEntered () {}, onExit () {}, onExited () {} } constructor () { super() this.count = count++ } onEnter = () => { console.time(`enter${this.count}`) this.props.onEnter() } onEntered = () => { console.timeEnd(`enter${this.count}`) this.props.onEntered() } onExit = () => { console.time(`exit${this.count}`) this.props.onExit() } onExited = () => { console.timeEnd(`exit${this.count}`) this.props.onExited() } render () { const { in: isIn, timeout, unmountOnExit, classNames, children } = this.props const { props: { className = '', style = {} } } = children return ( <CSSTransition in={isIn} timeout={timeout} unmountOnExit={unmountOnExit} classNames={classNames} onEnter={this.onEnter} onEntered={this.onEntered} onExit={this.onExit} onExited={this.onExited} > {React.cloneElement(children, { className: `animated ${className}`, style: { ...{ '--webkit-animation-duration': `${timeout}ms`, animationDuration: `${timeout}ms` }, ...style } })} </CSSTransition> ) } } 複製程式碼