日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > vue >内容正文

vue

callback回调使用 vue_前端动画必知必会:React 和 Vue 都在用的 FLIP 思想实战

發布時間:2024/8/5 vue 51 豆豆
生活随笔 收集整理的這篇文章主要介紹了 callback回调使用 vue_前端动画必知必会:React 和 Vue 都在用的 FLIP 思想实战 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

前言

在 Vue 的官網中的過渡動畫章節中,可以看到一個很酷炫的動畫效果

乍一看,讓我們手寫出這個邏輯應該是非常復雜的,先看看本文最后要實現的效果吧,和這個案例是非常類似的。

預覽

也可以直接進預覽網址里看:

http://sl1673495.gitee.io/flip-animation

圖片素材依然引用自知乎問題《有個漂亮女朋友是種怎樣的體驗?》,侵刪。

分析需求

拿到了這個需求,第一直覺是怎么做?假設第一行第一個圖片移動到了第二行第三列,是不是要計算出第一行的高度,再計算出第二行前兩個元素的寬度,然后從初始的坐標點通過 CSS 或者一些動畫 API 移動過去?這樣做是可以,但是在圖片不定高不定寬,并且一次要移動很多圖片情況下,這個計算方法就非常復雜了。并且這種情況下,圖片的坐標都需要我們手動管理,非常不利于維護和擴展。

換種思路,能不能直接很自然的把 DOM 元素通過原生 API 添加到 DOM 樹中,然后讓瀏覽器幫我們好這個終點值,最后我們再動畫位移過去?

在文檔里我們發現一個名詞:FLIP,這給了我們一個線索,是不是用這個玩意就可以寫出這個動畫呢?

答案是肯定的,順著這個線索找到 Aerotwist 社區里的一篇文章:flip-your-animations,以這篇文章為切入點,一步步來實現一個類似的效果。

FLIP

FLIP 究竟是什么東西呢?先看下它的定義:

First

即將做動畫的元素的初始狀態(比如位置、透明度等等)。

Last

即將做動畫的元素的最終狀態。

Invert

這一步比較關鍵,假設我們圖片的初始位置是 左: 0, 上:0,元素動畫后的最終位置是 左:100, 上100,那么很明顯這個元素是向右下角運動了 100px。

但是,此時我們不按照常規思維去先計算它的最終位置,然后再命令元素從 0, 0 運動到 100, 100,而是先讓元素自己移動過去(比如在 Vue 中用數據來驅動,在數組前面追加幾個圖片,之前的圖片就自己移動到下面去了)。

這里有一個關鍵的知識點要注意了,也是我在之前的文章《深入解析你不知道的 EventLoop 和瀏覽器渲染、幀動畫、空閑回調》中提到過的:

DOM 元素屬性的改變(比如 left、right、 transform 等等),會被集中起來延遲到瀏覽器的下一幀統一渲染,所以我們可以得到一個這樣的中間時間點:DOM 狀態(位置信息)改變了,而瀏覽器還沒渲染。

有了這個前置條件,我們就可以保證先讓 Vue 去操作 DOM 變更,此時瀏覽器還未渲染,我們已經能得到 DOM 狀態變更后的位置了。

說的具體點,假設我們的圖片是一行兩個排列,圖片數組初始化的狀態是 [img1, img2,此時我們往數組頭部追加兩個元素 [img3, img4, img1, img2],那么 img1 和 img2 就自然而然的被擠到下一行去了。

假設 img1 的初始位置是 0, 0,被數據驅動導致的 DOM 改變擠下去后的位置是 100, 100,那么此時瀏覽器還沒有渲染,我們可以在這個時間點把 img1.style.transform = translate(-100px, -100px),讓它 先 Invert 倒置回位移前的位置。

Play

倒置了以后,想要讓它做動畫就很簡單了,再讓它回到 0, 0 的位置即可,本文會采用最新的 Web Animation API 來實現最后的 Play。

MDN 文檔:Web Animation

實現

首先圖片渲染很簡單,就讓圖片通過簡單的排成 4 列即可:

.wrap {display: flex;flex-wrap: wrap; }.img {width: 25%; }<div v-else class="wrap"><div class="img-wrap" v-for="src in imgs" :key="src"><img ref="imgs" class="img" :src="src" /></div> </div>

那么關鍵點就在于怎么往這個 imgs 數組里追加元素后,做一個流暢的路徑動畫。

我們來實現追加圖片的方法 add:

async add() {const newData = this.getSister()await preload(newData) }

首先隨機的取出幾張圖片作為待放入數組的元素,利用 new Image 預加載這些圖片,防止渲染一堆空白圖片到屏幕上。

然后定義一個計算一組 DOM 元素位置的函數 getRects,利用 getBoundingClientRect 可以獲得最新的位置信息,這個方法在接下來獲取圖片元素舊位置和新位置時都要使用。

function getRects(doms) {return doms.map((dom) => {const rect = dom.getBoundingClientRect()const { left, top } = rectreturn { left, top }}) }// 當前已有的圖片 const prevImgs = this.$refs.imgs.slice() const prevPositions = getRects(prevImgs)

記錄完圖片的舊位置后,就可以向數組里追加新的圖片了:

this.imgs = newData.concat(this.imgs)

隨后就是比較關鍵的點了,我們知道 Vue 是異步渲染的,也就是改變了這個 imgs 數組后不會立刻發生 DOM 的變動,此時我們要用到 nextTick 這個 API,這個 API 把你傳入的回調函數放進了 microTask 隊列,正如上文提到的事件循環的文章里所說,microTask隊列的執行一定發生在瀏覽器重新渲染前。

由于先調用了 this.imgs = newData.concat(this.imgs) 這段代碼,觸發了 Vue 的響應式依賴更新,此時 Vue 內部會把本次 DOM 更新的渲染函數先放到 microTask隊列中,此時的隊列是[changeDOM]。

調用了 nextTick(callback) 后,這個callback函數也會被追加到隊列中,此時的隊列是 [changeDOM, callback]。

這下聰明的你肯定就明白了,為什么 nextTick的回調函數里一定能獲取到最新的 DOM 元素,此時新加入圖片的 DOM 已經在 DOM 樹中,但是屏幕還沒有發生繪制,調用 getBoundingClientRect 可以觸發「強制同步布局」,此后的代碼里就可以獲取到 DOM 在屏幕預計出現的準確位置。(注意,位置信息是最新的,但是屏幕上并沒有發生繪制)

由于我們之前保存了圖片元素節點的數組 prevImgs,所以在 nextTick 里對這些舊圖片再調用一次 getRect 方法,獲取到的就是舊圖片的最新位置了。

async add() {// 最新 DOM 狀態this.$nextTick(() => {// 再調用同樣的方法獲取最新的元素位置const currentPositions = getRects(prevImgs)}) },

此時我們已經擁有了 Invert 步驟的關鍵信息,新位置和舊位置,那么接下來就很簡單了,把圖片數組循環做一個倒置后 Play的動畫即可。

prevImgs.forEach((imgRef, imgIndex) => {const currentPosition = currentPositions[imgIndex]const prevPosition = prevPositions[imgIndex]// 倒置后的位置,雖然圖片移動到最新位置了,但你先給我回去,等著我來讓你做動畫。const invert = {left: prevPosition.left - currentPosition.left,top: prevPosition.top - currentPosition.top,}const keyframes = [// 初始位置是倒置后的位置{transform: `translate(${invert.left}px, ${invert.top}px)`,},// 圖片更新后本來應該在的位置{ transform: "translate(0)" },]const options = {duration: 300,easing: "cubic-bezier(0,0,0.32,1)",}// 開始運動!const animation = imgRef.animate(keyframes, options) })

此時一個非常流暢的路徑動畫效果就完成了。

完整實現如下:

async add() {const newData = this.getSister()await preload(newData)const prevImgs = this.$refs.imgs.slice()const prevPositions = getRects(prevImgs)this.imgs = newData.concat(this.imgs)this.$nextTick(() => {const currentPositions = getRects(prevImgs)prevImgs.forEach((imgRef, imgIndex) => {const currentPosition = currentPositions[imgIndex]const prevPosition = prevPositions[imgIndex]const invert = {left: prevPosition.left - currentPosition.left,top: prevPosition.top - currentPosition.top,}const keyframes = [{transform: `translate(${invert.left}px, ${invert.top}px)`,},{ transform: "translate(0)" },]const options = {duration: 300,easing: "cubic-bezier(0,0,0.32,1)",}const animation = imgRef.animate(keyframes, options)})}) },

亂序

現在我們想要實現官網 demo 中的 shuffle 效果,有了追加圖片邏輯的鋪墊,是不是已經覺得思路如泉涌了?沒錯,即使圖片被打亂的再厲害,只要我們有「圖片開始時的位置」和「圖片結束時的位置」,那就可以輕松做到路徑動畫。

現在我們需要做的是把動畫的邏輯抽離出來,我們分析一下整條鏈路:

保存舊位置 -> 改變數據驅動視圖更新 -> 獲得新位置 -> 利用 FLIP 做動畫

其實外部只需要傳入一個 update 方法告訴我們如何去更新圖片數組,就可以把這個邏輯完全抽象到一個函數里去。

scheduleAnimation(update) {// 獲取舊圖片的位置const prevImgs = this.$refs.imgs.slice()const prevSrcRectMap = createSrcRectMap(prevImgs)// 更新數據update()// DOM更新后this.$nextTick(() => {const currentSrcRectMap = createSrcRectMap(prevImgs)Object.keys(prevSrcRectMap).forEach((src) => {const currentRect = currentSrcRectMap[src]const prevRect = prevSrcRectMap[src]const invert = {left: prevRect.left - currentRect.left,top: prevRect.top - currentRect.top,}const keyframes = [{transform: `translate(${invert.left}px, ${invert.top}px)`,},{ transform: "" },]const options = {duration: 300,easing: "cubic-bezier(0,0,0.32,1)",}const animation = currentRect.img.animate(keyframes, options)})}) }

那么追加圖片和亂序的函數就變得非常簡單了:

// 追加圖片 async add() {const newData = this.getSister()await preload(newData)this.scheduleAnimation(() => {this.imgs = newData.concat(this.imgs)}) }, // 亂序圖片 shuffle() {this.scheduleAnimation(() => {this.imgs = shuffle(this.imgs)}) }

源碼地址

https://github.com/sl1673495/flip-animation

總結

FLIP

FLIP 不光可以做位置變化的動畫,對于透明度、寬高等等也一樣可以很輕松的實現。

比如電商平臺中經常會出現一個動畫,點擊一張商品圖片后,商品從它本來的位置慢慢的放大成了一張完整的頁面。

FLIP的思路掌握后,只要你知道元素動畫前的狀態和元素動畫后的狀態,你都可以輕松的通過「倒置狀態」后,讓它們做一個流暢的動畫后到達目的地,并且此時的 DOM 狀態是很干凈的,而不是通過大量計算的方式強迫它從 0, 0 位移到 100, 100,并且讓 DOM 樣式上留下 transform: translate(100px, 100px) 類似的字樣。

Web Animation

利用 Web Animation API 可以讓我們用 JavaScript 更加直觀的描述我們需要元素去做的動畫,想象一下這個需求如果用 CSS 來做,我們大概會這樣去完成這個需求:

const currentImgStyle = currentRect.img.style currentImgStyle.transform = `translate(${invert.left}px, ${invert.top}px)` currentImgStyle.transitionDuration = "0s"this._reflow = document.body.offsetHeightcurrentRect.img.classList.add("move")currentImgStyle.transform = currentRect.img.style.transitionDuration = ""currentRect.img.addEventListener("transitionend", () => {currentRect.img.classList.remove("move") })

這是選擇用比較原生的方式去控制 CSS 樣式實現的 FLIP 動畫,這段代碼讓我覺得不舒服的點在于:

  • 需要通過 class 的增加和刪除來和 CSS 來進行交互,整體流程不太符合直覺。
  • 需要監聽動畫完成事件,并且做一些清理操作,容易遺漏。
  • 需要利用 document.body.offsetHeight 這樣的方式觸發 強制同步布局,比較 hack 的知識點。
  • 需要利用 this._reflow = document.body.offsetHeight 這樣的方式向元素實例上增加一個沒有意義的屬性,防止被 Rollup 等打包工具 tree-shaking 誤刪。 比較 hack 的知識點 +1。
  • 而利用 Web Animation API 的代碼則變得非常符合直覺和易于維護:

    const keyframes = [{transform: `translate(${invert.left}px, ${invert.top}px)`,},{ transform: "" }, ] const options = {duration: 300,easing: "cubic-bezier(0,0,0.32,1)", }const animation = currentRect.img.animate(keyframes, options)

    關于兼容性問題,W3C 已經提供了 Web Animation API Polyfill,可以放心大膽的使用。

    期待在不久的未來,我們可以拋棄舊的動畫模式,迎接這種更新更好的 API。

    希望這篇文章能讓對動畫發愁的你有一些收獲,謝謝!

    與50位技術專家面對面20年技術見證,附贈技術全景圖

    總結

    以上是生活随笔為你收集整理的callback回调使用 vue_前端动画必知必会:React 和 Vue 都在用的 FLIP 思想实战的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 激情第一页 | 99中文字幕| 精品国产av 无码一区二区三区 | 伊人婷婷在线 | www.色就是色 | 亚洲热在线视频 | 麻豆偷拍 | 在线观看免费中文字幕 | 少妇视频网站 | 精品福利在线观看 | 成人免费毛片观看 | 尹人在线视频 | 男女一级特黄 | 国产视频黄 | 午夜一区 | 亚洲av片在线观看 | 能看的av网站| 国产一区二区免费在线观看 | 日本成人午夜视频 | 男男play呻吟动漫网站 | 蜜桃视频网站 | www.色人阁.com | 国产精品一区二区免费视频 | 91中文| 久久久久九九九九 | 999视频 | 亚洲美女屁股眼交3 | 粗大黑人巨茎大战欧美成人免费看 | 欧美亚洲丝袜 | 一级片一级| 探花国产 | 丰满人妻av一区二区三区 | 麻豆chinese新婚xxx | 四虎毛片| 欧美黑人疯狂性受xxxxx喷水 | 欧美性爱精品一区 | 四虎免费影视 | 国产精品2| 色久婷婷| 亚洲精品一区二区三区四区 | 一本久草| 糖心av| 在线免费观看a级片 | 性色在线视频 | 中文字幕在线观看亚洲 | 日韩1区2区3区 | 快播在线视频 | 久久精品视频免费播放 | 欧美刺激性大交 | 亚洲一级网 | 国产日韩欧美视频 | 成人三级做爰av | 韩国福利一区 | 久久高清无码电影 | 亚洲熟女综合一区二区三区 | 女人下面无遮挡 | 激情五月av | 99精品欧美一区二区 | 伊人视频在线观看 | 偷拍精品一区二区三区 | 国产专区av | 欧美日韩在线视频观看 | 国内一级黄色片 | 边吃奶边添下面好爽 | 美女丝袜合集 | 国产精品美女网站 | 色视频网址 | 日韩高清一级 | 啪啪小视频网站 | 特级毛片在线播放 | 日韩国产欧美 | 亚洲第一免费网站 | 一级成人毛片 | 亚洲素人在线 | 久久久久久黄色片 | 搡老岳熟女国产熟妇 | 久久97久久97精品免视看 | 日韩在线观看不卡 | 视色视频 | 天天摸天天舔天天操 | 手机av免费在线观看 | 天天操天天操 | 国产第一页在线观看 | 美女网站在线免费观看 | 久久久久久免费观看 | 快播怡红院 | 在线观看日韩精品 | 91国产视频在线 | 99国内揄拍国内精品人妻免费 | 中文字幕成人网 | 国产第一页在线观看 | 一级免费看片 | 成人综合区 | 国产精久久久久久 | www.免费av | 看免费的毛片 | 成年人在线播放视频 | 在线理论视频 | 精品美女一区二区三区 |