[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)