React.js中的动画效果
写在前面:
目前网上搜到的有很多是讲 ReactCSSTransitionGroup 的,现在它已经被 CSSTransitionGroup 取代了,但是它们实现的机制是一样的。本文介绍的是CSSTransitionGroup。
介绍两种,一种是渐变的过渡动画,一种是移动元素的间隔动画。
一、过渡动画
1.用什么——CSSTransitionGroup
React通过CSSTransitionGroup可以实现动画效果。
在之前版本中使用 ReactTransitionGroup
和 ReactCSSTransitionGroup
实现动画的,这两个包都被移动到了 react-transition-group
包中。
2.怎么用
一个元素渐隐渐现动画的简单例子:
使用npm install react-transition-group --save
导入react-transition-group
import { CSSTransitionGroup } from 'react-transition-group';
// JSX
<CSSTransitionGroup
transitionName="example"
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
{SomeView}
</CSSTransitionGroup>
// CSS
.example-enter {
opacity: 0.01;
}
.example-enter.example-enter-active {
opacity: 1;
transition: opacity 200ms ease-in;
}
.example-leave {
opacity: 1;
}
.example-leave.example-leave-active {
opacity: 0.01;
transition: opacity 200ms ease-in;
}
实现一个渐隐渐现的动画非常简单,在元素外部套上一个CSSTransitionGroup,对group进行一些设置,然后在CSS中对相应的类进行一些动画设置即可。
3.这些属性都是什么
首先看CSSTransitionGroup,它包含了以下几个属性(上例用到了其中三个):
- transitionName
它定义了这个动画组的名字。它可以是一个字符串,也可以是一个对象。如果是对象,则必须包含以下六个属性:
transitionName={{
enter:'itemEnter',
leave:'itemLeave',
appear:'itemAppear',
enterActive:'itemEnterActive',
leaveActive:'itemLeaveActive',
appearActive:'itemAppearActive'
}}
在定制CSS动画的时候,如果transitionName是字符串,则类需要以它为前缀;如果transitionName为对象,则需要以对象中设置的为准。
- transitionAppear / transitionEnter / transitionLeave
这是三个BOOL值,代表是否要开启这个过渡动画。
Appear是在元素挂载的时候的动画,Enter是元素出现时候的动画,Leave是元素消失时候的动画。
- transitionAppearTimeout / tansitionEnterTimeout / transitionLeaveTimeout
如果上一个BOOL值被设置成true,则这里必须设置与其对应的Timeout值,用于指定对应过渡动画的总时长。
注意,这个timeout不是动画的准确时间,而是动画的的时长上限。如果动画时间小于timeout,则画面会呆住;如果动画时间大于timeout,则画面会在最后一帧直接跳到最终状态去。
详细的说明一下动画时间小于timeout的情况:在Enter的时候,动画是从一开始就播放,多出来的时间在后面;Leave的时候,动画会在最后时刻播放完,多出来的时间在前面。
这里就算设置了过渡动画存在,且设置了时间timeout,但是元素并不会自动的就生成一个什么动画效果。
所以还需要在CSS中添加在具体状态下的表现。
源码中对这几个属性具体的定义如下:
4.是怎么做到的
CSSTransitionGroup分为五个模块。
TransitionEvents模块负责对transitioned,animationed时间进行绑定和解绑;
ChildMapping提供了对 TransitionGroup 这个 component 的 children 进行格式化的工具;
CSSTransitionGroup 会调用 CSSTransitionGroupChild 对 children 中的每个元素进行包装,然后将包装后的 children 作为 TransitionGroup 的 children
TransitionGroup抽象appear,enter,leave三种动画过程,管理所有children的声明周期;
怎么样可以实现对目标元素动画呢?
CSSTransitionGroup的答案是对每个目标元素增加一组生命周期方法。
当这个元素出现的回调方法中,给它添加一个class,实现一段动画;在这个元素消失的回调方法中,再给它添加一个类,实现消失动画。
具体的来看:
CSSTransitionGroup中把每个需要动画的元素格式化成了CSSTransitionGroupChild组件。
对每个Child组件都实现三个方法:
componentWillEnter
componentWillLeave
componentWillAppear
源码部分如下:
在这三个方法中,都调用一个transition方法,传入参数'enter', 'leave', 'appear',以及设置好的timeout等值。
在transition方法中,会对传进来的字符串参数进行拼接:
transitionName + 'appear'/'enter'/'leave'
transitionName + 'appear'/'enter'/'leave' + 'active'
然后将拼接好的类名添加到指定元素的DOM节点上,从而实现动画。
源码如下:
开发者可以直接在CSS中按约定className编辑动画,而CSSTransitionGroup会识别到并展示出来。
PS:劣势
CSSTransitionGroup的优势是非常明显的,简化代码、提高性能等,但是其劣势我们也需要了解,以在做实际项目时进行适当的取舍。
- 不兼容较老的、不支持CSS3的浏览器;
- 不支持为CSS属性之外的东西(比如滚动条位置或canvas绘画)添加动画;
- 可控粒度不够细。CSS3动画只支持start、end、iteration三个事件,不支持对中间状态进行处理。
- transitionEnd和animationEnd事件不稳定。
二、间隔动画
间隔动画实现方式很简单,有两种:
- requestAnimationFrame
- setTimeout
requestAnimationFrame可以以最小的性能损耗实现最流畅的动画,它被调用的次数频繁度超出你想象。在requestAnimationFrame不支持或不可用的情况下,就要考虑降级到不那么智能的setTimeout了。
间隔动画在实现原理上其实很简单,就是周期性的触发组件的状态更新,通过在组件的render方法中加入这个状态值,组件能够在每次状态更新触发的重渲染中正确表示当前的动态阶段。
var Todo = React.createClass(
getInitialState: function() {
return {
left: 0
};
},
componentWillUpdate: function() {
requestAnimationFrame(this. resolveAnimationFrame);
},
render: function() {
return <div style={{left: this.state.left}}>This will animate!</div>;
},
resolveAnimationFrame: function() {
if(this.state.left <= 100) {
this.setState({
left: this.state.left + 1
});
}
}
);
本文有很多内容参考了:
http://www.alloyteam.com/2016/01/react-animation-practice/