FLIP實(shí)現(xiàn)animation動(dòng)畫

本文代碼均放在git倉庫

FLIP是什么

首先FLIP并不是一項(xiàng)新技術(shù),可以把它理解為一種實(shí)現(xiàn)動(dòng)畫的新的理念或者新的方法盐欺。

FLIP是 First赁豆、Last、Invert和 Play四個(gè)單詞首字母的縮寫冗美。

  • First魔种,指的是在任何事情發(fā)生之前(過渡之前),記錄當(dāng)前元素的位置和尺寸粉洼,即動(dòng)畫開始之前那一刻元素的位置和尺寸信息节预,可以使用 getBoundingClientRect()這個(gè) API來處理;
  • Last:執(zhí)行一段代碼属韧,讓元素發(fā)生相應(yīng)的變化安拟,并記錄元素在動(dòng)畫最后狀態(tài)的位置和尺寸,即動(dòng)畫結(jié)束之后那一刻元素的位置和尺寸信息宵喂;
  • Invert:計(jì)算元素第一個(gè)位置(First)和最后一個(gè)位置(Last)之間的位置或者尺寸變化糠赦,然后使用這些數(shù)字做一定的計(jì)算,讓元素進(jìn)行移動(dòng)(通過 transform來改變?cè)氐奈恢煤统叽纾┕兀瑥亩鴦?chuàng)建它位于初始位置的一個(gè)錯(cuò)覺拙泽。即讓元素處于動(dòng)畫的結(jié)束狀態(tài),然后使用 transform屬性將元素反轉(zhuǎn)回動(dòng)畫的開始狀態(tài)(這個(gè)狀態(tài)的信息在 First步驟就拿到了)裸燎;
  • Play:將元素反轉(zhuǎn)(假裝在first位置)顾瞻,我們可以把 transform設(shè)置為 none,因?yàn)槭チ?transform的約束顺少,所以元素肯定會(huì)往本該在的位置(即動(dòng)畫結(jié)束時(shí)的那個(gè)狀態(tài))進(jìn)行移動(dòng)朋其,也就是last的位置王浴,如果給元素加上 transition的屬性,那么這個(gè)過程自然也就是以一種動(dòng)畫的形式發(fā)生了梅猿。

舉個(gè)??

var app = document.getElementById('app');
var first = app.getBoundingClientRect(); // 初始態(tài)
app.classList.add('app-to-end');
var last = app.getBoundingClientRect(); // 終態(tài)
var invert = first.top - last.top;
// 使元素看起來好像在起始點(diǎn)
app.style.transform = `translateY(${invert}px)`;
requestAnimationFrame(function() {
   // 啟用tansition氓辣,并移除翻轉(zhuǎn)的改變
  app.classList.add('animate-on-transforms');
  app.style.transform = '';
  // 此時(shí),方塊就會(huì)從假裝在起始點(diǎn)開始transition
});
app.addEventListener('transitionend', () => {
  app.classList.remove('animate-on-transforms');
});

<div id="app"></div>
<style>
    #app {
        position: absolute;
        width:20px;
        height:20px;
        background: red;
    }
    .app-to-end {
        top: 100px;
    }
    .animate-on-transforms {
        transition: all 2s;
    }
</style>

在React中是什么用的袱蚓?

微信app里聊天界面點(diǎn)擊預(yù)覽圖片時(shí)钞啸,圖片從對(duì)話框到全屏預(yù)覽的這個(gè)過程,用了一個(gè)過渡的動(dòng)畫喇潘,呈現(xiàn)出圖片從小圖到大圖和從大圖恢復(fù)到小圖的全過程体斩。代碼倉庫

First

<ul className="pic-list">
  {
    listData.map((item, index) => (
      <li
        key={index}
        className="pic-item"
        onClick={this.previewItem.bind(this, 1, item)}  // 給每一個(gè)item綁定處理函數(shù)
        title="點(diǎn)擊預(yù)覽">
        <img src={item.bgPic} alt="" className="pic" />
      </li>
    ))
  }
</ul>

在previewItem函數(shù)中的處理邏輯:計(jì)算初始態(tài)

previewItem (status, previewImgInfo = null, e) {
    previewVisibleStatus = status;
    if (previewVisibleStatus === 1) {
      // 計(jì)算初始態(tài)
      const currentPreviewEle = e.target;
      rectInfo = currentPreviewEle.getBoundingClientRect()
      previewFirstRect[0] = rectInfo.left
      previewFirstRect[1] = rectInfo.top
      this.setState({
        previewImgInfo,
        previewStatus: 1
      })
    } else {
      this.setState({
        previewStatus: 1
      })
    }
}

此時(shí)會(huì)觸發(fā)一次render()函數(shù)

{
  (previewVisibleStatus === 1 || previewVisibleStatus === 2) ? (
    <>
      <div
        className="preview-box"
        onClick={this.previewItem.bind(this, 2)}
        style={{
          opacity: previewStatus === 3 && previewVisibleStatus !== 2 ? .65 : 0
        }}
      ></div>
      <img
        ref={this.previewRef}
        src={previewImgInfo.bgPic} // 圖片URL
        style={{
          // 此時(shí)previewStatus是1,transform屬性為translate3d(0, 0, 0) scale(1)
          transform: previewStatus === 2 || previewVisibleStatus === 2
            ? `xxxxxx` : 'translate3d(0, 0, 0) scale(1)',
          transformOrigin: '0 0'
        }}
        onClick={this.previewItem.bind(this, 2)}
        onTransitionEnd={this.transEnd.bind(this)}
        alt="" />
    </>
  ) : null
}

Last

接著會(huì)觸發(fā)componentDidUpdate生命周期函數(shù)颖低,在該生命周期函數(shù)中主要是執(zhí)行updatePreviewStatus函數(shù)絮吵。

updatePreviewStatus () {
    if (this.state.previewStatus === 1) {
      // 計(jì)算終態(tài)
      if (previewVisibleStatus === 1) {
        const lastRectInfo = this.previewRef.current.getBoundingClientRect()
        previewLastRect[0] = lastRectInfo.left
        previewLastRect[1] = lastRectInfo.top
        scaleValue = rectInfo.width / lastRectInfo.width
      }
      this.setState({
        previewStatus: 2
      })
    } else if (this.state.previewStatus === 2) {
      // Play
      setTimeout(() => {
        this.setState({
          previewStatus: 3
        })
      }, 0)
    }
}

Invert

再次觸發(fā)render()函數(shù)

{
  (previewVisibleStatus === 1 || previewVisibleStatus === 2) ? (
    <>
      <div
        className="preview-box"
        onClick={this.previewItem.bind(this, 2)}
        style={{
          opacity: previewStatus === 3 && previewVisibleStatus !== 2 ? .65 : 0
        }}
      ></div>
      <img
        ref={this.previewRef}
        src={previewImgInfo.bgPic}  // 圖片URL
        style={{
          // 此時(shí)previewStatus是2,transform屬性為下面這一大坨
          transform: previewStatus === 2 || previewVisibleStatus === 2
            ? `translate3d(${previewFirstRect[0] - previewLastRect[0]}px, ${previewFirstRect[1] - previewLastRect[1]}px, 0) scale(${scaleValue})`
            : 'translate3d(0, 0, 0) scale(1)',
          transformOrigin: '0 0'
        }}
        onClick={this.previewItem.bind(this, 2)}
        onTransitionEnd={this.transEnd.bind(this)}
        alt="" />
    </>
  ) : null
}

此時(shí)忱屑,transform屬性是

translate3d(${previewFirstRect[0] - previewLastRect[0]}px, ${previewFirstRect[1] - previewLastRect[1]}px, 0) scale(${scaleValue})

也就是說第二次執(zhí)行render函數(shù)的時(shí)候蹬敲,圖片在(translate3d + scale)的作用下縮放回了初始位置。

Play

再次觸發(fā)componentDidUpdate生命周期函數(shù)莺戒,在該生命周期函數(shù)中主要是執(zhí)行updatePreviewStatus函數(shù)伴嗡。

updatePreviewStatus () {
    if (this.state.previewStatus === 1) {
      // xxxxx
    } else if (this.state.previewStatus === 2) {
      // Play
      setTimeout(() => {
        this.setState({
          previewStatus: 3
        })
      }, 0)
    }
}

觸發(fā)render()函數(shù)

<>
  <div
    className="preview-box"
    onClick={this.previewItem.bind(this, 2)}
    style={{
      opacity: previewStatus === 3 && previewVisibleStatus !== 2 ? .65 : 0
    }}
  ></div>
  <img
    ref={this.previewRef}
    className={`img${(previewStatus === 3 && previewVisibleStatus === 1) || previewVisibleStatus === 2 ? ' active' : ''}`}
    src={previewImgInfo.bgPic}
    onClick={this.previewItem.bind(this, 2)}
    onTransitionEnd={this.transEnd.bind(this)}
    alt="" />
</>

為圖片添加 transition屬性,并移除相關(guān) transform屬性从铲,即可啟動(dòng)動(dòng)畫瘪校。

css:
.img.active {
  transition: all .36s ease-in-out;
}

至于放大后的圖片恢復(fù)到小圖這一個(gè)階段,可以看成是另外一個(gè) FLIP動(dòng)畫名段,繼續(xù)套用即可阱扬,只不過這個(gè)動(dòng)畫就是上一個(gè)放大動(dòng)畫的逆向,所需的尺寸和位置信息都已經(jīng)拿到了吉嫩,可以省去調(diào)用 getBoundingClientRect的過程价认。

為什么要用FLIP?

有些人可能比較疑惑自娩,如果想要實(shí)現(xiàn)動(dòng)畫的話用踩,直接 transform不就好了,為什么要多此一舉搞個(gè) FLIP的概念出來忙迁?

對(duì)于一些動(dòng)畫脐彩,你明確的知道它的初始態(tài)(First)和結(jié)束態(tài)(Last),比如你就想讓一個(gè)元素從 left:10px;移動(dòng)到 left:100px;姊扔,那么你直接 transform就好了惠奸,根本沒必要 FLIP,用了反而多此一舉恰梢;

但除此之外佛南,還有一部分你無法明確的初始態(tài)(First)或結(jié)束態(tài)(Last)的動(dòng)畫梗掰。這樣情況下使用FLIP就會(huì)比較爽快了。但是你可能又會(huì)說嗅回,我即時(shí)不知道結(jié)束態(tài)及穗,但是可以使用瀏覽器 API進(jìn)行測(cè)量啊。

不好意思绵载,這正是 FLIP要做的事情之一埂陆,你還是在無意識(shí)地情況下用到了這個(gè)東西,只不過相對(duì)于被前人總結(jié)并優(yōu)化后的 FLIP來說娃豹,你的整體用法可能更零散更不規(guī)范一些焚虱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市懂版,隨后出現(xiàn)的幾起案子鹃栽,更是在濱河造成了極大的恐慌,老刑警劉巖躯畴,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件谍咆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡私股,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門恩掷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倡鲸,“玉大人,你說我怎么就攤上這事黄娘∏妥矗” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵逼争,是天一觀的道長(zhǎng)优床。 經(jīng)常有香客問我,道長(zhǎng)誓焦,這世上最難降的妖魔是什么胆敞? 我笑而不...
    開封第一講書人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮杂伟,結(jié)果婚禮上移层,老公的妹妹穿的比我還像新娘。我一直安慰自己赫粥,他們只是感情好观话,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著越平,像睡著了一般频蛔。 火紅的嫁衣襯著肌膚如雪灵迫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評(píng)論 1 305
  • 那天晦溪,我揣著相機(jī)與錄音瀑粥,去河邊找鬼。 笑死尼变,一個(gè)胖子當(dāng)著我的面吹牛利凑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播嫌术,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼哀澈,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了度气?” 一聲冷哼從身側(cè)響起割按,我...
    開封第一講書人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎磷籍,沒想到半個(gè)月后适荣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡院领,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年弛矛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片比然。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡丈氓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出强法,到底是詐尸還是另有隱情万俗,我是刑警寧澤,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布饮怯,位于F島的核電站闰歪,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蓖墅。R本人自食惡果不足惜库倘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望置媳。 院中可真熱鬧于樟,春花似錦、人聲如沸拇囊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽寥袭。三九已至路捧,卻和暖如春关霸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背杰扫。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來泰國打工队寇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人章姓。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓佳遣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親凡伊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子零渐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

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

  • CSS參考手冊(cè) 一、初識(shí)CSS3 1.1 CSS是什么 CSS3在CSS2.1的基礎(chǔ)上增加了很多強(qiáng)大的新功能系忙。目前...
    沒汁帥閱讀 3,584評(píng)論 1 13
  • 1 CALayer IOS SDK詳解之CALayer(一) http://doc.okbase.net/Hell...
    Kevin_Junbaozi閱讀 5,152評(píng)論 3 23
  • 一:在制作一個(gè)Web應(yīng)用或Web站點(diǎn)的過程中诵盼,你是如何考慮他的UI、安全性银还、高性能风宁、SEO、可維護(hù)性以及技術(shù)因素的...
    Arno_z閱讀 1,166評(píng)論 0 1
  • 一蛹疯、CSS入門 1戒财、css選擇器 選擇器的作用是“用于確定(選定)要進(jìn)行樣式設(shè)定的標(biāo)簽(元素)”。 有若干種形式的...
    寵辱不驚丶?xì)q月靜好閱讀 1,598評(píng)論 0 6
  • 通過jQuery捺弦,您可以選裙毯病(查詢,query)HTML元素羹呵,并對(duì)它們執(zhí)行“操作”(actions)。 jQuer...
    枇杷樹8824閱讀 657評(píng)論 0 3