如何在Vue中實(shí)現(xiàn)Svelte的Defer Transition
最近觀看了Rich Harris的<Rethinking Reactivity>視頻,驚嘆于Svelte框架的高效同時(shí),還發(fā)現(xiàn)了Vue所不具備的一些關(guān)于動(dòng)畫的原生支持—defer transitions.
先看看Svelte所謂的defer transition的效果吧。
這是使用Svelte做的Todo Demo應(yīng)用。整個(gè)todo分成3個(gè)部分。輸入部分,todo列表和done列表。當(dāng)點(diǎn)擊todo列表中的條目時(shí),相應(yīng)條目會(huì)被“移動(dòng)”到done列表,反之亦然。
在這里,條目從一個(gè)列表轉(zhuǎn)移到另一個(gè)列表,不是很突兀的閃現(xiàn),而是非常友好地從點(diǎn)擊處,移動(dòng)到目的地;同時(shí),當(dāng)列表中條目離開時(shí),剩余的條目會(huì)絲滑地向上移動(dòng)填補(bǔ)空缺的位置。在Svelte里,只需要僅僅加上幾行代碼,就能實(shí)現(xiàn),對(duì)于開發(fā)者非常友好且高效。 參考如下代碼或者Svelte教程
{#each todos.filter(t => !t.done) as todo (todo.id)} <label in:receive="{{key: todo.id}}" out:send="{{key: todo.id}}" animate:flip> <input type=checkbox on:change={() => mark(todo, true)}> {todo.description} <button on:click="{() => remove(todo)}">remove</button> </label> {/each}
僅僅在element上加上了in、out和animate屬性。這里,in和out各自接受框架提供的函數(shù)receive和send并且給他們提供了篩選條件。 animate屬性接收內(nèi)置的flip方法。這里的flip不是翻轉(zhuǎn),而是FLIP技術(shù)技術(shù),vue在<transition-group>中也有用到。主要用于整體移動(dòng)列表剩余條目去填補(bǔ)所失去元素的位置。
于是我就在想,如果是Vue的話,如何能達(dá)到相應(yīng)的效果呢。 (不想看詳細(xì)說明的話,可以直接查看code pen中的代碼)
Vue原生提供了兩個(gè)組件支持動(dòng)畫。transition和transition-group。由于是list的移動(dòng),所以我們這里使用transition-group。具體使用方法可以參考Vue教程Transitions & Animation。
要想達(dá)到同樣的效果,有兩大UI動(dòng)畫效果要實(shí)現(xiàn)。
列表中條目消失時(shí),剩余條目移動(dòng)補(bǔ)齊空位
條目消失同時(shí)在另外一個(gè)列表插入時(shí),條目移動(dòng)
第一個(gè)需求的實(shí)現(xiàn)比較簡(jiǎn)單,vue原生已經(jīng)提供了良好的支持,參考Vue文檔中的List-Move-Transitions即可
為了實(shí)現(xiàn)第二個(gè)需求,有幾個(gè)問題必須解決:
- 消失條目的位置信息
- 插入條目的位置信息
- 動(dòng)效開始與結(jié)束的時(shí)機(jī)
我們先看看前兩個(gè)問題的如何解決。根據(jù)文檔的介紹,transition-group提供了javascript hook。分別是:
v-on:before-enter v-on:enter v-on:after-enter v-on:enter-cancelled v-on:before-leave v-on:leave v-on:after-leave v-on:leave-cancelled
可視化表示的話,大概是如下圖所示:
before-enter: 用于設(shè)置插入條目的transition的初始值。此時(shí)無法獲取BoundingClientRect. enter: 動(dòng)效期。此時(shí)enter鉤子函數(shù)的入?yún)l能獲取boundingClientRect after-enter: 動(dòng)效結(jié)束后的回調(diào)函數(shù) enter-cancelled: 取消enter的鉤子
leave也是類似。
所以,我們能拿到條目元素DOMRect信息的時(shí)機(jī)只有enter和leave的時(shí)候。
這樣,我們就可以在leave時(shí)候,拿到leave條目的DOMRect數(shù)據(jù)并且保存起來。在enter的時(shí)候, 我們就能同時(shí)擁有l(wèi)eave條目和enter條目的位置信息了。
位置信息是拿到了,那怎么才能在條目進(jìn)入的時(shí)候,有從消失條目移動(dòng)過來的效果呢。(可以先想想, 再看后面的解釋)。
所以,我們想要達(dá)成移動(dòng)的效果,首先需要隱藏掉leave條目元素,
leave(el, done) { console.log("before leave"); const rect = el.getBoundingClientRect(); sendRectMap.set(el.dataset.key, rect); el.style.display = "none"; },
然后給enter條目元素設(shè)定關(guān)于位置初始狀態(tài),初始化的位置即為leave條目元素的位置,然后當(dāng)transition開始生效的時(shí)候,讓其位置恢復(fù)到插入(enter)的位置。
這種方法其實(shí)就是所謂的FLIP技術(shù)。transition-group組件里也使用了這種技術(shù)來移動(dòng)剩余列表填充移走條目空白。
var first = el.getBoundingClientRect(); // Now set the element to the last position. el.classList.add('totes-at-the-end'); // Read again. This forces a sync // layout, so be careful. var last = el.getBoundingClientRect(); // You can do this for other computed // styles as well, if needed. Just be // sure to stick to compositor-only // props like transform and opacity // where possible. var invert = first.top - last.top; // Invert. el.style.transform = `translateY(${invert}px)`; // Wait for the next frame so we // know all the style changes have // taken hold. requestAnimationFrame(function() { // Switch on animations. el.classList.add('animate-on-transforms'); // GO GO GOOOOOO! el.style.transform = ''; });
那么接下來的問題就是,在什么時(shí)機(jī)去設(shè)置enter條目元素transition的初始狀態(tài),在什么時(shí)機(jī)去設(shè)置enter條目元素transition的結(jié)束時(shí)狀態(tài)。
按照上面提到的javascript hook,我們可以在before-enter鉤子函數(shù)里設(shè)置初始狀態(tài),接著在enter鉤子函數(shù)里設(shè)置transition結(jié)束狀態(tài)。那么,我們的初始狀態(tài)是什么呢?我們通過getBoundingClientRect,可以獲取到enter元素(后用to來標(biāo)識(shí))的DOMRect數(shù)據(jù),包括top, left, bottom, right, width, height , x, y。 同時(shí)我們也能通過之前保存的leave位置map獲取到leave條目的位置信息(稱為from)。所以在before-enter時(shí),我們通過計(jì)算得到的偏移量,通過translate去初始化to元素的位置。然后再在enter階段,translate其值到from, 再加上transition到css中即可。
在before-enter鉤子中:
// 移動(dòng)元素的標(biāo)識(shí)符 const key = el.dataset.key; // enter條目 map,注意這里,在before-enter鉤子中, // receiveRectMap是get不到to的。需要特殊處理。后面有提及 const to = receiveRectMap.get(key); // leave條目 map const from = sendRectMap.get(key); // 計(jì)算偏移量 const dx = from.left - to.left; const dy = from.top - to.top; // 初始化to條目的位置 el.style.transform = `translate(${dx}px, ${dy}px)`; el.style.opacity = 0;
在enter鉤子中:
el.style.transition = "all 800ms"; el.style.transform = ""; el.style.opacity = 1; el.style.display = "block";
上面的代碼中,在before-enter里面,to是通過receiveRectMap.get(key)來獲取的。但是,這時(shí),receiveRectMap中還沒有對(duì)應(yīng)key的DOMRect值。雖然,before-enter的入?yún)⑹莈l(HTMLElement),但是該el元素的DOMRect中的所有值都為0,所以我們需要在enter方法中,把el塞入到receiveRectMap中。這樣就會(huì)有一個(gè)矛盾,那就是無法在before-enter中通過translate初始化to元素的位置了。所以,我們這里使用defer transition技術(shù),延遲transition的發(fā)生。
我們可以在enter中使用setTimeout或者requestAnimationFrame實(shí)現(xiàn)defer transition,
requestAnimationFrame(() => { const key = el.dataset.key; // 這樣,receiveRectMap中就有該key的值了。 const to = receiveRectMap.get(key); const from = sendRectMap.get(key); const dx = from.left - to.left; const dy = from.top - to.top; // 由于我們延遲了transition的發(fā)生, // 所以to元素的位置其實(shí)已經(jīng)到達(dá)了目的地位置, // 所以我們需要使用transition手動(dòng)地將其過渡到from位置,這一行很重要 el.style.transition = "all 0ms"; el.style.transform = `translate(${dx}px, ${dy}px)`; // 初始化結(jié)束后,在下一個(gè)animation frame中,使用FLIP技術(shù),使其移動(dòng)回來。 requestAnimationFrame(() => { el.style.transition = "all 800ms"; el.style.transform = ""; el.style.opacity = 1; el.style.display = "block"; }); });
完整代碼可以參考codepen
最后效果:
以上就是如何在Vue中實(shí)現(xiàn)Svelte的Defer Transition的詳細(xì)內(nèi)容,更多關(guān)于Vue 實(shí)現(xiàn)Svelte的Defer Transition的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue2如何使用face-api.js實(shí)現(xiàn)人臉識(shí)別功能
本文介紹了如何在Vue.js項(xiàng)目中利用face-api.js實(shí)現(xiàn)人臉識(shí)別功能,首先需要安裝Vue.js和face-api.js以及其依賴TensorFlow.js,接著,下載并配置必要的模型文件到項(xiàng)目目錄,之后,將人臉識(shí)別功能封裝成Vue組件,并在組件中通過視頻流進(jìn)行人臉檢測(cè)和結(jié)果展示2024-09-09用Vue?Demi同時(shí)支持Vue2和Vue3的方法
這篇文章主要介紹了用Vue?Demi同時(shí)支持Vue2和Vue3的方法,實(shí)際開發(fā)中,同一個(gè)API在不同的版本中可能導(dǎo)入的來源不一樣,比如ref方法,本文結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12Vue父子組建的簡(jiǎn)單通信之控制開關(guān)Switch的實(shí)現(xiàn)
這篇文章主要介紹了Vue父子組建的簡(jiǎn)單通信之控制開關(guān)Switch的實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06