讓React應用「動」起來

WEB應用中動畫很重要

不論是web應用仍是原生應用,也不管是PC端應用仍是移動端應用,動畫都扮演了一個重要的角色。css

儘管動畫並不會添加應用的實際動能,但一個好的動畫,一個流暢且優雅,選擇在恰當時機出現的動畫,能爲應用增色很多,能很好的引導用戶進行下一步操做,讓應用的場景切換更合理。一個小小的細節動畫,就能幾個層次的提高應用的用戶體驗。react

舉個簡單的例子,應用中最多見的頁面間切換,若是缺乏切換動畫,那就會是這個樣子:git

image

很是生硬,頁面的出現顯得很是突兀。做爲對比,咱們能夠看下添加了動畫的頁面切換是什麼樣子的呢:github

image

增長了用戶預期,也能較好的提示用戶頁面的層級關係。web

再舉個彈窗的例子,先放兩個對比圖,左邊是無動畫的效果,右邊是添加完動畫的效果:spring

dialog_no_animation.gif?v=1dialog_animation.gif?v=1

動畫雖然沒有添加什麼實際可見的功能,可是經過對比,不難發現,動畫的添加,讓彈窗的出現顯得平滑天然,讓頁面的場景替換有一個過程,減小突兀感,讓用戶體驗感覺增色很多不是嗎?segmentfault

固然有可能由於錄製的問題,動畫效果不是很明顯,你可能有不一樣的見解。

動畫實現的基本原理

web應用的基本骨架是DOM,正是一個個的DOM節點,構建出web應用,換句話說,就是web應用呈現出來的樣子是DOM決定的(固然這裏把CSS樣式,概括爲了DOM的一部分)。因此動畫的實現,從本質上來說,就是操做DOM:讓DOM在不一樣的時間節點,在不一樣的位置、有不一樣的大小、透明度、呈現不一樣的背景色等,而且讓這種變化連續起來,則構成了咱們能觀察到的動畫。框架

基於web動畫實現的基本原理,在咱們直接操做DOM的時代,實現動畫相對咱們來講,很是直觀——只要知道怎麼操做DOM便可。好比須要實現一個黑色背景的div方塊,1s內從離左邊距100px的位置,移動到離左邊距200px的位置,則咱們只須要每秒控制該div的left值增長(200-100)/60px便可實現一個勻速的動畫效果。怎麼樣,很簡單吧。函數

上述動畫實現的方式,咱們稱爲JS動畫。是由JS腳本邏輯,動態的改變DOM的CSS屬性值。性能

CSS3中,添加了動畫的實現的方案,因此web中第二種動畫實現,被咱們稱爲CSS動畫。CSS動畫,最主要的幾個CSS屬性是: transition,transform,animation,因爲與本文的主題不是密切相關,此處就不作詳細介紹,有興趣能夠自行搜索相關文章查閱。

MV*模式下的動畫實現

這裏的MV*模式咱們不展開說,特指React中動畫的實現。React因爲加入了虛擬DOM等諸多特性,而且其開發模式讓開發者不須要或者不推薦直接接觸到真實的DOM結構。因此其動畫實現,與常規的開發方式下的動畫實現,存在必定得差別。

動畫的實現最終必定是落地到操做DOM,MV*模式的框架則是數據驅動DOM的展現。若是咱們由於動畫實現的須要,對DOM的操做出現問題,勢必會影響到應用的相關操做。咱們先不去深刻探討怎麼解決這個問題,先看看React官方和社區對這個問題是怎麼解決的,讓咱們能先給咱們的React應用添加上須要的動畫。

由淺及深,咱們先學會怎麼使用,再去了解內部的實現原理,從而根據應用自身需求,能實現自定義的動畫,能實現更爲複雜的交互動畫等。

React中咱們怎麼添加動畫

React的動畫庫中,比較經常使用的是react-addons-css-transition-groupreact-addons-transition-group 以及react-motion 。其中, react-addons-css-transition-groupreact-addons-transition-groupHigh-Level API庫,react-addons-css-transtion-group是基於react-addons-transition-group的上層封裝。目前react-addons-css-transition-groupreact-addons-transition-group合併成一個庫,叫react-transition-group

react-transition-group@v1.x版本中的API, 基本保持與兩個單獨庫的API形式一致,但@v2.x版本中的API變化較大,並不能徹底切換,這個須要注意。本文的示例是以獨立庫,也就是類react-transtion-group@v1.xAPI提供的。

另外一個經常使用的React動畫庫是react-motion 。該庫擁有很是棒的特性,可以建立出很是細膩的動畫,接着往下看,會介紹下基本的使用,而後參照其官方文檔,相信能夠實現出大多數你想要的動畫的。

1. ReactCSSTransitionGroup

react-addons-css-transition-group,通常稱其export的組件爲ReactCSSTransitionGroup,它提供一種聲明的方式來定義CSS動畫ReactCSSTransitionGroup子組件必須大於或等於1個,不能爲空。

ReactCSSTranstionGroup組件暴露的屬性有:

  • transitionAppear/transitionEnter/transitionLeave: Boolean 類型,標識是否開啓動畫
  • transitionAppearTimeout/transitionEnterTimeout/transitionLeaveTimeout: 定義各階段動畫的時長
  • transitionName:自定義各階段動畫的 CSS 樣式名
  • component: ReactCSSTranstion以什麼組件包裹(wrap)子組件,默認爲span,能夠是React Element 。

咱們以 todo-list 爲例,看看ReactCSSTransitionGroup怎麼使用。因爲篇幅限制,todo-list 相關的業務代碼忽略,能夠在這裏查看完整代碼。如下是動畫部分代碼:

index.js:

import React , { Component } from 'react' ;
import ReactCSSTranstionGroup from 'react-addons-css-transition-group' ;

export default class App extends Component{
    ... ,
    
    render(){
        const { items } = this.state ;
        
        return (
            <div>
                ... ,
                <div className="list">
                    <ReactCSSTransitionGroup
                        transitionName="example"
                        transitionEnterTimeout={500}
                        transitionLeaveTimeout={300}
                        component="div"
                    >
                    {
                        items.map((item)=>(
                            <div key={item} className="item">{item}</div>
                        ))   
                    }
                    </ReactCSSTransitionGroup>
                </div>
            </div>
        )
        
    }
    
}

style.css:

.example-enter {
    opacity: 0.01;
}

.example-enter.example-enter-active {
    opacity: 1;
    transition: opacity 500ms ease-in;
}

.example-leave {
    opacity: 1;
}

.example-leave.example-leave-active {
    opacity: 0.01;
    transition: opacity 300ms ease-in;
}

有兩點須要注意:

  • 每一個子組件必須有key,這樣ReactCSSTrasntionGroup才能正確的mountingunmounting子組件。
  • 自定義的動畫時長,須要與CSS樣式中定義的動畫時長對應上。

關於react-addons-css-transition-group,知乎這篇文章CSS 動畫及其在 React 中的應用有較爲詳細的介紹。

2. ReactTransitionGroup

react-addons-transition-groupreact-addons-css-transition-grouplow-level API,其提供動畫執行中須要的各生命週期函數:

  • componentWillAppear(): componentDidMount時執行, 渲染TransitionGroup時執行而且只會執行一次。
  • componentDidAppear(): 傳給componentWillAppear的callback執行後執行。
  • componentWillEnter(): componentDidMount時執行,子組件添加進TransitionGroup時執行。
  • componentDidEnter(): 傳給componentWillEnter的callback執行後執行。
  • componentWillLeave(): 子組件從TransitionGroup中移除時執行,Though the child has been removed, TransitionGroup will keep it in the DOM until callback is called.
  • componentDidLeave(): componentWillLeave的callback執行後執行。一般狀況下與ComponentWillUnmount的時機一致。

那咱們看看,一樣以 todo-list 爲例, ReactTransitionGroup須要怎麼作呢?

index.js:

import React,{ Component } from 'react' ;
import ReactTransitionGroup from 'react-addons-transition-group' ;

class Item extends Component{
    
    // 獲取組件真實DOM
    getRef(ref){
        this.ref=ref ;
    }
    
    componentWillEnter(callback){
        this.ref.classList.add('example-enter') ;
        setTimeout(()=>{
          callback() ;
        },500) ;
    }
    
    componentDidEnter(){
        this.ref.classList.add('example-enter-active') ;
    }
    
    componentWillLeave(){
        this.ref.classList.remove('example-enter','example-enter-active') ;
        this.ref.classList.add('example-leave-active','example-leave') ;
        setTimeout(()=>{
          callback() ;
        },300) ;
    }

    render(){
        const { text , onRemove } = this.props ;
        return (
            <div ref={this.getRef} onClick={onRemove} className={styles.item}>
                {text}
            </div>
        )
    }
    
}

export class App extends Component{
    ... ,
    
    render(){
        const { items } = this.state ;
        
        return (
            <div>
                ... ,
                <div className="list">
                {
                    items.map((item,i)=>(
                        <Item key={item} text={item} ...otherProps />
                    ))
                }
                </div>
            </div>
        )
        
    }
}

style.css:

.example-enter {
    opacity: 0.01;
}

.example-enter.example-enter-active {
    opacity: 1;
    transition: opacity 500ms ease-in;
}

.example-leave {
    opacity: 1;
}

.example-leave.example-leave-active {
    opacity: 0.01;
    transition: opacity 300ms ease-in;
}

相比於ReactCSSTransitionGroup,咱們須要本身去控制組件的動畫生命週期,增長了必定的複雜度,可是對自動以動畫,又能提供更好的靈活度。能夠根據業務場景,選擇合適的Group,去實現咱們的需求。

一般狀況下,咱們用ReactCSSTransitionGroup就能知足較多的業務場景了,而且從實現上會容易不少。

3. ReactMotion

react-motion提供了5個API接口:

  • spring: 動畫生成方法
  • Motion: React 組件
  • StaggeredMotion: React 組件
  • TransitionMotion: React 組件
  • presets: spring方法的配置項

跟其餘React動畫庫同樣,react-motion也提供React組件去包裹須要動畫的業務組件。其中:

  • Motion組件只接受一個children組件
  • StaggeredMotion組件接受一組children組件
  • TranstionMotion組件能夠支持其children組件mountingunmounting定義動畫

仍然是 todo-list 的例子,react-motion的實現也很是簡單:

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

export default class App extends Component{
    ... ,
    
    render(){
        const { items } = this.state ;
        return (
            <div>
                ... ,
                <div className="list">
                {
                    items.map((item)=>{
                        return (
                            <Motion defaultStyle={{opacity:0}} style={{opacity:spring(1)}}>
                            {
                              interpolatingStyle => (
                                <div
                                  key={item}
                                  className="item"
                                  style={interpolatingStyle}
                                >
                                  {item}
                                </div>
                              )
                            }
                            </Motion>
                        )
                    })
                }
                </div>
            </div>
        )
    }
    
}

經過上述簡單的代碼,便可實現每一個Item在mounting的時候漸現的效果。

另外一方面,觀察上述實現,咱們不難發現,react-motion不只僅支持React web應用,它應該也能輕鬆的應用到React-Native中。由於其React組件只是根據提供的defaultStylestyle屬性,生成動畫的數據,業務應用中拿到生成的數據後根據須要添加須要動畫的組件樣式。react-motion在web應用中性能表現較爲可觀,在React-Native應用中的性能表現,有待咱們調研。

除了上述簡單的動畫應用,react-motion在複雜動畫的實現方面,表現很是優越。下面的動圖是react-motion實現的一個動畫演示:

image

這個示例展現了部分react-motion的能力,更多關於react-motion的應用就讓咱們一塊兒去發現吧。

結語

固然React動畫相關的庫還有不少,本文不過多介紹。經過上述對這些庫的使用作簡單介紹,筆者但願經過對它們實現進行分析,讓讀者能更好的理解與掌握,能對React動畫的實現原理和實現方式,有更爲清晰的認識。

可是對相關代碼的研究,深刻度還不足以給讀者朋友分享,因此暫時留坑,後續會將相關源碼的學習,記錄在文檔React動畫的實現原理一文中,並計劃添加從零開始,實現React動畫文章做爲學習成果。若是對React動畫保有興趣,能夠關注這兩篇文章的後續內容。

相關文章
相關標籤/搜索