[TOC]
# 介紹
一個簡單的 loading 動畫或者頁面切換效果不僅能緩解用戶的等待情緒,甚至通過使用品牌 logo 等形式,默默達到品牌宣傳的效果。
傳統 web 動畫大多數都通過直接操作實際 DOM 元素來實現,這在 React 中顯然是不被提倡的。那么,在 React 中動畫都是如何實現的呢?
在 React 中實現動畫本質上與傳統 web 動畫一樣,仍然是兩種方式: 通過 css3 動畫實現和通過 js 修改元素屬性。只不過在具體實現時,要更為符合 React 的框架特性,可以概括為幾類:
1. 基于定時器或 `requestAnimationFrame(RAF)` 的間隔動畫;
2. 基于 css3 的簡單動畫;
3. React 動畫插件?`ReactCSSTransitionGroup`?;
4. 結合 hook 實現復雜動畫;
5. 其他第三方動畫庫。
# 基于`requestAnimationFrame(RAF)`
```
//?使用requestAnimationFrame改變state
this.state=?{?percent:?10?};
...
increase = () => {
const percent = this.state.percent;
const targetPercent = percent >= 90 ? 100 : percent + 10;
const speed = (targetPercent - percent) / 400;
let start = null;
const animate = timestamp => {
if (!start) start = timestamp;
const progress = timestamp - start;
const currentProgress = Math.min(
parseInt(speed * progress + percent, 10),
targetPercent
);
this.setState({ percent: currentProgress });
if (currentProgress < targetPercent) {
window.requestAnimationFrame(animate); // 遞歸調用自身,直到滿足條件
}
};
window.requestAnimationFrame(animate);
?};
...
```
# Framer Motion
現已升級為:[Framer Motion](https://github.com/framer/motion)
- Creating our first animation with React pose
` npm install react react-dom react-pose styled-components`
# CSSTransitionGroup
- Animating a todo list with
`npm install react-transition-group`
`react-transition-group`包含`CSSTransitionGroup`和`TransitionGroup`兩個動畫插件,其中,后者是底層 api,前者是后者的進一步封裝,可以較為便捷地實現 css 動畫。
```
import React, { Component } from 'react';
import { CSSTransitionGroup } from 'react-transition-group';
...
render() {
const renderTabs = () => {
return tabData.map((item, index) => {
return (
<div
className={`tab-item${item.id === activeId ? ' tab-item-active' : ''}`}
key={`tab${item.id}`}
>
{item.panel}
<span className="btns btn-delete" onClick={() => this.deleteTab(item.id)}>?</span>
</div>
);
})
}
return (
<div>
<div className="tabs" >
<CSSTransitionGroup
transitionName="tabs-wrap"
transitionEnterTimeout={500}
transitionLeaveTimeout={500}
>
{renderTabs()}
</CSSTransitionGroup>
<span className="btns btn-add" onClick={this.addTab}>+</span>
</div>
<div className="tab-cont">
cont
</div>
</div>
);
...
```
使用`CSSTransitionGroup`需要注意以下幾點:
* `CSSTransitionGroup`默認在 DOM 樹中生成一個`span`標簽包裹其子節點,如果想要使用其他 html 標簽,可設定`CSSTransitionGroup`的`component`屬性;
* `CSSTransitionGroup`的子元素必須添加`key`值才會在節點發生變化時,準確地計算出哪些節點需要添加入場動畫,哪些節點需要添加離場動畫;
* `CSSTransitionGroup`的動畫效果**只作用于直接子節點,不作用于其孫子節點**;
* 動畫的結束時間不以 css 中 transition-duration 為準,而是以`transitionEnterTimeout`,`transitionLeaveTimeout`,`TransitionAppearTimeout`為準,因為某些情況下 transitionend 事件不會被觸發,詳見[MDN transitionend](https://developer.mozilla.org/en-US/docs/Web/Events/transitionend)。
## 結合 hook 實現復雜動畫
CSSTransitionGroup?的底層 API `TransitonGroup`?還為其子元素額外提供了一系列特殊的生命周期 hook 函數,在這些 hook 函數中結合第三方動畫庫可以實現豐富的入場、離場動畫效果。
TransisitonGroup?分別提供一下6個生命周期 hook 函數:
1. componentWillAppear(callback)
2. componentDidAppear()
3. componentWillEnter(callback)
4. componentDidEnter()
5. componentWillLeave(callback)
6. componentDidLeave()
# 其他相關庫
* [react-animations](https://github.com/FormidableLabs/react-animations)
* [React-Spring](https://github.com/pmndrs/react-spring)
* [https://github.com/animatedjs/animated](https://github.com/animatedjs/animated)
* [https://github.com/chenglou/react-motion](https://github.com/chenglou/react-motion)
此外,還有很多優秀的第三方動畫庫,如 [GASP](https://greensock.com/gsap)、[react-motion](https://github.com/chenglou/react-motion)?,[Animated](https://github.com/animatedjs/animated),?[velocity-react](https://github.com/google-fabric/velocity-react),[react-web-animation](https://github.com/bringking/react-web-animation)等,這些動畫庫在使用時也各有千秋。
# 結語
當我們在 React 中實現動畫時,首先要考量動畫的難易程度和使用場景,對于簡單動畫,優先使用 css3 實現,其次是基于 js 的時間間隔動畫。如果是元素入場動畫和離場動畫,則建議結合?CSSTransitionGroup?或者?TransitionGroup?實現。當要實現的動畫效果較為復雜時,不妨嘗試一些優秀的第三方庫,打開精彩的動效大門。
# 參考
[React 中常見的動畫實現方式](https://tech.youzan.com/react-animations/)
[創建 React 動畫的五種方式](https://zhuanlan.zhihu.com/p/28536964)