之前一直投入在React Native
中,寫動畫的時候不是用CSS 中的 transitions / animations
,就是依賴像GreenSock
這樣的庫,最近轉向Web
,在Tweet
獲得不少大佬關於React Web 動畫
的迴應,因而決定分享給你們,若有其餘看法,很是歡迎在下面評論
中交流
如下即是本文要分享的建立 React 動畫
的幾種方式javascript
下面,勒次個特斯大特一特css
給元素添加 class
是最簡單,最多見的書寫方式。若是你的 app
正在使用 CSS
,那麼這將是你最愉快的選擇java
贊同者
: 咱們只需修改 opacity
和 transform
這樣的屬性,就可構建基本的動畫,並且,在組件中,咱們能夠很是容易地經過 state
去更新這些值react
反對者
:這種方式並不跨平臺
,在 React Native
中就不適用,並且,對於較複雜的動畫,這種方式難以控制
spring
接下來,咱們經過一個實例來體驗一下這種建立方式:當 input focus 的時候,咱們增長它的寬度
npm
首先,咱們要建立兩個 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 組件
app
在此,推薦一個 在線的 React VS Code IDE,真的很強大,讀者不想構建本身的 React app
,能夠在其中檢驗如下代碼的正確性dom
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
,咱們經過更新該值
來建立咱們的動畫componentDidMount
中,咱們添加兩個監聽器
,一個 focus
,一個 blur
,指定的回調函數都
是 focus
focus
方法會獲取以前 focused
的值,並負責切換
該值render
中,咱們經過 state
來改變 input
的 classNames
,從而實現咱們的動畫JavaScipt styles
跟 CSS 中的 classes
相似,在 JS
文件中,咱們就能夠擁有全部邏輯
贊同者
:跟 CSS 動畫
同樣,且它的表現更加清晰。它也不失爲一個好方法,能夠沒必要依賴任何 CSS
反對者
:跟 CSS 動畫
同樣,也是不跨平臺
的,且動畫一旦複雜,也難以控制
在下面的實例中,咱們將建立一個 input
,當用戶輸入時,咱們將一個 button
從 disable
轉變爲 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
的值disabled
的值,肯定是否將 buttonEnabled
添加到 button
中React Motion
是 Cheng Lou 書寫的一個很是不錯的開源項目。它的思想是你能夠對Motion 組件
進行簡單的樣式設置
,而後你就能夠在回調函數
中經過這些值,享受動畫帶來的樂趣
對於絕大多數的動畫組件,咱們每每不但願對動畫屬性
(寬高、顏色等)的變化時間作硬編碼
處理,react-motion
提供的 spring
函數就是用來解決這一需求的,它能夠逼真地模仿真實的物理效果,也就是咱們常見的各種緩動效果
下面是一個森破
的示例
<Motion style={{ x: spring(this.state.x) }}> { ({ x }) => <div style={{ transform: `translateX(${x}px)` }} /> } </Motion>
這是官方提供的幾個 demo
,真的能夠是不看不知道,一看嚇一跳
贊同者
:React Motion
能夠在 React Web
中使用,也能夠在 React Native
中使用,由於它是跨平臺的。其中的 spring
概念最開始對我來講感受挺陌生,然而上手以後,發現它真的很神奇
,而且,它有很詳細的 API
反對者
:在某些狀況下,他不如純 CSS / JS 動畫
,雖然它有不錯的 API
,容易上手,但也須要學習成本
爲了使用它,首先咱們要用 yarn
或 npm
安裝它
yarn add react-motion
在下面的實例中,咱們將建立一個 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
中 import Motion
和 spring
height
的 state
,初始值爲 38
,表明 menu
的高度animate
方法設置 menu
的 height
,若是 原 height
爲 38
,則設置 新 height
爲 233
,若是 原 height
爲 233
,則設置 新 height
爲 38
render
中,咱們使用 Motion 組件
包裝整個 p 標籤
列表,將 this.state.height
的當前值設爲組件的 height
,而後在組件的回調函數
中使用該值做爲整個下拉的高度this.animate
切換下拉的高度Animated
是基於 React Native
使用的同一個動畫庫創建起來的
它背後的思想是建立聲明式動畫
,經過傳遞配置對象來控制動畫
贊同者
:跨平臺
,它在 React Native
中已經很是穩定,若是你在 React Native
中使用過,那麼你將不用再重複學習。其中的 interpolate
是一個神奇的插值函數,咱們將在下面看到
反對者
:基於 Twitter
的交流,它目前貌似不是 100%
的穩定,在老的瀏覽器中的,存在前綴
和性能
的問題,並且,它也有學習成本
爲了使用 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> ); } } 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
中 import Animated
和 Easing
new Animated.Value(0)
建立一個值爲 0
的類屬性 - animatedValue
animate
方法,處理全部的動畫,首先經過 this.animatedValue.setValue(0)
初始化動畫值,實現的效果就是每次從新執行
該動畫,而後調用 Animated.timing
,animatedValue
做爲第一個參數傳遞,配置對象
做爲第二個參數,一個設置最終動畫值
,一個設置持續時間
,一個設置緩動效果
render
中,咱們用 interpolate
方法建立 marginLeft
對象,包含 inputRange
和 outputRange
數組,咱們使用此對象做爲 UI
中 message
的 style
屬性Animated.div
替代默認的 div
animatedValue
和 marginLeft
做爲 Animated.div
的 style
屬性Velocity React
是基於已經存在的 Velocity
創建起來的
贊同者
:上手容易,API
簡單明瞭,相對其餘庫更易於掌握
反對者
:有些不得不克服的問題,好比 componentDidMount
後動畫並無真正地起做用等,並且,它不跨平臺
下面是一個森破
的示例
<VelocityComponent animation={{ opacity: this.state.showSubComponent ? 1 : 0 }} duration={500} > <MySubComponent/> </VelocityComponent>
首先仍是要用 yarn
或 npm
安裝它
yarn add velocity-react
在下面的實例中,咱們將建立一個很酷的動畫輸入
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
中 import VelocityComponent
可重複
使用的組件來知足每一個 letter
的動畫animation
的 opacity
設爲 1
,marginTop
設爲 0
,這些值表明着傳入子組件的重寫值
,即當組件被建立時,組件的 opacity
會由初始的 0
變爲 1
,marginTop
會由初始的 100
變爲 0
,咱們還設置了 500 ms
的持續時間,最後值得一提的是 runOnMount
屬性,它的意思是在組件 掛載
或 建立
完後執行該動畫onChange
方法會獲取用戶的每次輸入,並建立一個由 VelocityLetter
組成的新數組render
中,咱們就使用該數組在 UI
中渲染 letters
總的來講,基本的動畫,我會選擇 JS style
,複雜的動畫,我更偏向 React Motion
。而對於 React Native
,我仍是堅持使用 Animated
,一旦 Animated
成熟,在 Web
中可能也會投入使用,目前,我真的很享受 React Motion