最近对前端动画方案做了一些调研,想找到一个简单且易复制的方案在React组件中使用(也就是用复制粘贴解决80%的问题),最终选择官方的react-transition-group加animate.css
我调研过的方案
anijs online demo 酷炫是很酷炫但是引用方式比较原始不太适合现在的前端构建流程和React
velocity.js 用jQuery $.animate() 相同的API 但是可以不依赖 jQuery,这方式并不是我所期望的故放弃
react-motion 非常酷炫如果要做复杂动画目前来看非它莫属,但是于我简单易复制的期望不符
react-transition-group 官方的方案容易上手,但是应对复杂动画比较无力,可是我并不做复杂的动画因此这也就是心中的完美方案
关于CSS动画
本文并不打算介绍 CSS 动画但是推荐一些资源,如果你对CSS 动画比较陌生也先阅读下面的资源
animationendEvent监听动画是否结束,一个比较实用的功能就是类似fadeout 这种退场动画完全结束的时候删除对应的DOM结构
animate.css
animate.css 是一个出色的样式库,提供了各种常用的CSS 动画效果,简易的例子如下基本见名知意这里就不做额外的解释了
不过大多数的时候我们必然不需要引入全部样式,甚至我们可能只想copy一个动画效果,在这里我fork 了一份 animate.css 然后在其构建的过程中添加了sourcemap方便copy
简单介绍一下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
in={fadeIn}
timeout={2000}
unmountOnExit
classNames="fade"
onEnter={this.onEnter}
onEntered={this.onEntered}
onExit={this.onExit}
onExited={this.onExited}
>
虽然在动画的执行的生命周期内出现了6个关键点但是使用css3 animation实现动画效果时我们只需操作俩个时间点 -enter和 -exit就ok了,之后要做的就是在animate.csscopy 对应的代码
@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 (
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
}
})}
)
}
}
在线 DEMO