CSS 與 JS 動畫的底層機制 + 如何優化它們的效能
這是致力於探索 JavaScript/">JavaScript 及其元件的系列文章中的第 13 篇。在找尋與介紹這些核心元件的過程中,我們也分享了我們在開發 ofollow,noindex" target="_blank">SessionStack 時的一些規則(SessionStack 是一個用來幫助使用者實時發現與重現其應用弱點的 JavaScript 應用,因此非常注重魯棒性與高效能。)
如果你沒看過之前的文章,可以看一下這裡:
-
對 WebAssembly 的比較 + 為什麼在某些情況下,它比JavaScript更好
概覽
正如大家所知,動畫在建立引人注目的 web 應用程式中扮演著重要的角色。隨著使用者越來越多地將注意力轉移到使用者體驗上,企業開始意識到完美無缺、令人愉快的使用者體驗的重要性,web應用程式變得越來越重,並具有更動態的UI。這一切都需要更復雜的動畫,以便在在使用者的整個使用過程中更流暢地進行狀態轉換 —— 今天,這甚至被認為不是什麼特別的事情。使用者正變得越來越高階,預設情況下,他們期望具有快速響應和互動性棒的使用者介面。
然而,讓你的介面動起來並不一定是簡單的。什麼互動需要動畫,什麼時候應該動畫,動畫應該有什麼樣的感覺,這些都是棘手的問題。
JavaScript 動畫 vs CSS 動畫
建立web動畫的兩種主要方法是使用JavaScript和CSS,兩者本身沒有對與錯。所以這種 PK 完全取決於你想實現什麼。
CSS 動畫
想讓螢幕上東西動起來的話,最簡單的方法就是 CSS 動畫。
我們從一個小例子開始:把一個元素在 X 和 Y 軸上都位移 50px,通過 CSS transition 來設定一個 1000ms 的動畫
當 move
class 被新增上時, transform
值被改變,運動開始了
除了過渡時間(transition duration),還有一個 緩動(easing) 選項,這就完成了動畫了。我們將會在下面進一步介紹緩動
如果您像上面的程式碼片段一樣建立單獨的CSS類來管理動畫,那麼您可以使用JavaScript來切換每個動畫。
如果你有以下元素:
然後你可以使用JavaScript來切換每個動畫的開關:
上面的程式碼片段獲取box類的所有元素,並新增move類以觸發動畫。
這樣做會給你的應用提供很好的平衡。您可以專注於使用JavaScript管理狀態,並簡單地在目標元素上設定適當的類,讓瀏覽器處理動畫。然後,您可以偵聽元素上的transitionend事件,但前提是您能夠放棄對Internet Explorer舊版本的支援:
偵聽在轉換結束時觸發的轉換事件,如下所示:
除了使用CSS轉換,您還可以使用CSS動畫(animation),這允許您對單個動畫關鍵幀(keyframes)、持續時間和迭代(iterations)有更多的控制。
關鍵幀用於指示瀏覽器在給定的點上CSS屬性需要具有哪些值,並填補空白。
讓我們看一個例子:
這是它的樣子(quick demo)—— https://sessionstack.github.io/blog/demos/keyframes/
使用CSS動畫,您可以將定義動畫與元素本身分離開,並使用 animation-name
屬性選擇所需的動畫。
CSS動畫有時還需要加供應商字首,在Safari、Safari Mobile和Android中使用-webkit。Chrome、Opera、Internet Explorer和Firefox都沒有字首。許多工具可以幫助您建立所需CSS的字首版本,允許您在原始檔中編寫無字首版本。
JavaScript 動畫
與使用CSS轉換或動畫相比,使用JavaScript建立動畫更加複雜,但它通常會為開發人員提供更強大的功能。
JavaScript動畫是作為程式碼的一部分編寫的。您還可以將它們封裝在其他物件中。要重新建立前面描述的CSS轉換,可以用 JavaScript 這麼搞:
預設情況下,Web動畫只修改元素的表示。如果您想讓您的物件保持在它被移動到的位置,那麼您應該在動畫完成時修改它的底層樣式。這就是為什麼我們在上面的例子中監聽 finish 事件,並設定 box.style
。將屬性轉換為相等的 translate(150px, 200px)
,這與動畫執行的第二個轉換相同。
使用JavaScript動畫,您可以在每一步完全控制元素的樣式。這意味著您可以減慢動畫、暫停動畫、停止動畫、反轉動畫,並根據需要操作元素。如果您正在構建複雜的、面向物件的應用程式,這尤其有用,因為這時你往往需要對動畫有一個更好的封裝。
緩動是什麼?
更自然的動效能讓使用者對你的web應用程式感到更舒服,從而帶來更好的使用者體驗。
在大自然中,其實沒有什麼東西是從一點線性地移動到另一點的。事實上,因為我們不是在真空中,會有不同的因素導致物體在我們周圍的物理世界中運動時會加速或減速。人類的大腦天生就期待這種運動,所以當你在為網路應用程式製作動畫時,你應該充分理解這一點。
有一些術語你需要理解:
-
“ease in” — 這是一個開始緩慢,然後加速的運動。
-
“ease out” —這是一個開始很快,然後減速的運動
這兩者可以組合在一起,例如 “ease in out”.
緩動讓你的動畫看起來更自然。
緩動的關鍵詞
CSS轉換和動畫允許您選擇您想要使用的緩動型別。有不同的關鍵字影響緩動效果。你也可以完全定製自己的緩動動畫。
下面是一些你可以在CSS中使用的緩動關鍵字:
-
linear(線性)
-
ease-in(加速)
-
ease-out(減速)
-
ease-in-out(先加速後減速)
讓我們把它們通讀一遍,看看它們到底是什麼意思。
Linear 動畫
沒有任何緩動效果的動畫稱為 線性(linear) 動畫。
下面是線性變化的示意圖:
隨著時間的推移,值均勻變大。線性運動時,物體往往會看起來不自然。一般來說,你應該避免線性運動。
這是一個線性動畫的簡單實現:
減速(Ease-out) 動畫
正如前面提到的,與線性動畫相比,緩動會使動畫開始得更快,而在最後會變慢。它的示意圖是這樣的:
一般來說,對於UI工作來說,減速緩動效果最好,因為快速的開始會給動畫一種響應的感覺,而結尾的慢速則可以理解為是運動末尾的自然效果。
有很多方法可以達到放鬆效果,但最簡單的是CSS中的 easy-out
關鍵字:
transition: transform 500ms ease-out;
加速(Ease-in)動畫
這與減速(ease-out)動畫相反——開始慢,結束快。這是示意圖:
與減速緩動動畫相比,加速緩動動畫會給人一種不尋常的感覺,因為它們啟動緩慢,會產生一種無響應的感覺。結束時運動很快也會讓使用者產生一種奇怪的感覺,因為現實世界中的物體在趨於停止時往往會減速。
要使用加速緩動動畫,與使用減速緩動與線性動畫一樣,你可以用如下關鍵字:
transition: transform 500ms ease-in;
(先加速後減速)Ease-in-out 動畫
這個動畫是加速動畫和減速動畫的結合。示意圖如下:
不要讓動畫持續時間太長,因為這會讓你的 UI 看起來像是沒有響應。
要使用先加速後減速動畫, 可以使用如下關鍵字:
transition: transform 500ms ease-in-out;
自定義緩動
您可以定義自己的緩動曲線,以更好的控制緩動效果。
事實上, ease-in, ease-out, linear, ease 關鍵字都是在 貝塞爾曲線(Bézier curves) 預定義好的, 這三個關鍵字對應的曲線在 CSS transitions specification 與 Web Animations specification 都有詳細的說明
貝塞爾曲線(Bézier curves)
讓我們看看貝塞爾曲線的是怎麼工作的。一條貝塞爾曲線需要四個值,或者更準確地說,它需要兩對數字。每一對描述了貝塞爾曲線控制點的X和Y座標。Bezier曲線的起始點座標為(0,0),結束點座標為(1,1)。這兩個控制點的X值必須在[0,1]範圍內,而雖然規範沒有明確具體是多少,但每個控制點的Y值是可以超過[0,1]限制的。
即使每個控制點的X和Y值稍有變化,也會得到完全不同的曲線。讓我們來看看兩幅貝塞爾曲線的圖形,這兩幅曲線上的點雖然距離很近,但座標不同。
再看這個:
如你所見,這兩張圖大相徑庭。第一個控制點有(0.045,0.183)的向量差,第二個控制點有(-0.427,-0.054)的向量差。
第二個曲線的CSS是這樣的:
transition: transform 500ms cubic-bezier(0.465, 0.183, 0.153, 0.946);
前兩個數字是第一個控制點的X和Y座標,後兩個數字是第二個控制點的X和Y座標。
效能優化
動畫應該保持60fps,否則會對使用者體驗造成負面影響。
和世界上的其他事物一樣,動畫也不是沒有代價的。動畫的一些屬性要比其他屬性代價更小一些。例如,動畫元素的寬度和高度會改變其幾何形狀,並可能導致頁面上的其他元素移動或改變大小。這個過程稱為佈局。我們在 之前的一篇文章 中更詳細地討論了佈局和渲染。
通常,您應該避免動畫的屬性觸發 reflow 或 repaint。對於大多數現代瀏覽器,這意味著將動畫限制在了 opacity 和 transform 上。
Will-change
您可以使用will-change通知瀏覽器您打算更改元素的屬性。這允許瀏覽器在進行更改之前進行最適當的優化。但是,不要過度使用will-change,因為這樣做會導致瀏覽器浪費資源,從而導致更多的效能問題。
為 transforms
和 opacity
新增 will-change 如下所示:
.box { will-change: transform, opacity; }
這項特性在 Chrome、Firefox和Opera 中都支援的很好。
在 JavaScript 與 CSS 中的選擇
你可能已經預料到了——這個問題沒有正確答案。你只需要記住以下幾件事:
-
基於css的動畫,以及原生支援的Web動畫,通常在名為“合成執行緒”的執行緒上處理。它不同於瀏覽器的“主執行緒”,在“主執行緒”中執行樣式、佈局、繪製和JavaScript。這意味著,如果瀏覽器在主執行緒上執行一些昂貴的任務,這些動畫可以繼續執行而不會被中斷。
-
transforms 與 opacity 的變化,在很多情況下,也可以被合成執行緒來處理。
-
如果任何動畫觸發了 repaint 、reflow 或兩者,則需要“主執行緒”來完成工作。無論動畫基於CSS,還是基於 JavaScript,都是如此,repaint 或 reflow 的開銷可能大過你的 CSS 和 JavaScript 執行邏輯,從而使動畫本身變得毫無意義。
選擇合適的東西來製作動畫
很棒的動畫可以為你的使用者新增樂趣和互動性。你可以對你喜歡的任何東西(無論寬度、高度、位置、顏色或背景)加動畫,但你需要注意潛在的效能瓶頸。選擇不當的動畫會對使用者體驗產生負面影響,因此動畫需要有效而適度。動畫越少越好。動畫只是為了讓你的使用者體驗感覺自然,但不要過度動畫。
使用動畫來支援互動
不要因為你能做就去做動畫。相反,你的目的是使用巧妙的動畫來加強使用者互動。要避免你的動畫不必要地打斷或阻礙了使用者活動。
避免動畫的那些昂貴屬性
唯一比動畫放置不當更糟的是那些導致頁面卡頓的動畫。這種型別的動畫會讓使用者非常不爽。
在 SessionStack 中使用動畫非常簡單。一般來說,我們遵循上面提到的實踐,但是由於UI的複雜性,我們也有更多使用動畫的場景。SessionStack必須將終端使用者在瀏覽web應用程式時遇到問題時發生的一切重建為視訊。為此,SessionStack 僅利用我們的庫在會話期間收集的資料:使用者事件、DOM更改、網路請求、異常、除錯訊息等。我們的播放器經過高度優化,能夠正確呈現和使用收集到的所有資料,以便從視覺和技術角度對終端使用者的瀏覽器及其中發生的一切進行畫素級的完美模擬。
為了確保複製的感覺是自然的,特別是在使用者會話又長又重的情況下,我們使用動畫來適當地指示載入/緩衝,並遵循關於如何實現它們的最佳實踐,這樣我們就不會佔用太多CPU時間,讓 event loop 保持空閒來呈現會話。
如果你願意,有一個免費的計劃 試一試 sessionstack .