用react-motion实现react动画
怎么写react的动画一直是让我比较头疼的问题,之前一直在使用官方的react-transition-group,感觉并不是很好用,也没有找到很好的替代方案。恰巧最近在知乎看了这样一篇文章(https://zhuanlan.zhihu.com/p/28500217),也就借着机会研究了下react-motion
react-motion
相比较react-transition-group,用react-motion进行动画的编写显得要直观很多,写法类似这样
<Motion defaultStyle={{left: 0}} style={{left: spring(10)}}>
{interpolatingStyle => <div style={interpolatingStyle} />}
</Motion>
指定一个初始style(defaultStyle),然后赋值一个目标style(style),中间每帧都会由react-motion计算出对应的style,用户只管使用生成的style(interpolatingStyle),不用关心物理效果的实现,动画中断的处理,一切事情都交给react-motion
基本概念
spring
spring是react-motion最简单也是最深奥的一个函数,是react-motion构筑动画的基石,用户可以通过spring实现各种物理效果,下面是官方文档中对spring的描述:
image.png接受两个参数,val和config
- val: 终点值,即你希望达到的最后状态的数值,number类型
- config: 用于生成物理效果的配置,关于stiffness和damping,下面着重介绍,precision用于配置val的精确度,一般我们用默认的0.01就行
stiffness 和 damping
我们可以通过spring(val, {stiffness: ?, damping: ?})来生成各种缓动效果,关于stiffness和damping,如果用书面形式说明可能会比较难以理解,所以请点击下面的官方链接感受下
http://chenglou.github.io/react-motion/demos/demo5-spring-parameters-chooser/
如果把我们要设置动画的物体想象成弹簧,stiffness相当于弹簧的强度,其影响的是弹簧回弹的速度,相同damping情况下,stiffness越大,回弹速度越快;damping是弹簧的减震性,其影响的是弹簧的回弹次数,相同stiffness情况下,damping越大,回弹次数越少
presets
一般情况下,大多数用户其实是不想手动去配置stiffness和damping的,所以react-motion也单独提供了一些预设值,可以通过下面的方式进行使用
import {presets} from 'react-motion'
//...
<Motion defaultStyle={{left: 0}} style={{left: spring(10, presets.wobbly)}}>
{interpolatingStyle => <div style={interpolatingStyle} />}
</Motion>
具体有哪些配置,请参考这里
ok,基本概念已经讲完了,下面开始正式开始用react-motion实现动画
实现动画
react-motion一共提供了三个组件来方便用户进行动画的实现,<Motion />适合编写单个组件的形变动画,<StaggeredMotion />用于编写一串有相互关联关系的实体的动画, <TransitionMotion />则是用来编写组件mount和unmount的动画,下面挨个来讲
<Motion/>
我们用Motion来实现一个简单的平移动画,效果是这样的
motion.gif代码如下:
/**
* react-motion 的测试
* 简单的demo Motion
*/
import React, {Component} from 'react'
import {Motion, spring, presets} from 'react-motion'
import './test.css'
class Test1 extends Component {
state = {
left: 0
}
clickHandler() {
let targetX = 0
if(this.state.left === 0) {
targetX = 200
} else {
targetX = 0
}
this.setState({
left: targetX
})
}
componentDidMount() {
this.clickHandler()
}
render() {
return (
<div className="container">
<Motion style={{x: spring(this.state.left, presets.wobbly)}}>
{interpolatingStyle => {
// debugger
return (
<div style={{transform: `translateX(${interpolatingStyle.x}px)`}} className='box'></div>
)
}}
</Motion>
<button onClick={this.clickHandler.bind(this)}>run</button>
</div>
)
}
}
export default Test1
<StaggeredMotion />
用StaggeredMotion 实现一个联动动画,链式反应的效果,如下:
staggeredmotion .gif代码如下:
/**
* react-motion 的测试
* 简单的demo StaggeredMotion
*/
import React, { Component } from 'react'
import { StaggeredMotion, spring, presets } from 'react-motion'
import './test.css'
class Test2 extends Component {
state = {
length: 10
}
addLength() {
let newLength
if (this.state.length) {
newLength = 0
} else {
newLength = 10
}
this.setState({
length: newLength
})
}
render() {
let boxes = []
for (let i = 0, len = this.state.length; i < len; i++) {
boxes.push({
scale: 0
})
}
return (
<div>
<div>
{this.state.length > 0 ? (
<StaggeredMotion defaultStyles={boxes}
styles={prevStyles => prevStyles.map((item, i) => {
return i === 0
? { scale: spring(1, { ...presets.noWobble }) }
: prevStyles[i - 1]
})}>
{interpolatingStyles =>
<div>
{interpolatingStyles.map((item, i) => {
return (
<div className="box2"
key={i}
style={{
transform: `scale(${item.scale}, ${item.scale})`
}}></div>
)
})}
</div>
}
</StaggeredMotion>
) : null}
</div>
<button onClick={this.addLength.bind(this)}>run</button>
</div>
)
}
}
export default Test2
<TransitionMotion />
实现一个简单的进出场动画,效果如下:
transition-motion.gif代码如下:
/**
* react-motion 的测试
* 简单的demo TransitionMotion
*/
import React, { Component } from 'react'
import { TransitionMotion, spring } from 'react-motion'
import './test.css'
class Test3 extends Component {
state = {
show: true
}
componentDidMount() {
this.setState({
show: false
})
}
clickHandler() {
this.setState({
show: !this.state.show
})
}
willEnter(styleThatEnter) {
return { scale: 0 }
}
willLeave(styleThatLeft) {
return { scale: spring(0) }
}
render() {
return (
<div>
<button onClick={this.clickHandler.bind(this)}>run</button>
<TransitionMotion styles={this.state.show ? [{
key: 'test',
style: { scale: spring(1) }
}] : []}
willEnter={this.willEnter}
willLeave={this.willLeave}>
{inStyles => (
inStyles[0] ? (
<div className="box3"
key={inStyles[0].key}
style={{
transform: `scale(${inStyles[0].style.scale},${inStyles[0].style.scale})`
}}></div>
) : null
)}
</TransitionMotion>
</div>
)
}
}
export default Test3