React Web 動畫的 5 種建立方式,每一種都不簡單

之前一直投入在 React Native 中,寫動畫的時候不是用 CSS 中的 transitions / animations,就是依賴像 GreenSock 這樣的庫,最近轉向 Web,在 Tweet 獲得不少大佬關於 React Web 動畫 的迴應,因而決定分享給你們,若有其餘看法,很是歡迎在下面 評論中交流

如下即是本文要分享的建立 React 動畫 的幾種方式javascript

下面,勒次個特斯大特一特css

CSS animation

給元素添加 class 是最簡單,最多見的書寫方式。若是你的 app 正在使用 CSS,那麼這將是你最愉快的選擇java

贊同者: 咱們只需修改 opacitytransform 這樣的屬性,就可構建基本的動畫,並且,在組件中,咱們能夠很是容易地經過 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>
    );
  }
}
  • 咱們有一個 focusedstate,初始值爲 false,咱們經過更新該值來建立咱們的動畫
  • componentDidMount 中,咱們添加兩個監聽器,一個 focus,一個 blur,指定的回調函數都focus
  • focus 方法會獲取以前 focused 的值,並負責切換該值
  • render 中,咱們經過 state 來改變 inputclassNames,從而實現咱們的動畫

JS Style

JavaScipt stylesCSS 中的 classes 相似,在 JS 文件中,咱們就能夠擁有全部邏輯

贊同者:跟 CSS 動畫 同樣,且它的表現更加清晰。它也不失爲一個好方法,能夠沒必要依賴任何 CSS

反對者:跟 CSS 動畫 同樣,也是不跨平臺的,且動畫一旦複雜,也難以控制

在下面的實例中,咱們將建立一個 input,當用戶輸入時,咱們將一個 buttondisable 轉變爲 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',
  }
}
  • 咱們有一個 disabledstate,初始值爲 true
  • onChange 方法會獲取用戶的輸入,當輸入非空時,就切換 disabled 的值
  • 根據 disabled 的值,肯定是否將 buttonEnabled 添加到 button

React Motion

React MotionCheng 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,容易上手,但也須要學習成本

爲了使用它,首先咱們要用 yarnnpm 安裝它

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 Motionspring
  • 咱們有一個 heightstate,初始值爲 38,表明 menu 的高度
  • animate 方法設置 menuheight,若是 原 height38,則設置 新 height233,若是 原 height233,則設置 新 height38
  • render 中,咱們使用 Motion 組件 包裝整個 p 標籤 列表,將 this.state.height 的當前值設爲組件的 height,而後在組件的回調函數中使用該值做爲整個下拉的高度
  • 當按鈕被點擊時,咱們經過 this.animate 切換下拉的高度

Animated

Animated 是基於 React Native 使用的同一個動畫庫創建起來的

它背後的思想是建立聲明式動畫,經過傳遞配置對象來控制動畫

贊同者跨平臺,它在 React Native 中已經很是穩定,若是你在 React Native 中使用過,那麼你將不用再重複學習。其中的 interpolate 是一個神奇的插值函數,咱們將在下面看到

反對者:基於 Twitter 的交流,它目前貌似不是 100% 的穩定,在老的瀏覽器中的,存在前綴性能的問題,並且,它也有學習成本

爲了使用 Animated,咱們首先仍是要用 yarnnpm 安裝它

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 AnimatedEasing
  • new Animated.Value(0) 建立一個值爲 0 的類屬性 - animatedValue
  • 建立 animate 方法,處理全部的動畫,首先經過 this.animatedValue.setValue(0) 初始化動畫值,實現的效果就是每次從新執行該動畫,而後調用 Animated.timinganimatedValue 做爲第一個參數傳遞,配置對象 做爲第二個參數,一個設置最終動畫值,一個設置持續時間,一個設置緩動效果
  • render 中,咱們用 interpolate 方法建立 marginLeft 對象,包含 inputRangeoutputRange 數組,咱們使用此對象做爲 UImessagestyle 屬性
  • 咱們使用 Animated.div 替代默認的 div
  • 咱們將 animatedValuemarginLeft 做爲 Animated.divstyle 屬性

Velocity React

Velocity React 是基於已經存在的 Velocity 創建起來的

贊同者:上手容易,API 簡單明瞭,相對其餘庫更易於掌握

反對者:有些不得不克服的問題,好比 componentDidMount 後動畫並無真正地起做用等,並且,它不跨平臺

下面是一個森破的示例

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

首先仍是要用 yarnnpm 安裝它

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 的動畫
  • 在這個組件中,咱們將 animationopacity 設爲 1marginTop 設爲 0,這些值表明着傳入子組件的重寫值,即當組件被建立時,組件的 opacity 會由初始的 0 變爲 1marginTop 會由初始的 100 變爲 0,咱們還設置了 500 ms 的持續時間,最後值得一提的是 runOnMount 屬性,它的意思是在組件 掛載建立 完後執行該動畫
  • 其中的 onChange 方法會獲取用戶的每次輸入,並建立一個由 VelocityLetter 組成的新數組
  • render 中,咱們就使用該數組在 UI 中渲染 letters

總結

總的來講,基本的動畫,我會選擇 JS style,複雜的動畫,我更偏向 React Motion。而對於 React Native,我仍是堅持使用 Animated,一旦 Animated 成熟,在 Web 中可能也會投入使用,目前,我真的很享受 React Motion

原文連接: React Animations in Depth (Nader Dabit)

相關文章
相關標籤/搜索