怎么寫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的描述:
接受兩個參數,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來實現一個簡單的平移動畫,效果是這樣的
代碼如下:
/**
* 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 實現一個聯動動畫,鏈式反應的效果,如下:
代碼如下:
/**
* 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 />
實現一個簡單的進出場動畫,效果如下:
代碼如下:
/**
* 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