文章目錄
- CPU、GPU和硬體加速
- The Old:translateZ()(or translate3d())Hack
- The New:值得稱道的will-change
- will-change使用及注意事項
- 不要宣告太多元素
- 給瀏覽器足夠的時間
- 更改完成后洗掉
- 在樣式表中謹慎使用
- 屬性值
- 除了對瀏覽器的渲染提示之外,will-change 會影響它宣告的元素嗎?
- 寫在最后
如果你曾經注意到過基于WebKit的瀏覽器在執行某些CSS操作(尤其是transform和animation)時出現的“閃爍”,那么你很可能接觸過“硬體加速”這個術語,
CPU、GPU和硬體加速
簡而言之:硬體加速意味著圖形處理單元(GPU)會協助你的瀏覽器渲染網頁,做一些繁重的作業,而不是把這些作業全部丟給中央處理單元(CPU)來完成,當一個CSS操作被硬體加速時,它的速度也會因為頁面的渲染速度變快而得到提升,
正如它們的名字所示,CPU和GPU都是處理單元,CPU位于計算機的主板上,它被稱為計算機的🧠,GPU位于計算機的顯卡上,負責處理和渲染影像,此外,GPU是專門為執行復雜的數學和幾何計算而設計的,這些計算也是圖形渲染的必要條件,因此,將操作丟給GPU處理可以產生巨大的性能提升,也可以減少移動設備上的CPU爭用,
硬體加速(又稱GPU加速)依賴于瀏覽器渲染頁面時使用的分層模型,當對頁面上的元素進行某些操作(比如3D變換)時,該元素會被移動到屬于它自己的“圖層”,在那里它可以獨立于頁面的其他部分進行渲染,并在之后被合成(畫到螢屏上),這樣就隔離了內容的渲染,如果該元素的變換是幀之間唯一的變化,那么頁面的其他部分就不必重新渲染,這樣往往能夠提供明顯的速度優勢,不過這里需要說明一下:只有3D變換擁有屬于自己的圖層,2D變換沒有,
CSS的animation、transform和transition不會自動進行GPU加速,一般是由瀏覽器緩慢的軟體渲染引擎執行,然而有些瀏覽器通過某些屬性提供硬體加速,以獲得更好的渲染性能,
例如,opacity不透明度就是少數可以適當加速的CSS屬性之一,因為GPU可以很容易地操縱它,基本上,如果你在transition或animation里淡化不透明度,瀏覽器會智能地把它丟給GPU操作,速度會非常快,不透明度是所有CSS屬性里性能最好的之一,你使用它不會出現任何問題,其他常見的硬體加速操作是CSS的3D變換,

The Old:translateZ()(or translate3d())Hack
很長時間以來,我們一直在使用 translateZ() (or translate3d()) 這種黑客行為(有時也稱為null transform hack)來欺騙瀏覽器將animation和transform推給硬體加速,
例如一個二維空間的元素可以添加這個簡單的規則來進行硬體加速:
transform: translate3d(0, 0, 0);
硬體加速操作會創建一個所謂的合成渲染層(compositor layer),并上傳到GPU進行合成,然而,強行創造一個圖層并不能解決某些頁面上的性能瓶頸,
雖然圖層創建技術可以提升頁面速度,但有一定代價:它們會占用系統RAM和GPU上的記憶體(在移動設備上很有限),而且擁有大量的圖層會產生不好的影響(尤其是在移動設備上),所以必須明智地使用它們,確保其可以真正幫助頁面性能,而且性能瓶頸不是由你頁面上的其他操作造成的,
為了避免圖層創建的黑客行為,我們引入了一個新的CSS屬性will-change,它允許我們提前告知瀏覽器我們可能會對一個元素做出什么樣的改變,從而使瀏覽器可以提前為元素做一些適當的優化,例如,在影片實際開始之前進行一些潛在的大量的準備作業,

The New:值得稱道的will-change
will-change屬性允許你提前告知瀏覽器你可能會對一個元素進行什么樣的改變,這樣它就可以提前設定適當的優化,避免了可能會對頁面的回應性產生負面影響的啟動成本,這些元素可以更快地被改變和渲染,頁面將能夠迅速地更新,從而帶來更流暢的體驗,
例如,當在一個元素上使用CSS三維變換時,該元素及其內容可能會升到一個新的圖層,就像前面提到的那樣,之后才會被合成(繪制到螢屏上),然而,在一個新的圖層中設定元素是一個代價相對昂貴的操作,這可能會使變換影片的開始時間延遲幾分之一秒,導致明顯的“閃爍”,
為了避免這種延遲,你可以在變化實際發生前的一段時間通知瀏覽器,這樣,瀏覽器就會有一些時間為這些變化做準備,當這些變化發生時,元素的圖層就會準備好,變換影片就可以執行,然后元素就可以被渲染,頁面就會迅速更新,
使用will-change,向瀏覽器提示即將發生的變換,可以在你期望被變換的元素上添加這個簡單的規則:
will-change: transform;
你也可以向瀏覽器宣告,你打算改變一個元素的滾動位置(該元素在可見滾動視窗中的位置,以及在該視窗中的可見程度)、或者改變其內容、一個或多個CSS屬性值,方法是指定你期望改變的屬性的名稱,如果你期望或計劃改變一個元素的多個項,你可以提供一個逗號分隔的值的串列,
例如,如果你將會改變元素位置,你可以這樣向瀏覽器宣告:
will-change: transform, opacity;
指定你想要改變的具體內容,可以讓瀏覽器更好地優化為這些特定的變化,這顯然提升速度的一個更好的方法,而不必訴諸于hack以及強迫瀏覽器進行不必要圖層的創建,

will-change使用及注意事項
知道了will-change的作用后,你可能會想:“只要讓瀏覽器優化所有的東西就行了!” 這個想法很合理,誰不希望他們所有的變化都被優化,并在需要時準備好?
盡管will-change很🐂🍺,但它與其他種類的“權力”沒有任何區別,濫用它會導致性能上的打擊,可能使你的頁面崩潰,
與任何優化一樣,will-change也有其不能直接察覺的副作用(畢竟它只是一種在幕后與瀏覽器對話的方式),所以它的使用可能很棘手,這里有一些你在使用這個屬性時需要注意的事情,以確保你能得到它的最佳效果,同時避免濫用帶來的傷害,
不要宣告太多元素
正如我前面提到的,如果告訴瀏覽器對可能發生在所有元素上的所有屬性的變化進行優化:
*,
*::before,
*::after {
will-change: all;
}
盡管這看起來很好,而且一開始來說很有意義,但事實上這非常有害,更多的操作是無效的,不僅all關鍵字是will-change的無效值(我們將在文章后面介紹有效和無效值的串列),而且這樣一攬子規則也并沒有用,
瀏覽器已經盡可能地優化了一切(還記得opacity和3D變換嗎?),所以這樣做并沒有真正改變什么,也沒有任何幫助,事實上,這樣做有可能會占用機器的大量資源,如果過度使用,會導致頁面變慢甚至崩潰,
換句話說,讓瀏覽器對可能發生也可能不發生的變化保持準備狀態而不是什么好事,Don’t do it.
給瀏覽器足夠的時間
will-change屬性之所以叫做will是因為:通知瀏覽器即將發生的變化,而不是正在發生的變化,
使用will-change,我們要求瀏覽器對我們所宣告的變化進行某些優化,為了實作這一目標,瀏覽器需要一些時間來實際進行這些優化,這樣,當變化發生時,優化就可以毫無延遲地應用,
在一個元素發生變化之前立即對其設定will-change,幾乎沒有任何效果,(它實際上可能比不設定更糟,可能會有一個新圖層的成本,而你正在做的影片以前并沒有資格做一個新層!)
例如,如果一個變化將在懸停時發生:
.element:hover {
will-change: transform;
transition: transform 2s;
transform: rotate(30deg) scale(1.5);
}
…告訴瀏覽器為已經發生的變化進行優化是沒有用的,這樣做是否定了will-change背后的整個概念,相反,你應該找到一種方法,至少可以稍微提前預測會發生變化的東西,并在那時設定will-change,
例如,如果一個元素在被點擊的時候會發生變化,那么就在該元素被懸停的時候設定will-change,這樣就給了瀏覽器足夠的時間來優化,從用戶懸停該元素到實際點擊該元素之間的時間足以讓瀏覽器進行優化設定,因為人類的反應時間相對較慢,所以在變化實際發生之前,瀏覽器會有大約200ms的時間視窗,這足以讓它進行優化設定,
.element {
transition: transform 1s ease-out;
}
.element:hover {
will-change: transform;
}
.element:active {
transform: rotateY(180deg);
}
但如果你希望變化發生在懸停時,而不是點擊時呢?上面的宣告將是無用的,在這種情況下,往往還是可以找到一些方法來預測動作發生之前的情況,
例如,懸停變化元素的祖先可能會提供足夠的準備時間:
.element {
transition: opacity .3s linear;
}
/* 當滑鼠進入或懸停在其祖先時,宣告該元素的變化 */
.ancestor:hover .element {
will-change: opacity;
}
/* 當元素被懸停時的變化 */
.element:hover {
opacity: .5;
}
然而,懸停祖先并不總是表明該元素肯定會被互動,所以你可以做一些事情,比如當視圖在你的應用程式中變得活躍時,或者如果該元素在視口的可見部分內,設定will-change,這將增加該元素被互動的機會,

更改完成后洗掉
瀏覽器為即將發生的變化所做的優化通常會占用機器的很多資源,通常要洗掉這些優化盡快恢復到正常行為,然而,will-change覆寫了這一行為,它維持優化的時間比瀏覽器所做的要長很多,
因此,你應該始終記得在元素變化完成后洗掉will-change,這樣瀏覽器就可以恢復優化所占用的資源,
如果在樣式表中宣告了will-change,就不可能洗掉它,這就是為什么建議你用JavaScript設定和取消它,通過腳本,你可以向瀏覽器宣告你的修改,然后在修改完成后,通過監聽這些修改完成的時間來洗掉will-change,
例如,就像我們在上一節的樣式規則中所做的那樣,你可以監聽元素(或其祖先)何時被懸停,然后在滑鼠進入時設定will-change,如果你的元素正在被影片化,你可以使用DOM事件animationEnd來監聽影片何時結束,然后在animationEnd被觸發時移除will-change,
// 一個例子
// 獲取點擊時將被影片化的元素,例如
var el = document.getElementById('element');
// 設定元素被懸停時的變化
el.addEventListener('mouseenter', hintBrowser);
el.addEventListener('animationEnd', removeHint);
function hintBrowser() {
// 在影片的關鍵幀中要改變的可優化的屬性
this.style.willChange = 'transform, opacity';
}
function removeHint() {
this.style.willChange = 'auto';
}
Craig Buckler寫了一篇關于在JavaScript中捕捉CSS影片事件的文章,如果你不熟悉這個,可以去看看,CSS-Tricks上也有一篇關于控制CSS影片和過渡的文章,也值得一看,
在樣式表中謹慎使用
正如我們在上一節中所看到的,will-change可以用來提示瀏覽器在幾毫秒內某個元素即將發生的變化,這就是在樣式表中宣告will-change的用例之一,盡管我們建議使用JavaScript來設定和取消will-change,但在某些情況下,在樣式表中設定它(并保持它)是合適的,
一個例子:在一些元素上設定will-change,這些元素可能會被用戶反復互動,并且應該以一種快速的方式回應用戶的互動,有限的元素數量意味著瀏覽器所做的優化不會被過度使用,因此不會有太大的傷害,
例如,通過在用戶要求時將側邊欄滑出的方式來改造它,下面的規則就很合適:
.sidebar {
will-change: transform;
}
另一個例子:在不斷變化的元素上使用will-change,比如一個回應用戶滑鼠移動的元素,隨著滑鼠的移動在螢屏上移動,在這種情況下,只需在樣式表中宣告will-change的值就可以了,因為它準確地描述了該元素將定期/不斷地變化,所以應該保持優化,
.annoying-element-stuck-to-the-mouse-cursor {
will-change: left, top;
}

屬性值
will-change有四個屬性值:auto,scroll-position,contents 和 <custom-ident>,
<custom-ident> 值用于指定你期望改變的一個或多個屬性的名稱,多個屬性用逗號隔開,
下面是帶有指定屬性名稱的有效的will-change宣告的例子,
will-change: transform;
will-change: opacity;
will-change: top, left, bottom, right;
<custom-ident>:排除了關鍵字will-change、none、all、auto、scroll-position和contents,還有一些通常被排除在外的關鍵字,所以,正如我們在文章開頭提到的,will-change: all的宣告是無效的,會被瀏覽器忽略,
auto并沒有特別的意思,除了通常的優化外,瀏覽器不會設定任何特別的優化,
scroll-position:顧名思義,表示你希望在不久的將來隨時改變一個元素的滾動位置,這個值很有用,在使用時瀏覽器會準備并渲染可滾動元素上滾動視窗中可見內容之外的內容,瀏覽器通常只渲染滾動視窗中的內容,以及超過該視窗的部分內容,以平衡因跳過渲染而節省的記憶體和時間以及使滾動看起來更漂亮,使用will-change: scroll-position,它可以做進一步的渲染優化,從而使更長或更快的內容滾動可以順利地完成,
contents:預計該元素的內容會發生變化,瀏覽器通常會隨著時間的推移快取元素的渲染,因為大多數東西并不經常改變,或者只改變它們的位置,這個值會被瀏覽器解讀為一個信號,即減少對該元素的快取,或者完全避免對該元素的快取,因為如果該元素的內容經常變化,那么保持對內容的快取將是無用且浪費時間的,所以瀏覽器會直接停止快取,只要該元素的內容發生變化,就繼續從頭渲染,
除了對瀏覽器的渲染提示之外,will-change 會影響它宣告的元素嗎?

會或者不會取決于你所宣告并告知瀏覽器的屬性,如果一個屬性的任何非初始值都會在該元素上創建一個stacking context ,那么在will-change中指定該屬性將在該元素上創建一個層疊背景關系,
注:如果一個元素含有層疊背景關系,我們可以理解為這個元素在z軸上的level更高一級,換句話說就是于網頁中元素級別更高,離用戶更近了,
例如,clip-path和opacity屬性,當它們的值不是初始值時,都會在被應用的元素上創建一個層疊背景關系,因此,使用這些屬性中的一個(或兩個)作為will-change的值會在元素上創建一個層疊背景關系,甚至在變化實際發生之前創建,這同樣適用于其他會在元素上創建層疊背景關系的屬性,
另外,一些屬性可以導致為固定位置的元素創建一個包含塊,例如,一個被轉換的元素會為其所有定位的子元素創建一個包含塊,即使是那些已經被設定為position: fixed的元素,所以,如果一個屬性導致了一個包含塊的產生,那么把它指定為will-change的值也會導致固定位置元素產生一個包含塊,
注:一般情況下,CSS中一個元素的位置和尺寸的計算都相對于一個矩形,這個矩形被稱為包含塊,
如前所述,will-change的一些變化會導致創建一個新的合成層,然而,GPU并不支持大多數瀏覽器中CPU所做的子像素抗鋸齒,有時會導致內容模糊(尤其是文字),
除此之外,will-change屬性對它所指定的元素沒有直接影響,它只是對瀏覽器的一個渲染提示,允許它為該元素發生的變化進行優化設定,除了在上述情況下創建堆疊背景關系和包含塊之外,它對元素沒有直接影響,
寫在最后
欲戴王冠,必承其重,will-change不應該被輕視,但也需要明智地使用,在這一點上,參考will-change規范編輯Tab Atkins Jr的話:
Set
will-changeto the properties you’ll actually change, on the elements that are actually changing. And remove it when they stop.
感謝你的閱讀!
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/306278.html
標籤:其他
