中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

React中怎么實現常見的動畫

發布時間:2021-07-06 14:31:38 來源:億速云 閱讀:305 作者:小新 欄目:web開發

這篇文章主要介紹React中怎么實現常見的動畫,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!

現在,用戶對于前端頁面的要求已經不能滿足于實現功能,更要有顏值,有趣味。除了整體 UI 的美觀,在合適的地方添加合適的動畫效果往往比靜態頁面更具有表現力,達到更自然的效果。比如,一個簡單的 loading 動畫或者頁面切換效果不僅能緩解用戶的等待情緒,甚至通過使用品牌 logo 等形式,默默達到品牌宣傳的效果。

React 作為最近幾年比較流行的前端開發框架,提出了虛擬 DOM 概念,所有 DOM 的變化都先發生在虛擬 DOM 上,通過 DOM diff 來分析網頁的實際變化,然后反映在真實 DOM 上,從而極大地提升網頁性能。然而,在動畫實現方面,React 作為框架并不會直接給組件提供動畫效果,需要開發者自行實現,而傳統 web 動畫大多數都通過直接操作實際 DOM 元素來實現,這在 React 中顯然是不被提倡的。那么,在 React 中動畫都是如何實現的呢?

所有動畫的本質都是連續修改 DOM 元素的一個或者多個屬性,使其產生連貫的變化效果,從而形成動畫。在 React 中實現動畫本質上與傳統 web 動畫一樣,仍然是兩種方式: 通過 css3 動畫實現和通過 js 修改元素屬性。只不過在具體實現時,要更為符合 React 的框架特性,可以概括為幾類:

  1. 基于定時器或 requestAnimationFrame(RAF) 的間隔動畫;

  2. 基于 css3 的簡單動畫;

  3. React 動畫插件 CssTransitionGroup;

  4. 結合 hook 實現復雜動畫;

  5. 其他第三方動畫庫。

一、基于定時器或 RAF 的間隔動畫

最早,動畫的實現都是依靠定時器 setIntervalsetTimeout 或者 requestAnimationFrame (RAF) 直接修改 DOM 元素的屬性。不熟悉 React 特性的開發者可能會習慣性地通過 ref 或者 findDOMNode() 獲取真實的 DOM 節點,直接修改其樣式。然而,通過 ref 直接獲取真實 DOM 并對其操作是是不被提倡使用,應當盡量避免這種操作。

因此,我們需要將定時器或者 RAF 等方法與 DOM 節點屬性通過 state 聯系起來。首先,需要提取出與變化樣式相關的屬性,替換為 state ,然后在合適的生命周期函數中添加定時器或者 requestAnimationFrame 不斷修改 state ,觸發組件更新,從而實現動畫效果。

示例

以一個進度條為例,代碼如下所示:

// 使用requestAnimationFrame改變state
import React, { Component } from 'react';

export default class Progress extends Component { 
  constructor(props) {
    super(props);
    this.state = {
      percent: 10
    };
  }

  increase = () => {
    const percent = this.state.percent;
    const targetPercent = percent >= 90 ? 100 : percent + 10;
    const speed = (targetPercent - percent) / 400;
    let start = null;
    const animate = timestamp => {
      if (!start) start = timestamp;
      const progress = timestamp - start;
      const currentProgress = Math.min(parseInt(speed * progress + percent, 10), targetPercent);
      this.setState({
        percent: currentProgress
      });
      if (currentProgress < targetPercent) {
        window.requestAnimationFrame(animate);
      }
    };
    window.requestAnimationFrame(animate);
  }

  decrease = () => {
    const percent = this.state.percent;
    const targetPercent = percent < 10 ? 0 : percent - 10;
    const speed = (percent - targetPercent) / 400;
    let start = null;
    const animate = timestamp => {
      if (!start) start = timestamp;
      const progress = timestamp - start;
      const currentProgress = Math.max(parseInt(percent - speed * progress, 10), targetPercent);
      this.setState({
          percent: currentProgress
        });
      if (currentProgress > targetPercent) {
        window.requestAnimationFrame(animate);
      }
    };
    window.requestAnimationFrame(animate);
  }

  render() {
    const { percent } = this.state;

    return (
      <div>
        <div className="progress">
          <div className="progress-wrapper" >
            <div className="progress-inner" style = {{width: `${percent}%`}} ></div>
          </div>
          <div className="progress-info" >{percent}%</div>
        </div>
        <div className="btns">
          <button onClick={this.decrease}>-</button>
          <button onClick={this.increase}>+</button>
        </div>
      </div>
    );
  }
}

在示例中,我們在 increasedecrease 函數中構建線性過渡函數 animationrequestAnimationFrame 在瀏覽器每次重繪前執行會執行過渡函數,計算當前進度條 width 屬性并更新該 state ,使得進度條重新渲染。該示例的效果如下所示:

React中怎么實現常見的動畫

這種實現方式在使用 requestAnimationFrame 時性能不錯,完全使用純 js 實現,不依賴于 css,使用定時器時可能出現掉幀卡頓現象。此外,還需要開發者根據速度函數自己計算狀態,比較復雜。

二、基于 css3 的簡單動畫

當 css3 中的 animationtransition 出現和普及后,我們可以輕松地利用 css 實現元素樣式的變化,而不用通過人為計算實時樣式。

示例

我們仍以上面的進度條為例,使用 css3 實現進度條動態效果,代碼如下所示:

import React, { Component } from 'react';

export default class Progress extends Component { 
  constructor(props) {
    super(props);
    this.state = {
      percent: 10
    };
  }

  increase = () => {
    const percent = this.state.percent + 10;
    this.setState({
      percent: percent > 100 ? 100 : percent,
    })
  }

  decrease = () => {
    const percent = this.state.percent - 10;
    this.setState({
      percent: percent < 0 ? 0 : percent,
    })
  }

  render() {
    // 同上例, 省略
    ....
  }
}
.progress-inner {
 transition: width 400ms cubic-bezier(0.08, 0.82, 0.17, 1);
 // 其他樣式同上,省略
 ...
}

在示例中, increasedecrease 函數中不再計算 width ,而是直接設置增減后的寬度。需要注意的是,在 css 樣式中設置了 transition 屬性,該屬性在其指定的 transition-property 發生變化時自動實現樣式的動態變化效果,并且可以設置不同的速度效果的速度曲線。該示例的效果如下圖所示,可以發現,與上一個例子不同的是,右側的進度數據是直接變化為目標數字,沒有具體的變化過程,而進度條的動態效果因為不再是線性變化,效果更為生動。

React中怎么實現常見的動畫

基于 css3 的實現方式具有較高的性能,代碼量少,但是只能依賴于 css 效果,對于復雜動畫也很難實現。此外,通過修改 state 實現動畫效果,只能作用于已經存在于 DOM 樹中的節點。如果想用這種方式為組件添加入場和離場動畫,需要維持至少兩個 state 來實現入場和離場動畫,其中一個 state 用于控制元素是否顯示,另一個 state 用于控制元素在動畫中的變化屬性。在這種情況下,開發者需要花費大量精力來維護組件的動畫邏輯,十分復雜繁瑣。

三、React 動畫插件 CssTransitionGroup

React 曾為開發者提供過動畫插件 react-addons-css-transition-group ,后交由社區維護,形成現在的 react-transition-group ,該插件可以方便地實現組件的入場和離場動畫,使用時需要開發者額外安裝。 react-transition-group 包含 CSSTransitionGroupTransitionGroup 兩個動畫插件,其中,后者是底層 api,前者是后者的進一步封裝,可以較為便捷地實現 css 動畫。

示例

以一個動態增加tab的為例,代碼如下:

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

let uid = 2; 
export default class Tabs extends Component { 
  constructor(props) {
    super(props);
    this.state = {
      activeId: 1,
      tabData: [{
        id: 1,
        panel: '選項1'
      }, {
        id: 2,
        panel: '選項2'
      }]
    };
  }

  addTab = () => {
    // 添加tab代碼
    ...
  }

  deleteTab = (id) => {
    // 刪除tab代碼
    ...
  }

  render() {
    const { tabData, activeId } = this.state;

    const renderTabs = () => {
      return tabData.map((item, index) => {
        return (
          <div
            className={`tab-item${item.id === activeId ? ' tab-item-active' : ''}`}
            key={`tab${item.id}`}
          >
            {item.panel}
            <span className="btns btn-delete" onClick={() => this.deleteTab(item.id)}>?</span>
          </div>
        );
      })
    }

    return (
      <div>
        <div className="tabs" >
          <CSSTransitionGroup
           transitionName="tabs-wrap"
           transitionEnterTimeout={500}
           transitionLeaveTimeout={500}
          >
           {renderTabs()}
          </CSSTransitionGroup>
          <span className="btns btn-add" onClick={this.addTab}>+</span>
        </div>
        <div className="tab-cont">
          cont
        </div>
      </div>
    );
  }
}
/* tab動態增加動畫 */
.tabs-wrap-enter {
 opacity: 0.01;
}

.tabs-wrap-enter.tabs-wrap-enter-active {
 opacity: 1;
 transition: all 500ms ease-in;
}

.tabs-wrap-leave {
 opacity: 1;
}

.tabs-wrap-leave.tabs-wrap-leave-active {
 opacity: 0.01;
 transition: all 500ms ease-in;
}

CSSTransitionGroup 可以為其子節點添加額外的 css 類,然后通過 css 動畫達到入場和離場動畫效果。為了給每個 tab 節點添加動畫效果,需要先將它們包裹在 CSSTransitionGroup 組件中。 當設定 transitionName 屬性為 'tabs-wrapper'transitionEnterTimeout 為400毫秒后,一旦 CSSTransitionGroup 中新增節點,該新增節點會在出現時被添加上 css 類 'tabs-wrapper-enter' ,然后在下一幀時被添加上 css 類 'tabs-wrapper-enter-active' 。由于這兩個 css 類中設定了不同的透明度和 css3 transition 屬性,所以節點實現了透明度由小到大的入場效果。400毫秒后 css 類 'tabs-wrapper-enter''tabs-wrapper-enter-active' 將會同時被移除,節點完成整個入場動畫過程。離場動畫的實現類似于入場動畫,只不過被添加的 css 類名為 'tabs-wrapper-leave''tabs-wrapper-leave-active' 。該示例效果如下圖所示:

React中怎么實現常見的動畫

CSSTransitionGroup 支持以下7個屬性:

React中怎么實現常見的動畫

其中,入場和離場動畫是默認開啟的,使用時需要設置 transitionEnterTimeouttransitionLeaveTimeout 。值得注意的是, CSSTransitionGroup 還提供出現動畫(appear),使用時需要設置 transitionAppearTimeout 。那么,出現動畫和入場動畫有什么區別呢?當設定 transitionAppeartrue 時, CSSTransitionGroup初次渲染 時,會添加一個出現階段。在該階段中, CSSTransitionGroup 的已有子節點都會被相繼添加 css 類 'tabs-wrapper-appear''tabs-wrapper-appear-active' ,實現出現動畫效果。因此, 出現動畫僅適用于 CSSTransitionGroup 在初次渲染時就存在的子節點 ,一旦 CSSTransitionGroup 完成渲染,其子節點就只可能有入場動畫(enter),不可能有出現動畫(appear)。

此外,使用 CSSTransitionGroup 需要注意以下幾點:

  1. CSSTransitionGroup 默認在 DOM 樹中生成一個 span 標簽包裹其子節點,如果想要使用其他 html 標簽,可設定 CSSTransitionGroupcomponent 屬性;

  2. CSSTransitionGroup 的子元素必須添加 key 值才會在節點發生變化時,準確地計算出哪些節點需要添加入場動畫,哪些節點需要添加離場動畫;

  3. CSSTransitionGroup 的動畫效果只作用于直接子節點,不作用于其孫子節點;

  4. 動畫的結束時間不以 css 中 transition-duration 為準,而是以 transitionEnterTimeouttransitionLeaveTimeoutTransitionAppearTimeout 為準,因為某些情況下 transitionend 事件不會被觸發,詳見 MDN transitionend 。

CSSTransitionGroup 實現動畫的優點是:

  1. 簡單易用,可以方便快捷地實現元素的入場和離場動畫;

  2. 與 React 結合,性能比較好。

CSSTransitionGroup 缺點也十分明顯:

  1. 局限于出現動畫,入場動畫和離場動畫;

  2. 由于需要制定 transitionName ,靈活性不夠;

  3. 只能依靠 css 實現簡單的動畫。

四、結合 hook 實現復雜動畫

在實際項目中,可能需要一些更炫酷的動畫效果,這些效果僅依賴于 css3 往往較難實現。此時,我們不妨借助一些成熟的第三方庫,如 jQuery 或 GASP,結合 React 組件中的生命周期鉤子方法 hook 函數,實現復雜動畫效果。除了 React 組件正常的生命周期外, CSSTransitionGroup 的底層 api TransitonGroup 還為其子元素額外提供了一系列特殊的生命周期 hook 函數,在這些 hook 函數中結合第三方動畫庫可以實現豐富的入場、離場動畫效果。

TransisitonGroup 分別提供一下六個生命周期 hook 函數:

  1. componentWillAppear(callback)

  2. componentDidAppear()

  3. componentWillEnter(callback)

  4. componentDidEnter()

  5. componentWillLeave(callback)

  6. componentDidLeave()

它們的觸發時機如圖所示:

React中怎么實現常見的動畫

示例

GASP 是一個 flash 時代發展至今的動畫庫,借鑒視頻幀的概念,特別適合做長時間的序列動畫效果。本文中,我們用 TransitonGroupreact-gsap-enhancer (一個可以將 GSAP 應用于 React 的增強庫)完成一個圖片畫廊,代碼如下:

import React, { Component } from 'react'; 
import { TransitionGroup } from 'react-transition-group'; 
import GSAP from 'react-gsap-enhancer' 
import { TimelineMax, Back, Sine } from 'gsap';

class Photo extends Component { 
  constructor(props) {
    super(props);
  }

  componentWillEnter(callback) {
    this.addAnimation(this.enterAnim, {callback: callback})
  }

  componentWillLeave(callback) {
    this.addAnimation(this.leaveAnim, {callback: callback})
  }

  enterAnim = (utils) => {
    const { id } = this.props;
    return new TimelineMax()
      .from(utils.target, 1, {
        x: `+=${( 4 - id ) * 60}px`,
        autoAlpha: 0,
        onComplete: utils.options.callback,
      }, id * 0.7);
  }

  leaveAnim = (utils) => {
    const { id } = this.props;
    return new TimelineMax()
      .to(utils.target, 0.5, {
        scale: 0,
        ease: Sine.easeOut,
        onComplete: utils.options.callback,
      }, (4 - id) * 0.7);
  }

  render() {
    const { url } = this.props;
    return (
      <div className="photo">
        <img src={url} />
      </div>
    )
  }
}

const WrappedPhoto = GSAP()(Photo);

export default class Gallery extends Component { 
  constructor(props) {
    super(props);
    this.state = {
      show: false,
      photos: [{
        id: 1,
        url: 'http://img4.imgtn.bdimg.com/it/u=1032683424,3204785822&fm=214&gp=0.jpg'
      }, {
        id: 2,
        url: 'http://imgtu.5011.net/uploads/content/20170323/7488001490262119.jpg'
      }, {
        id: 3,
        url: 'http://tupian.enterdesk.com/2014/lxy/2014/12/03/18/10.jpg'
      }, {
        id: 4,
        url: 'http://img4.imgtn.bdimg.com/it/u=360498760,1598118672&fm=27&gp=0.jpg'
      }]
    };
  }

  toggle = () => {
    this.setState({
      show: !this.state.show
    })
  }

  render() {
    const { show, photos } = this.state;

    const renderPhotos = () => {
      return photos.map((item, index) => {
        return <WrappedPhoto id={item.id} url={item.url} key={`photo${item.id}`} />;
      })
    }

    return (
      <div>
        <button onClick={this.toggle}>toggle</button>
        <TransitionGroup component="div">
          {show && renderPhotos()}
        </TransitionGroup>
      </div>
    );
  }
}

在該示例中,我們在子組件 PhotocomponentWillEntercomponentWillLeave 兩個 hook 函數中為每個子組件添加了入場動畫 enterAnim 和 離場動畫 LeaveAnim 。在入場動畫中,使用 TimeLineMax.from(target, duration, vars, delay) 方式建立時間軸動畫,指定了每個子組件的動畫移動距離隨 id 增大而減小,延期時間隨著 id 增大而增大,離場動畫中每個子組件的延期時間隨著 id 增大而減小,從而實現根據組件 id 不同具有不同的動畫效果。實際使用時,你可以根據需求對任一子組件添加不同的效果。該示例的效果如下圖所示:

React中怎么實現常見的動畫

在使用 TransitionGroup 時,在 componentnWillAppear(callback)componentnWillEntercallback)componentnWillLeave(callback) 函數中一定要 在函數邏輯結束后調用 callback ,以保證 TransitionGroup 能正確維護子節點的狀態序列 。

結合 hook 實現動畫可以支持各種復雜動畫,如時間序列動畫等,由于依賴第三方庫,往往動畫效果比較流暢,用戶體驗較好。但是第三方庫的引入,需要開發者額外學習對應的 api,也提升了代碼復雜度。

五、其他第三方動畫庫

此外,還有很多優秀的第三方動畫庫,如 react-motion ,Animated, velocity-react 等,這些動畫庫在使用時也各有千秋。

Animated

Animated 是一個跨平臺的動畫庫,兼容 React 和 React Native。由于在動畫過程中,我們只關心動畫的初始狀態、結束狀態和變化函數,并不關心每個時刻元素屬性的具體值,所以 Animatied 采用聲明式的動畫,通過它提供的特定方法計算 css 對象,并傳入 Animated.div 實現動畫效果。

示例

我們使用 Animated 實現一個圖片翻轉的效果,代碼如下。

import React, { Component } from 'react'; 
import Animated from 'animated/lib/targets/react-dom';

export default class PhotoPreview extends Component { 
  constructor(props) {
    super(props);
    this.state = {
      anim: new Animated.Value(0)
    };
  }

  handleClick = () => {
    const { anim } = this.state;
    anim.stopAnimation(value => {
      Animated.spring(anim, {
        toValue: Math.round(value) + 1
      }).start();
    });
  }

  render() {
    const { anim } = this.state;

    const rotateDegree = anim.interpolate({
      inputRange: [0, 4],
      outputRange: ['0deg', '360deg']
    });

    return (
      <div>
        <button onClick={this.handleClick}>向右翻轉</button>
        <Animated.div
          style={{
            transform: [{
              rotate: rotateDegree
            }]
          }}
          className="preivew-wrapper"
        >
          <img
            alt="img"
            src="http://img4.imgtn.bdimg.com/it/u=1032683424,3204785822&fm=214&gp=0.jpg"
          />
        </Animated.div>
      </div>
    );
  }
}

在該示例中,我們希望實現每點擊一次按鈕,圖片向右旋轉90°。在組件初始化時新建了一個初始值為 0 的 Animated 對象 this.state.anim ,在 render 函數中通過插值函數 interpolate 根據 Animated 對象的當前值計算得到對應的旋轉角度 rotateDegree 。我們假設每點擊一次按鈕, Animated 對象的值加 1,相應地圖像轉動90°,所以,設置 interpolate 函數的輸入區間為[0, 4],輸出區間為['0deg', '360deg']進行線性插值。如果 Animated 對象當前值為 2,對應的旋轉角度就是 180deg。在組件渲染結構中,需要使用 Animated.div 包裹動畫節點,并將變化的元素屬性封裝為 css 對象作為 stlye 傳入 Animated.div 中。在點擊事件中,考慮到按鈕可以多次連續點擊,我們首先使用 stopAnimation 停止當前動畫,并獲取 Animated 對象的當前值 value ,隨后使用 Animated.spring 函數開啟一次彈簧動畫過程,從而實現一個流暢的動畫效果。由于每次轉動停止時,我們希望圖片的翻轉角度都是90°的整數倍,所以需要對 Animated.spring 的終止值進行取整。最終我們實現了如下效果:

React中怎么實現常見的動畫

使用時需要注意一下幾點:

  1. Animated 對象的值和其插值結果只能作用于 Animated.div 節點;

  2. interpolate 默認會根據輸入區間和輸出區間進行線性插值,如果輸入值超出輸入區間不受影響,插值結果默認會根據輸出區間向外延展插值,可以通過設置 extrapolate 屬性限制插值結果區間。

Animated 在動畫過程中不直接修改組件 state ,而是通過其新建對象的組件和方法直接修改元素的屬性,不會重復觸發 render 函數,是 React Native 中非常穩定的動畫庫。但是在 React 中存在低版本瀏覽器兼容問題,且具有一定學習成本。

結語

當我們在 React 中實現動畫時,首先要考量動畫的難易程度和使用場景,對于簡單動畫,優先使用 css3 實現,其次是基于 js 的時間間隔動畫。如果是元素入場動畫和離場動畫,則建議結合 CSSTransitionGroup 或者 TransitionGroup 實現。當要實現的動畫效果較為復雜時,不妨嘗試一些優秀的第三方庫,打開精彩的動效大門。

以上是“React中怎么實現常見的動畫”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

沧州市| 根河市| 寿阳县| 亚东县| 中卫市| 广元市| 舟山市| 兴隆县| 丽江市| 盘山县| 丘北县| 郓城县| 遵义县| 闵行区| 织金县| 建德市| 南丹县| 奇台县| 沈丘县| 久治县| 高雄市| 尚义县| 哈密市| 通化市| 灵寿县| 霍林郭勒市| 涟水县| 南康市| 五台县| 奎屯市| 文昌市| 宜良县| 沈阳市| 宁晋县| 湛江市| 山东省| 阿勒泰市| 姚安县| 平江县| 松江区| 尉犁县|