創(chuàng)建 React 動畫的五種方式

簡評:這篇文章將介紹五種可選方式來創(chuàng)建 React Web 動畫,其中有一些是跨平臺的(可以支持 React Native )

1. 基于 React 組件狀態(tài)的 CSS 動畫

對于我來說最基礎(chǔ)也是最顯然的來創(chuàng)建動畫就是使用 CSS 類的屬性并通過添加或刪除他們來展現(xiàn)動畫眶俩。如果在你的應(yīng)用中已經(jīng)使用了 CSS测蹲,這是種很好的方式來實現(xiàn)基礎(chǔ)動畫赃额。

缺點:不是跨平臺的(不支持 React Native)通危,依賴于 CSS 和 DOM沈撞,如果需要實現(xiàn)復雜的效果励幼,這種方式會變得難以控制汰寓。

優(yōu)點:高性能。關(guān)于 CSS 動畫苹粟,有一條已知的規(guī)則:除了透明度和變換意外有滑,不要改變?nèi)魏螌傩裕ǔ泻馨舻男阅芮断鳌毛好;跔顟B(tài)更新這些值非常簡單,而且只要簡單地重新渲染我們的組件就能達到平滑變換的效果苛秕。

看個例子:我們將會基于 React 組件使用 CSS 動畫來動畫化一個 input 組件肌访。

首先我們要創(chuàng)建兩個類關(guān)連上我們的 input:

.input {
  transition: width .35s linear;
  outline: none;
  border: none;
  border-radius: 4px;
  padding: 10px;
  font-size: 20px;
  width: 150px;
  background-color: #dddddd;
}

.input-focused {
  width: 240px;
}

我們有一些基礎(chǔ)的屬性,并且我們設(shè)置了 width .35 linear 的變換艇劫,給動畫一些屬性吼驶。

同時 input-focused 類將把寬度從 150 px 改動到 240 px。

現(xiàn)在在我們的 React 應(yīng)用中把他們用起來:

class App extends Component {
  state = {
    focused: false
  }
  componentDidMount() {
    this.input.addEventListener('focus', this.focus);
    this.input.addEventListener('blur', this.focus);
  }
  focus = () => {
    this.setState((state) => ({ focused: !state.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>
    );
  }
}
  1. 我們創(chuàng)建了一個 focused 狀態(tài)并設(shè)為 false。我們將用這個狀態(tài)出發(fā)更新我們動畫化的組件蟹演。

  2. componentDidMount 中风钻,我們添加了兩個監(jiān)聽器,一個監(jiān)聽 blur酒请,一個監(jiān)聽 focus骡技。兩個監(jiān)聽器都能夠調(diào)用 focus 方法。注意到我們正在引用 this.input羞反,這是因為我們使用 ref 方法創(chuàng)建了一個引用布朦,然后把它設(shè)置為一個類屬性。我們在 componentDidMount 中做這些因為在 componentWillMount 時我們還沒有進入 dom苟弛。

  3. focus 方法會檢查上個 focused 狀態(tài)的值喝滞,并基于他的值來觸發(fā)阁将。

  4. 在 render 中膏秫,主要注意的是我們給 input 設(shè)置了 classNames。我們檢查 this.state.focused 是否為 true做盅,如果是缤削,我們會加入 input-focused 類。我們創(chuàng)建了一個數(shù)組吹榴,并調(diào)用 .join('') 作為一個可用的 className亭敢。

2. 基于 React 組件狀態(tài)的 JS 樣式動畫

用 JS 樣式來創(chuàng)建動畫的方式和用 CSS 類有點相似。好處是你可以獲得相同的性能图筹,但你不用依賴 CSS 類帅刀,你可以在 JS 文件中寫上所有的邏輯。

優(yōu)點:像 CSS 動畫远剩,好處是性能杠杠的扣溺。同樣也是種很好的方式,因為你不需要依賴于任何 CSS 文件瓜晤。

缺點:同樣和 CSS 動畫一樣锥余,不是跨平臺的(不支持 React Native),依賴于 CSS 和 DOM痢掠,如果要創(chuàng)造復雜的動畫驱犹,會變得難以控制。

這個例子中足画,我們會創(chuàng)建一個輸入框雄驹,當用戶輸入時,會變成可點擊和不可點擊的狀態(tài)淹辞,給予用戶反饋荠医。

class App extends Component {
  state = {
    disabled: true,
  }
  onChange = (e) => {
    const length = e.target.value.length;
    if (length >= 4) {
      this.setState(() => ({ disabled: false }))
    } else if (!this.state.disabled) {
      this.setState(() => ({ disabled: true }))
    }
  }
  render() {
    const label = this.state.disabled ? 'Disabled' : 'Submit';
    return (
      <div className="App">
        <button
          style={Object.assign({}, styles.button, !this.state.disabled && styles.buttonEnabled)}
          disabled={this.state.disabled}
        >{label}</button>
        <input
          style={styles.input}
          onChange={this.onChange}
        />
      </div>
    );
  }
}

const styles = {
  input: {
    width: 200,
    outline: 'none',
    fontSize: 20,
    padding: 10,
    border: 'none',
    backgroundColor: '#ddd',
    marginTop: 10,
  },
  button: {
    width: 180,
    height: 50,
    border: 'none',
    borderRadius: 4,
    fontSize: 20,
    cursor: 'pointer',
    transition: '.25s all',
  },
  buttonEnabled: {
    backgroundColor: '#ffc107',
    width: 220,
  }
}
  1. 初始化一個 disabled 狀態(tài),設(shè)為 true

  2. onChange 方法綁定了 input彬向,我們會檢查輸入了多少個字符兼贡。如果有 4 個或以上,我們將 disabled 設(shè)為 false娃胆,否則它還沒被設(shè)為 true 的話那就設(shè)為 true遍希。

  3. 按鈕元素的樣式屬性將會決定添加動畫類 buttonEnabled 與否,取決于 this.state.disabled的值里烦。

  4. 按鈕的樣式有一個 .25s all 的變換凿蒜,因為我們想讓 backgroundColorwidth 屬性同時動畫化。

3. React Motion

React MotionCheng Lou(華裔 FB 大神胁黑,不確定國籍)寫的很棒的庫废封,他在動畫方面工作超過 2 年了,包括 React Web 和 React Native丧蘸。他在 2015 年的 React Europe 上發(fā)表了一個很棒的關(guān)于討論動畫的演講漂洋。

React Motion 背后的思想是它將 API 引用的內(nèi)容作為 “Spring”,這是一個非常穩(wěn)定的基礎(chǔ)動畫配置力喷,在大多數(shù)情況下工作良好刽漂,同時也是可配置的。它不依賴于時間弟孟,所以當你想要取消/停止/撤銷一個動畫或者在你的應(yīng)用中使用可變維度的時候會更好用贝咙。

React Motion 的用法是你在一個 React Motion 組件中設(shè)置一個樣式配置,然后你會收到一個包含這些樣式值的回調(diào)函數(shù)拂募⊥バ桑基礎(chǔ)的例子看起來是這樣的:

<Motion style={{ x: spring(this.state.x) }}>
  {
    ({ x }) =>
      <div style={{ transform: `translateX(${x}px)` }} />
  }
</Motion>

優(yōu)點:React Motion 是跨平臺的。spring 的概念一開始覺得很奇怪陈症,但在真正使用后會覺得它是個天才的想法蔼水,并且將所有的東西都處理得非常好。同時 API 設(shè)計的也很棒爬凑!

缺點:我注意到在某些情況下它的性能不如純 CSS/JS 樣式動畫徙缴。盡管 API 很容易上手,但你還是要花時間去學習嘁信。

要使用這個庫于样,你可以通過 npm 或者 yarn 安裝:
yarn add react-motion

這個例子中,我們將創(chuàng)建一個下拉菜單潘靖,按鈕按下會觸發(fā)菜單展開動畫穿剖。

import React, { Component } from 'react';

import {Motion, spring} from 'react-motion';

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: {
    overflow: 'hidden',
    border: '2px solid #ddd',
    width: 300,
    marginTop: 20,
  },
  selection: {
    padding: 10,
    margin: 0,
    borderBottom: '1px solid #ededed'
  },
  button: {
    justifyContent: 'center',
    alignItems: 'center',
    display: 'flex',
    cursor: 'pointer',
    width: 200,
    height: 45,
    border: 'none',
    borderRadius: 4,
    backgroundColor: '#ffc107',
  },
}
  1. 我們從 react-motion 中導入了 Motionspring

  2. height 狀態(tài)初始化為 38. 我們將會用它來動畫化菜單的高度卦溢。

  3. animate 方法會檢查當前高度值糊余,如果是 38 就改為 250秀又,否則將它重置為 38.

  4. render 中,我們使用 Motion 組件包裹了一個 p 標簽列表贬芥。我們設(shè)置了 Motion 樣式屬性吐辙,傳遞了 this.state.height 作為高度值。現(xiàn)在蘸劈,高度將在 Motion 組件的回調(diào)中返回昏苏。我們可以在回調(diào)中用這個高度來設(shè)置包裹著列表的 div 樣式。

  5. 當按鈕點擊時威沫,調(diào)用了 this.animate 觸發(fā)高度屬性變化贤惯。

4. Animated

Animated 庫基于在 React Native 中使用的同名動畫庫。

Animated 的基本思想是你可以創(chuàng)建聲明式動畫棒掠,并傳遞配置對象來控制在動畫中發(fā)生的事情孵构。

優(yōu)點:跨平臺。在 React Native 中也非常穩(wěn)定烟很,所以如果你在 Web 中學習了就不用再學一次了颈墅。Animated 允許我們通過 interpolate 方法插入一個單一的值到多個樣式中。我們還可以利用多個 Easing 屬性的優(yōu)勢溯职,開箱即用精盅。

缺點:根據(jù)我通過 Twitter 的交流帽哑,看起來這個庫在 Web 上還沒有達到 100% 穩(wěn)定谜酒,像為老版本瀏覽器自動添加前綴的問題及一些性能問題。如果你還沒有從 React Native 中學過妻枕,同樣需要花費時間學習僻族。

可以通過 npm 或 yarn 安裝:
yarn add animated

在這個例子中,我們將模仿點擊訂閱后彈出一條消息屡谐。

import Animated from 'animated/lib/targets/react-dom';
import Easing from 'animated/lib/Easing';

class App 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>
    );
  }
}
  1. animated 中導入 AnimatedEasing述么。注意到我們沒有直接導入整個庫,但我們實際上直接引入了 react-domEasing APIs愕掏。

  2. 創(chuàng)建了一個 animatedValue 類屬性度秘,通過調(diào)用 *new Animated.Value(0) *設(shè)為 0.

  3. 創(chuàng)建了一個 animated 方法。這個方法控制動畫的發(fā)生饵撑,我們稍后將使用這個動畫值并使用 interpolate 方法創(chuàng)建其他動畫值剑梳。在這個方法中,我們通過調(diào)用 this.animatedValue.setValue(0) 將動畫值設(shè)為 0滑潘,這樣每次這個函數(shù)被調(diào)用時都能觸發(fā)動畫垢乙。然后調(diào)用了 Animated.timing, 傳遞動畫值作為第一個參數(shù)(this.animatedValued),第二個參數(shù)是一個配置對象语卤。這個配置對象有個 toValue 屬性追逮,將成為最終的動畫值酪刀。duration 是動畫的時長,easing 屬性將聲明動畫的類型(我們選擇了 Elastic)钮孵。

  4. 在我們的 render 方法中骂倘,我們首先通過使用 interpolate 方法創(chuàng)建了一個可動畫化的值叫 marginLeftinterpolate 接受一個配置對象包含 inputRange 數(shù)組和一個 outputRange 數(shù)組巴席,將會基于輸入和輸出創(chuàng)建一個新值稠茂。我們用這個值來設(shè)置 UI 中消息的 marginLeft 屬性。

  5. Animated.div 取代常規(guī)的 div情妖。

  6. 我們用 animatedValuemarginLeft 屬性為* Animated.div* 添加樣式睬关,用 animatedValue 設(shè)置 opacitymarginLeft 設(shè)置 marginLeft毡证。

5. Velocity React

Velocity React 基于已有的 Velocity DOM 庫电爹。

用過之后,我的感覺是它的 API 像 Animated 和 React Motion 的結(jié)合體料睛∝ぢ幔總體來說,他看起來是一個有趣的庫恤煞,我會在 web 上做動畫的時候想到它屎勘,但我想的比較多的是 React Motion 和 Animated。

優(yōu)點:非常容易上手居扒。API 相當簡單明了概漱,比 React Motion 更容易掌握。

缺點:學它的時候有幾個瑕疵必須要克服喜喂,包括不在 componentDidMount 中運行動畫瓤摧,而是必須聲明 runOnMount 屬性。同樣不是跨平臺的玉吁。

基礎(chǔ)的 API 看起來像這樣:

<VelocityComponent
  animation={{ opacity: this.state.showSubComponent ? 1 : 0 }}      
  duration={500}
>
  <MySubComponent/>
</VelocityComponent>

可以通過 npm 或 yarn 來安裝:
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 App 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: {
    height: 40,
    backgroundColor: '#ddd',
    width: 200,
    border: 'none',
    outline: 'none',
    marginBottom: 20,
    fontSize: 22,
    padding: 8,
  },
  letters: {
    display: 'flex',
    height: 140,
  },
  letter: {
    opacity: 0,
    marginTop: 100,
    fontSize: 22,
    whiteSpace: 'pre',
  }
}
  1. velocity-react 中導入 VelocityComponent照弥。

  2. 我們創(chuàng)建了一個可以重用的組件來保存每個要動畫化的字符。

  3. 在這個組件中进副,我們設(shè)置動畫的 opacity 為 1这揣,marginTop 為 0. 子組件會根據(jù)我們傳入的值重寫這些值。這個例子中影斑,<p> 的初始 opacity 為 0给赞, marginTop 為 100. 當組件被創(chuàng)建時,我們將 opacity 從 0 設(shè)為 1鸥昏,將 marginTop 從 100 設(shè)為 0. 我們同時設(shè)置了時長為 500 毫秒塞俱,以及一個 runOnMount 屬性,聲明我們想讓動畫在組件被安裝或者創(chuàng)建時運行吏垮。

  4. renderinput 元素回調(diào)了一個 onChange 方法障涯。onChange 將會從 input 中得到每個字符罐旗,并使用上面的 VelocityLetter 組件創(chuàng)建了一個新的數(shù)組。

  5. render 中唯蝶,我們用這個數(shù)組來渲染字符到 UI 中九秀。

總結(jié)

總體來說,我會適應(yīng) JS 樣式動畫來做基礎(chǔ)動畫粘我,React Motion 來做任何 Web 上瘋狂的東西鼓蜒。至于 React Native,我堅持使用 Animated征字。盡管我現(xiàn)在正在開始享受使用 React Motion都弹,一旦 Animated 變得更加成熟,我可能在 web 上也會切換到 Animated匙姜!

原文鏈接:React Animations in Depth
推薦閱讀:教你用 Web Speech API 和 Node.js 來創(chuàng)建一個簡單的 AI 聊天機器人

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末畅厢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子氮昧,更是在濱河造成了極大的恐慌框杜,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件袖肥,死亡現(xiàn)場離奇詭異咪辱,居然都是意外死亡,警方通過查閱死者的電腦和手機椎组,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門油狂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人庐杨,你說我怎么就攤上這事选调〖泄” “怎么了灵份?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長哮洽。 經(jīng)常有香客問我填渠,道長,這世上最難降的妖魔是什么鸟辅? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任氛什,我火速辦了婚禮,結(jié)果婚禮上匪凉,老公的妹妹穿的比我還像新娘枪眉。我一直安慰自己,他們只是感情好再层,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布贸铜。 她就那樣靜靜地躺著堡纬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪蒿秦。 梳的紋絲不亂的頭發(fā)上烤镐,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天,我揣著相機與錄音棍鳖,去河邊找鬼炮叶。 笑死,一個胖子當著我的面吹牛渡处,可吹牛的內(nèi)容都是我干的镜悉。 我是一名探鬼主播,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼医瘫,長吁一口氣:“原來是場噩夢啊……” “哼积瞒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起登下,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤茫孔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后被芳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缰贝,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年畔濒,在試婚紗的時候發(fā)現(xiàn)自己被綠了剩晴。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡侵状,死狀恐怖赞弥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情趣兄,我是刑警寧澤绽左,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站艇潭,受9級特大地震影響拼窥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蹋凝,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一鲁纠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鳍寂,春花似錦改含、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刃唤。三九已至,卻和暖如春白群,著一層夾襖步出監(jiān)牢的瞬間尚胞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工帜慢, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留笼裳,地道東北人。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓粱玲,卻偏偏與公主長得像躬柬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子抽减,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 以前一直投入在 React Native 中卵沉,寫動畫的時候不是用 CSS 中的 transitions / ani...
    楓上霧棋閱讀 934評論 0 8
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫颠锉、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,058評論 4 62
  • 記憶中第一次上網(wǎng)是在初中史汗。當時琼掠,只聽聞某年級的學生半夜翻墻跑到網(wǎng)吧通宵或者某某同學沉迷于網(wǎng)吧荒廢學業(yè),卻未曾到網(wǎng)吧...
    萬卷無書閱讀 266評論 0 0
  • 楊柳岸 曉風月 自古情多是傷離別 灞橋邊 難眠夜 望盡紅塵悲歌 知是故人遠踏雪 深夜煎茶共邀月 遙憶當年事 天寒心...
    書云公子閱讀 532評論 0 1