以前一直投入在
React Native
中剩辟,寫動畫的時候不是用CSS 中的 transitions / animations
元扔,就是依賴像GreenSock
這樣的庫躯保,最近轉(zhuǎn)向Web
,在Tweet
得到很多大佬關(guān)于React Web 動畫
的回應(yīng)澎语,于是決定分享給大家途事,如有其他見解验懊,非常歡迎在下面評論
中交流
以下便是本文要分享的創(chuàng)建 React 動畫
的幾種方式
- CSS animation
- JS Style
- React Motion
- Animated
- Velocity React
下面,勒次個特斯大特一特
CSS animation
給元素添加 class
是最簡單尸变,最常見的書寫方式义图。如果你的 app
正在使用 CSS
,那么這將是你最愉快的選擇
贊同者
: 我們只需修改 opacity
和 transform
這樣的屬性召烂,就可構(gòu)建基本的動畫碱工,而且,在組件中奏夫,我們可以非常容易地通過 state
去更新這些值
反對者
:這種方式并不跨平臺
怕篷,在 React Native
中就不適用,而且酗昼,對于較復(fù)雜的動畫匙头,這種方式難以控制
接下來,我們通過一個實例來體驗一下這種創(chuàng)建方式:當 input focus 的時候仔雷,我們增加它的寬度
首先,我們要創(chuàng)建兩個 input
要用到的 class
.input {
width: 150px;
padding: 10px;
font-size: 20px;
border: none;
border-radius: 4px;
background-color: #dddddd;
transition: width .35s linear;
outline: none;
}
.input-focused {
width: 240px;
}
一個是它原始
的樣式舔示,一個是它 focus
后的樣式
下面碟婆,我們就開始書寫我們的 React 組件
在此,推薦一個 在線的 React VS Code IDE惕稻,真的很強大竖共,讀者不想構(gòu)建自己的 React app
,可以在其中檢驗以下代碼的正確性
class App extends Component {
state = {
focused: false,
}
componentDidMount() {
this._input.addEventListener('focus', this.focus);
this._input.addEventListener('blur', this.focus);
}
focus = () => {
this.setState(prevState => ({
focused: !prevState.focused,
}));
}
render() {
return (
<div className="App">
<div className="container">
<input
ref={input => this._input = input}
className={['input', this.state.focused && 'input-focused'].join(' ')}
/>
</div>
</div>
);
}
}
- 我們有一個
focused
的state
俺祠,初始值為false
公给,我們通過更新該值
來創(chuàng)建我們的動畫 - 在
componentDidMount
中,我們添加兩個監(jiān)聽器
蜘渣,一個focus
淌铐,一個blur
,指定的回調(diào)函數(shù)都
是focus
-
focus
方法會獲取之前focused
的值蔫缸,并負責(zé)切換
該值 - 在
render
中腿准,我們通過state
來改變input
的classNames
,從而實現(xiàn)我們的動畫
JS Style
JavaScipt styles
跟 CSS 中的 classes
類似拾碌,在 JS
文件中吐葱,我們就可以擁有所有邏輯
贊同者
:跟 CSS 動畫
一樣,且它的表現(xiàn)更加清晰校翔。它也不失為一個好方法弟跑,可以不必依賴任何 CSS
反對者
:跟 CSS 動畫
一樣,也是不跨平臺
的防症,且動畫一旦復(fù)雜孟辑,也難以控制
在下面的實例中哎甲,我們將創(chuàng)建一個 input
,當用戶輸入時扑浸,我們將一個 button
從 disable
轉(zhuǎn)變?yōu)?enable
class App extends Component {
state = {
disabled: true,
}
onChange = (e) => {
const length = e.target.value.length;
if (length > 0) {
this.setState({ disabled: false });
} else {
this.setState({ disabled: true });
}
}
render() {
const { disabled } = this.state;
const label = disabled ? 'Disabled' : 'Submit';
return (
<div style={styles.App}>
<input
style={styles.input}
onChange={this.onChange}
/>
<button
style={Object.assign({},
styles.button,
!this.state.disabled && styles.buttonEnabled
)}
disabled={disabled}
>
{label}
</button>
</div>
);
}
}
const styles = {
App: {
display: 'flex',
justifyContent: 'left',
},
input: {
marginRight: 10,
padding: 10,
width: 190,
fontSize: 20,
border: 'none',
backgroundColor: '#ddd',
outline: 'none',
},
button: {
width: 90,
height: 43,
fontSize: 17,
border: 'none',
borderRadius: 4,
transition: '.25s all',
cursor: 'pointer',
},
buttonEnabled: {
width: 120,
backgroundColor: '#ffc107',
}
}
- 我們有一個
disabled
的state
烧给,初始值為true
-
onChange
方法會獲取用戶的輸入,當輸入非空時喝噪,就切換disabled
的值 - 根據(jù)
disabled
的值础嫡,確定是否將buttonEnabled
添加到button
中
React Motion
React Motion
是 Cheng Lou 書寫的一個非常不錯的開源項目。它的思想是你可以對Motion 組件
進行簡單的樣式設(shè)置
酝惧,然后你就可以在回調(diào)函數(shù)
中通過這些值榴鼎,享受動畫帶來的樂趣
對于絕大多數(shù)的動畫組件,我們往往不希望對動畫屬性
(寬高晚唇、顏色等)的變化時間做硬編碼
處理巫财,react-motion
提供的 spring
函數(shù)就是用來解決這一需求的,它可以逼真地模仿真實的物理效果哩陕,也就是我們常見的各類緩動效果
下面是一個森破
的示例
<Motion style={{ x: spring(this.state.x) }}>
{
({ x }) =>
<div style={{ transform: `translateX(${x}px)` }} />
}
</Motion>
這是官方提供的幾個 demo
平项,真的可以是不看不知道,一看嚇一跳
贊同者
:React Motion
可以在 React Web
中使用悍及,也可以在 React Native
中使用闽瓢,因為它是跨平臺的。其中的 spring
概念最開始對我來說感覺挺陌生心赶,然而上手之后扣讼,發(fā)現(xiàn)它真的很神奇
,并且缨叫,它有很詳細的 API
反對者
:在某些情況下椭符,他不如純 CSS / JS 動畫
,雖然它有不錯的 API
耻姥,容易上手销钝,但也需要學(xué)習(xí)成本
為了使用它,首先我們要用 yarn
或 npm
安裝它
yarn add react-motion
在下面的實例中琐簇,我們將創(chuàng)建一個 dropdown 菜單
曙搬,當點擊按鈕時,下拉菜單友好展開
class App extends Component {
state = {
height: 38,
}
animate = () => {
this.setState((state) => ({ height: state.height === 233 ? 38 : 233 }));
}
render() {
return (
<div className="App">
<div style={styles.button} onClick={this.animate}>Animate</div>
<Motion
style={{ height: spring(this.state.height) }}
>
{
({ height }) =>
<div style={Object.assign({}, styles.menu, { height } )}>
<p style={styles.selection}>Selection 1</p>
<p style={styles.selection}>Selection 2</p>
<p style={styles.selection}>Selection 3</p>
<p style={styles.selection}>Selection 4</p>
<p style={styles.selection}>Selection 5</p>
<p style={styles.selection}>Selection 6</p>
</div>
}
</Motion>
</div>
);
}
}
const styles = {
menu: {
marginTop: 20,
width: 300,
border: '2px solid #ddd',
overflow: 'hidden',
},
button: {
display: 'flex',
width: 200,
height: 45,
justifyContent: 'center',
alignItems: 'center',
border: 'none',
borderRadius: 4,
backgroundColor: '#ffc107',
cursor: 'pointer',
},
selection: {
margin: 0,
padding: 10,
borderBottom: '1px solid #ededed',
},
}
- 我們從
react-motion
中 importMotion
和spring
- 我們有一個
height
的state
鸽嫂,初始值為38
纵装,代表menu
的高度 -
animate
方法設(shè)置menu
的height
,如果原 height
為38
据某,則設(shè)置新 height
為233
橡娄,如果原 height
為233
,則設(shè)置新 height
為38
- 在
render
中癣籽,我們使用Motion 組件
包裝整個p 標簽
列表挽唉,將this.state.height
的當前值設(shè)為組件的height
滤祖,然后在組件的回調(diào)函數(shù)
中使用該值作為整個下拉的高度 - 當按鈕被點擊時,我們通過
this.animate
切換下拉的高度
Animated
Animated
是基于 React Native
使用的同一個動畫庫建立起來的
它背后的思想是創(chuàng)建聲明式動畫
瓶籽,通過傳遞配置對象來控制動畫
贊同者
:跨平臺
匠童,它在 React Native
中已經(jīng)非常穩(wěn)定,如果你在 React Native
中使用過塑顺,那么你將不用再重復(fù)學(xué)習(xí)汤求。其中的 interpolate
是一個神奇的插值函數(shù),我們將在下面看到
反對者
:基于 Twitter
的交流严拒,它目前貌似不是 100%
的穩(wěn)定扬绪,在老的瀏覽器中的,存在前綴
和性能
的問題裤唠,而且挤牛,它也有學(xué)習(xí)成本
為了使用 Animated
,我們首先還是要用 yarn
或 npm
安裝它
yarn add animated
在下面的實例中种蘸,我們將模擬在提交表單成功后顯示的動畫 message
import Animated from 'animated/lib/targets/react-dom';
import Easing from 'animated/lib/Easing';
class AnimatedApp extends Component {
animatedValue = new Animated.Value(0);
animate = () => {
this.animatedValue.setValue(0);
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 1000,
easing: Easing.elastic(1),
}
).start();
}
render() {
const marginLeft = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [-120, 0],
});
return (
<div className="App">
<div style={styles.button} onClick={this.animate}>Animate</div>
<Animated.div
style={
Object.assign(
{},
styles.box,
{ opacity: this.animatedValue, marginLeft })}
>
<p>Thanks for your submission!</p>
</Animated.div>
</div>- 我們將 `animatedValue`
和 `marginLeft` 作為 `Animated.div ` 的 `style` 屬性墓赴, );
}
}
const styles = {
button: {
display: 'flex',
width: 125,
height: 50,
justifyContent: 'center',
alignItems: 'center',
border: 'none',
borderRadius: 4,
backgroundColor: '#ffc107',
cursor: 'pointer',
},
box: {
display: 'inline-block',
marginTop: 10,
padding: '0.6rem 2rem',
fontSize:'0.8rem',
border: '1px #eee solid',
borderRadius: 4,
boxShadow: '0 2px 8px rgba(0,0,0,.2)',
},
}
- 從
animated
中 importAnimated
和Easing
- 用
new Animated.Value(0)
創(chuàng)建一個值為0
的類屬性 -animatedValue
- 創(chuàng)建
animate
方法,處理所有的動畫航瞭,首先通過this.animatedValue.setValue(0)
初始化動畫值诫硕,實現(xiàn)的效果就是每次重新執(zhí)行
該動畫,然后調(diào)用Animated.timing
沧奴,animatedValue
作為第一個參數(shù)傳遞,配置對象
作為第二個參數(shù)长窄,一個設(shè)置最終動畫值
滔吠,一個設(shè)置持續(xù)時間
,一個設(shè)置緩動效果
- 在
render
中挠日,我們用interpolate
方法創(chuàng)建marginLeft
對象疮绷,包含inputRange
和outputRange
數(shù)組,我們使用此對象作為UI
中message
的style
屬性 - 我們使用
Animated.div
替代默認的div
- 我們將
animatedValue
和marginLeft
作為Animated.div
的style
屬性
Velocity React
Velocity React
是基于已經(jīng)存在的 Velocity
建立起來的
贊同者
:上手容易嚣潜,API
簡單明了冬骚,相對其他庫更易于掌握
反對者
:有些不得不克服的問題,比如 componentDidMount
后動畫并沒有真正地起作用等懂算,而且只冻,它不跨平臺
下面是一個森破
的示例
<VelocityComponent
animation={{ opacity: this.state.showSubComponent ? 1 : 0 }}
duration={500}
>
<MySubComponent/>
</VelocityComponent>
首先還是要用 yarn
或 npm
安裝它
yarn add velocity-react
在下面的實例中,我們將創(chuàng)建一個很酷的動畫輸入
import { VelocityComponent } from 'velocity-react';
const VelocityLetter = ({ letter }) => (
<VelocityComponent
runOnMount
animation={{ opacity: 1, marginTop: 0 }}
duration={500}
>
<p style={styles.letter}>{letter}</p>
</VelocityComponent>
)
class VelocityApp extends Component {
state = {
letters: [],
}
onChange = (e) => {
const letters = e.target.value.split('');
const arr = [];
letters.forEach((l, i) => {
arr.push(<VelocityLetter letter={l} />)
});
this.setState({ letters: arr });
}
render() {
return (
<div className="App">
<div className="container">
<input onChange={this.onChange} style={styles.input} />
<div style={styles.letters}>
{
this.state.letters
}
</div>
</div>
</div>
);
}
}
const styles = {
input: {
marginBottom: 20,
padding: 8,
width: 200,
height: 40,
fontSize: 22,
backgroundColor: '#ddd',
border: 'none',
outline: 'none',
},
letters: {
display: 'flex',
height: 140,
},
letter: {
marginTop: 100,
fontSize: 22,
whiteSpace: 'pre',
opacity: 0,
}
}
- 從
velocity-react
中 importVelocityComponent
- 我們要創(chuàng)建一個
可重復(fù)
使用的組件來滿足每個letter
的動畫 - 在這個組件中计技,我們將
animation
的opacity
設(shè)為1
喜德,marginTop
設(shè)為0
,這些值代表著傳入子組件的重寫值
垮媒,即當組件被創(chuàng)建時舍悯,組件的opacity
會由初始的0
變?yōu)?1
航棱,marginTop
會由初始的100
變?yōu)?0
,我們還設(shè)置了500 ms
的持續(xù)時間萌衬,最后值得一提的是runOnMount
屬性饮醇,它的意思是在組件掛載
或創(chuàng)建
完后執(zhí)行該動畫 - 其中的
onChange
方法會獲取用戶的每次輸入,并創(chuàng)建一個由VelocityLetter
組成的新數(shù)組 - 在
render
中秕豫,我們就使用該數(shù)組在UI
中渲染letters
總結(jié)
總的來說朴艰,基本的動畫,我會選擇 JS style
馁蒂,復(fù)雜的動畫呵晚,我更偏向 React Motion
。而對于 React Native
沫屡,我還是堅持使用 Animated
饵隙,一旦 Animated
成熟,在 Web
中可能也會投入使用沮脖,目前金矛,我真的很享受 React Motion