那些年讓我們頭疼的CSS3動畫

小美娜娜發表於2019-03-19

這是筆者整理的個人在CSS3動畫上遇到的問題,全部都是筆者個人的經驗,以及解決方案,並不相信網上會有重複的文章。大家可以點進每個小欄目的codeplay去地直觀feel一下。

常見錯誤:Animation篇

首先先來複習一下animation的有哪些屬性:

屬性名 預設 作用
animation-name none keyframe 的名字
animation-duration 0s 執行總時長
animation-timing-function ease 執行的速度變化,總不可能總是勻速吧
animation-delay 0s 延遲時間
animation-iteration-count 1 重複的次數,也可以是無限的infinite
animation-direction normal 動畫執行方向,可以正著來,也可以反著來reverse
animation-fill-mode none important 動畫結束之後和開始之前的狀態,是不是動畫未開始的時候就是預設樣式,結束的時候又返回預設樣式。
animation-play-state running 動畫的狀態,注意如果說這個時候動畫執行結束了,狀態也是running,因為這個running不是表示動畫正在執行,而是一種狀態,有沒有被強行暫停。

常見錯誤一:動畫做完就disappear了!喂喂喂!沒讓你消失啊。code play~

這個應該是animation中的基礎題,所以放在了第一位。這個問題的解決方案就是animation-fill-mode這個屬性沒有設定或者設定錯誤。這個屬性從專業的角度來說是動畫的一個延續,就是0~100%的keyframe走完之後和開始之前的歸宿。簡單地來說就是動畫結束之後或者開始之前,當前元素的狀態是否保留動畫最後一幀的狀態或者未開始之前使用第一幀的樣式。這個屬性預設是不保留狀態,也就是說開始之前是原始狀態,開始之後才開始轉換樣式,結束之後會立刻切換至原始狀態,彷彿這個動畫不曾存在過。這裡的forwards是指動畫結束之後(無論是正著來還是倒這來)的狀態,backwards是指動畫delay的時候的狀態。both就很簡單了,包含了結束之後和開始之前的狀態。當然大家更喜歡both,這樣就不用考慮之前或者之後的問題了。

常見錯誤二:怎麼停不下來,想控制動畫的動態。code play~

這裡我們可以借用animation-play-state來控制動畫的是否繼續。劃重點,這邊的是否繼續並不包含重新開始。

那麼,我可不可以通過控制animation-direction的值來控制動畫的重新開始呢?比如我一個動畫reverse一下之後又重新開始了。emmm,想法很好,但是現實很殘酷。animation的time很智慧,比如我在動畫的途中改變了動畫,然後animation會根據當前時間的反方向的狀態獲取狀態,然後從哪個點開始執行,也就是說在動畫途中改變direction,最終動畫還是按照原來的時間執行,也就是除非取消動畫,否則動畫的時間是固定的(當然會有一些小誤差)。所以想利用direction來讓動畫動起來就是太天真了。

既然如此,我可不可以來回動呢,比如一個element,我希望他可以從左到右再從右到左。這個時候animation-direction可以設定為alternate,這樣就會控制奇偶次數的不同動畫範圍,but!動畫次數要>1,不然哪裡來的奇偶呀~

這裡有一篇CSS triks上詳細講解如何重新開始動畫的部落格,Restart CSS Animation

常見錯誤:Transition篇

複習一下transition的各個屬性值。

transition屬性 預設值 作用
transition-delay 0 延遲
transition-duration 0 變換速度
transition-property all 變化屬性,預設監控全部變化,如果只需要某一個值比如transform,就只監聽transform即可。
transition-timing-function ease 時間函式

只有我一個人覺得Transition是一個大坑嗎,感覺比animation更難以駕馭。

常見錯誤三:是我眼花了嗎,怎麼直接結束了,彷彿不曾存在過。play code

有一種情況,我在頁面首次渲染地時候就對transform進行了修改,想著只要改變tranform,transition就會工作。但是這個transition地工作性質是對比當前渲染狀態和上一次渲染狀態的的差別。因此像這個樣子改變,在首次渲染之前就改變了transform,transition失去了對比的參照物,然後就不動了,解決這個問題可以用requestAnimationFrame解決,這個方法就是用於渲染前的最後一步也就是paint之前的一步,然後修改了layout,最後transition感受到了召喚,識別出了差別,就正常工作了起來。

FailTransition.style.transform="translateX(100px)"
requestAnimationFrame(()=>{
  SuccessTransition.style.transform="translateX(100px)"
})
複製程式碼

步驟大概是這樣的:

Recalculate Style>Layout>requestAnimationFrame>Recalculate Style>Layout>Update Layer Tree>Paint>Composite Layers

還有一種情況,就是我們希望這個元素可以先向右再向左,根據上次的經驗我們可以這麼寫,但是失效了,為什麼呢?因為點選之後設定的樣式,還沒抵達paint就被requestAnimationFrame重寫了,然後就按照最後一次的樣式和上一次渲染的樣式做了對比,進行了變換。

change.addEventListener("click",()=>{
    ChangeFrequency.style.transform="translateX(10000px)"
    requestAnimationFrame(()=>{
        ChangeFrequency.style.transform="translateX(50px)"
    })
})
複製程式碼

為了解決這個問題我們可以雙重requestAnimationFrame。然而可以看到生效了,但是transition會自動修復,1s內完成的動畫,絕不會超時,因此我們的動畫悲劇沒有按照我們設定的那樣向右移動1s之後再向左執行1s,而是總時間1s,所以會閃現結束。

change.addEventListener("click",()=>{
    ChangeFrequency.style.transform="translateX(10000px)"
    requestAnimationFrame(()=>{
        requestAnimationFrame(()=>{
            ChangeFrequency.style.transform="translateX(50px)"
        })
    })
})
複製程式碼

至於如何實現串聯動畫,我的第一選擇是animation,第二選擇是監聽transitionend事件,當地一個動畫結束後再執行之後的動畫。

function moveBack(){
  ChangeFrequencyFix.style.transform="translateX(50px)"
  ChangeFrequencyFix.removeEventListener("transitionend",moveBack)
}
ChangeFrequencyFix.addEventListener("transitionend",moveBack)

複製程式碼

常見錯誤四:怎麼回事!怎麼從奇怪的地方出現了!這是一個小bug。code play

理想是從左上到右下,然後放大,動畫結束後,重新從原點出發,從上放大滑動到下方,然後卻直接從右下平行滑動到了最後的位置,這個小bug可以說是作死。因為transition的特性是保留上一次動畫的最後一幀,然後過渡到新的狀態,如果不想要某一個狀態的重置,記得關閉transition,否則就會出現連續的動畫。

function goTrravel(){
  correctToGo.removeEventListener("transitionend",goTrravel)
  correctToGo.style.transition="none"
  correctToGo.classList.remove("active")
  requestAnimationFrame(()=>{ 
    requestAnimationFrame(()=>{ 
      correctToGo.classList.add("pop")
      correctToGo.style.transition="transform 2s,opacity 2s"
    })
  })
}
correctToGo.addEventListener("transitionend",goTrravel)
複製程式碼

因為transition是實時監控的,所以如果如果去除了transform的狀態,他會回到default的狀態。因此,如果想要拋棄回到初始化或者某一狀態需要重置transition。

常見錯誤五:喂喂喂,你怎麼還在這個層下面,我都給你z-index:1000了,你快出來啊。動畫提升層code play

這裡需要提出兩個概念一個是css context stack,MDN參考網址

一般情況下如果想要層級高,就用z-index設定就可以了。甚至父節點A的層級低於父節點B的層級,但是,再沒有提升層的情況下,同一層中父節點A的子節點可以高於父節點B的層級。但是若是產生了提升層,除非整個父節點A高於父節點B,父節點A中的子節點才可以高於父節點B的層級。

下圖就是一個例子,大家可以看見下面這個產生提升層的情況,裡面的黃色子節點是無法突破紅色節點的,因為他們已經處於不同的層了。z-index只作用於當前層,沒有跨層處理的能力。

那些年讓我們頭疼的CSS3動畫

常見錯誤六:will-change和translateZ(0)作用是一樣的。

這一塊,我們都知道如果想要加速GPU渲染就使用類似於hack的translateZ(0)或者是CSS新屬性will-change,那麼這兩者的原理是什麼,具體的使用情況是什麼?

其實他們就是一個提升層的概念,將之後可能會改變的元素從當前的層中抽離,阻止composition,這樣這部分修改的時候就不會影響整個頁面的佈局,從而阻止了reflow。

那麼translateZ(0)的作用是否和will-change一樣呢?不!

雖然他們都是提升層,但是will-change帶有快取作用,也就是說change的內容會被快取,只有第一動畫回paint之後的重複動畫就不會再繪製,但是translateZ(0)每次動畫都會重新繪製。可以說will-change對於重複動畫很有好了。但是!不要濫用哦~will-change會快取,因此很佔記憶體。大家慎用。

參考連結:

  • youtu.be/cCOL7MC4Pl0 , JS conf上面講evenloop的視訊,裡面的evenloop和渲染的關係對筆者的啟發很大。
  • developer.mozilla.org/zh-CN/docs/… , MDN是研究CSS必備網站。
  • www.w3.org/TR/2016/WD-… , 如果想要深入研究layer這一塊,可以參考CSS的規範,可以又更深入的感受。不過CSS的規範是給大家深入學習CSS的,並不是教大家如何使用的。

注:

原創宣告!全部都是筆者一個字一個字敲出來,程式碼是筆者一個個code出來的。

歡迎轉載,註明出處。

相關文章