24 _ 分層和合成機制:為什么CSS動畫比JavaScript高效?

在上一篇文章中我們分析了CSS和JavaScript是如何影響到DOM樹生成的,今天我們繼續沿著渲染流水線向下分析,來聊聊DOM樹之后所發生的事情。

在前面《05 | 渲染流程(上):HTML、CSS和JavaScript文件,是如何變成頁面的?》文章中,我們介紹過DOM樹生成之后,還要經歷布局、分層、繪制、合成、顯示等階段后才能顯示出漂亮的頁面。

本文我們主要講解渲染引擎的分層和合成機制,因為分層和合成機制代表了瀏覽器最為先進的合成技術,Chrome團隊為了做到這一點,做了大量的優化工作。了解其工作原理,有助于拓寬你的視野,而且也有助于你更加深刻地理解CSS動畫和JavaScript底層工作機制。

顯示器是怎么顯示圖像的

每個顯示器都有固定的刷新頻率,通常是60HZ,也就是每秒更新60張圖片,更新的圖片都來自于顯卡中一個叫前緩沖區的地方,顯示器所做的任務很簡單,就是每秒固定讀取60次前緩沖區中的圖像,并將讀取的圖像顯示到顯示器上。

那么這里顯卡做什么呢?

顯卡的職責就是合成新的圖像,并將圖像保存到后緩沖區中,一旦顯卡把合成的圖像寫到后緩沖區,系統就會讓后緩沖區和前緩沖區互換,這樣就能保證顯示器能讀取到最新顯卡合成的圖像。通常情況下,顯卡的更新頻率和顯示器的刷新頻率是一致的。但有時候,在一些復雜的場景中,顯卡處理一張圖片的速度會變慢,這樣就會造成視覺上的卡頓。

幀 VS 幀率

了解了顯示器是怎么顯示圖像的之后,下面我們再來明確下幀和幀率的概念,因為這是后續一切分析的基礎。

當你通過滾動條滾動頁面,或者通過手勢縮放頁面時,屏幕上就會產生動畫的效果。之所以你能感覺到有動畫的效果,是因為在滾動或者縮放操作時,渲染引擎會通過渲染流水線生成新的圖片,并發送到顯卡的后緩沖區。

大多數設備屏幕的更新頻率是60次/秒,這也就意味著正常情況下要實現流暢的動畫效果,渲染引擎需要每秒更新60張圖片到顯卡的后緩沖區。

我們把渲染流水線生成的每一副圖片稱為一幀,把渲染流水線每秒更新了多少幀稱為幀率,比如滾動過程中1秒更新了60幀,那么幀率就是60Hz(或者60FPS)。

由于用戶很容易觀察到那些丟失的幀,如果在一次動畫過程中,渲染引擎生成某些幀的時間過久,那么用戶就會感受到卡頓,這會給用戶造成非常不好的印象。

要解決卡頓問題,就要解決每幀生成時間過久的問題,為此Chrome對瀏覽器渲染方式做了大量的工作,其中最卓有成效的策略就是引入了分層和合成機制。分層和合成機制代表了當今最先進的渲染技術,所以接下來我們就來分析下什么是合成和渲染技術。

如何生成一幀圖像

不過在開始之前,我們還需要聊一聊渲染引擎是如何生成一幀圖像的。這需要回顧下我們前面《06 | 渲染流程(下):HTML、CSS和JavaScript文件,是如何變成頁面的?》介紹的渲染流水線。關于其中任意一幀的生成方式,有重排、重繪合成三種方式。

這三種方式的渲染路徑是不同的,通常渲染路徑越長,生成圖像花費的時間就越多。比如重排,它需要重新根據CSSOM和DOM來計算布局樹,這樣生成一幅圖片時,會讓整個渲染流水線的每個階段都執行一遍,如果布局復雜的話,就很難保證渲染的效率了。而重繪因為沒有了重新布局的階段,操作效率稍微高點,但是依然需要重新計算繪制信息,并觸發繪制操作之后的一系列操作。

相較于重排和重繪,合成操作的路徑就顯得非常短了,并不需要觸發布局和繪制兩個階段,如果采用了GPU,那么合成的效率會非常高。

所以,關于渲染引擎生成一幀圖像的幾種方式,按照效率我們推薦合成方式優先,若實在不能滿足需求,那么就再退后一步使用重繪或者重排的方式。

本文我們的焦點在合成上,所以接下來我們就來深入分析下Chrome瀏覽器是怎么實現合成操作的。Chrome中的合成技術,可以用三個詞來概括總結:分層、分塊合成

分層和合成

通常頁面的組成是非常復雜的,有的頁面里要實現一些復雜的動畫效果,比如點擊菜單時彈出菜單的動畫特效,滾動鼠標滾輪時頁面滾動的動畫效果,當然還有一些炫酷的3D動畫特效。如果沒有采用分層機制,從布局樹直接生成目標圖片的話,那么每次頁面有很小的變化時,都會觸發重排或者重繪機制,這種“牽一發而動全身”的繪制策略會嚴重影響頁面的渲染效率。

為了提升每幀的渲染效率,Chrome引入了分層和合成的機制。那該怎么來理解分層和合成機制呢?

你可以把一張網頁想象成是由很多個圖片疊加在一起的,每個圖片就對應一個圖層,Chrome合成器最終將這些圖層合成了用于顯示頁面的圖片。如果你熟悉PhotoShop的話,就能很好地理解這個過程了,PhotoShop中一個項目是由很多圖層構成的,每個圖層都可以是一張單獨圖片,可以設置透明度、邊框陰影,可以旋轉或者設置圖層的上下位置,將這些圖層疊加在一起后,就能呈現出最終的圖片了。

在這個過程中,將素材分解為多個圖層的操作就稱為分層,最后將這些圖層合并到一起的操作就稱為合成。所以,分層和合成通常是一起使用的。

考慮到一個頁面被劃分為兩個層,當進行到下一幀的渲染時,上面的一幀可能需要實現某些變換,如平移、旋轉、縮放、陰影或者Alpha漸變,這時候合成器只需要將兩個層進行相應的變化操作就可以了,顯卡處理這些操作駕輕就熟,所以這個合成過程時間非常短。

理解了為什么要引入合成和分層機制,下面我們再來看看Chrome是怎么實現分層和合成機制的。

在Chrome的渲染流水線中,分層體現在生成布局樹之后,渲染引擎會根據布局樹的特點將其轉換為層樹(Layer Tree),層樹是渲染流水線后續流程的基礎結構。

層樹中的每個節點都對應著一個圖層,下一步的繪制階段就依賴于層樹中的節點。在《06 | 渲染流程(下):HTML、CSS和JavaScript文件,是如何變成頁面的?》中我們介紹過,繪制階段其實并不是真正地繪出圖片,而是將繪制指令組合成一個列表,比如一個圖層要設置的背景為黑色,并且還要在中間畫一個圓形,那么繪制過程會生成|Paint BackGroundColor:Black | Paint Circle|這樣的繪制指令列表,繪制過程就完成了。

有了繪制列表之后,就需要進入光柵化階段了,光柵化就是按照繪制列表中的指令生成圖片。每一個圖層都對應一張圖片,合成線程有了這些圖片之后,會將這些圖片合成為“一張”圖片,并最終將生成的圖片發送到后緩沖區。這就是一個大致的分層、合成流程。

需要重點關注的是,合成操作是在合成線程上完成的,這也就意味著在執行合成操作時,是不會影響到主線程執行的。這就是為什么經常主線程卡住了,但是CSS動畫依然能執行的原因。

分塊

如果說分層是從宏觀上提升了渲染效率,那么分塊則是從微觀層面提升了渲染效率。

通常情況下,頁面的內容都要比屏幕大得多,顯示一個頁面時,如果等待所有的圖層都生成完畢,再進行合成的話,會產生一些不必要的開銷,也會讓合成圖片的時間變得更久。

因此,合成線程會將每個圖層分割為大小固定的圖塊,然后優先繪制靠近視口的圖塊,這樣就可以大大加速頁面的顯示速度。不過有時候, 即使只繪制那些優先級最高的圖塊,也要耗費不少的時間,因為涉及到一個很關鍵的因素——紋理上傳,這是因為從計算機內存上傳到GPU內存的操作會比較慢。

為了解決這個問題,Chrome又采取了一個策略:在首次合成圖塊的時候使用一個低分辨率的圖片。比如可以是正常分辨率的一半,分辨率減少一半,紋理就減少了四分之三。在首次顯示頁面內容的時候,將這個低分辨率的圖片顯示出來,然后合成器繼續繪制正常比例的網頁內容,當正常比例的網頁內容繪制完成后,再替換掉當前顯示的低分辨率內容。這種方式盡管會讓用戶在開始時看到的是低分辨率的內容,但是也比用戶在開始時什么都看不到要好。

如何利用分層技術優化代碼

通過上面的介紹,相信你已經理解了渲染引擎是怎么將布局樹轉換為漂亮圖片的,理解其中原理之后,你就可以利用分層和合成技術來優化代碼了。

在寫Web應用的時候,你可能經常需要對某個元素做幾何形狀變換、透明度變換或者一些縮放操作,如果使用JavaScript來寫這些效果,會牽涉到整個渲染流水線,所以JavaScript的繪制效率會非常低下。

這時你可以使用 will-change來告訴渲染引擎你會對該元素做一些特效變換,CSS代碼如下:

.box {
will-change: transform, opacity;
}

這段代碼就是提前告訴渲染引擎box元素將要做幾何變換和透明度變換操作,這時候渲染引擎會將該元素單獨實現一幀,等這些變換發生時,渲染引擎會通過合成線程直接去處理變換,這些變換并沒有涉及到主線程,這樣就大大提升了渲染的效率。這也是CSS動畫比JavaScript動畫高效的原因

所以,如果涉及到一些可以使用合成線程來處理CSS特效或者動畫的情況,就盡量使用will-change來提前告訴渲染引擎,讓它為該元素準備獨立的層。但是凡事都有兩面性,每當渲染引擎為一個元素準備一個獨立層的時候,它占用的內存也會大大增加,因為從層樹開始,后續每個階段都會多一個層結構,這些都需要額外的內存,所以你需要恰當地使用 will-change。

總結

好了,今天就介紹到這里,下面我來總結下今天的內容。

  • 首先我們介紹了顯示器顯示圖像的原理,以及幀和幀率的概念,然后基于幀和幀率我們又介紹渲染引擎是如何實現一幀圖像的。通常渲染引擎生成一幀圖像有三種方式:重排、重繪和合成。其中重排和重繪操作都是在渲染進程的主線程上執行的,比較耗時;而合成操作是在渲染進程的合成線程上執行的,執行速度快,且不占用主線程。
  • 然后我們重點介紹了瀏覽器是怎么實現合成的,其技術細節主要可以使用三個詞來概括:分層、分塊和合成。
  • 最后我們還講解了CSS動畫比JavaScript動畫高效的原因,以及怎么使用 will-change來優化動畫或特效。

思考時間

觀察下面代碼,結合Performance面板、內存面板和分層面板,全面比較在box中使用 will-change和不使用 will-change的效率、性能和內存占用等情況。


<html>

<head>
<title>觀察will-change</title>
<style>
.box {
will-change: transform, opacity;
display: block;
float: left;
width: 40px;
height: 40px;
margin: 15px;
padding: 10px;
border: 1px solid rgb(136, 136, 136);
background: rgb(187, 177, 37);
border-radius: 30px;
transition: border-radius 1s ease-out;
}

    body {font-family: Arial;}
&lt;/style&gt;

</head>

<body>
<div id="controls">
<button id="start">start</button>
<button id="stop">stop</button>
</div>
<div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
<div class="box">旋轉盒子</div>
</div>
<script>

    let boxes = document.querySelectorAll('.box');let boxes1 = document.querySelectorAll('.box1');let start = document.getElementById('start');let stop = document.getElementById('stop');let stop_flag = falsestart.addEventListener('click', function () {stop_flag = falserequestAnimationFrame(render);})stop.addEventListener('click', function () {stop_flag = true})let rotate_ = 0let opacity_ = 0function render() {if (stop_flag)return 0rotate_ = rotate_ + 6if (opacity_ &gt; 1)opacity_ = 0opacity_ = opacity_ + 0.01let command = 'rotate(' + rotate_ + 'deg)';for (let index = 0; index &lt; boxes.length; index++) {boxes[index].style.transform = commandboxes[index].style.opacity = opacity_}requestAnimationFrame(render);}
&lt;/script&gt;

</body>

</html>

歡迎在留言區與我分享你的想法,也歡迎你在留言區記錄你的思考過程。感謝閱讀,如果你覺得這篇文章對你有幫助的話,也歡迎把它分享給更多的朋友。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/19996.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/19996.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/19996.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

linux下can-utils的使用以及can接口的配置(以ubuntu20.04為例)

linux下can-utils的使用以及can接口的配置&#xff08;以ubuntu20.04為例&#xff09; can-utils是什么 can-utils 是一套用于Linux操作系統的開源工具&#xff0c;專門用來處理與CAN&#xff08;Controller Area Network&#xff09;總線相關的任務。CAN總線廣泛應用于汽車和…

C語言文件操作:打開關閉,讀寫

程序文件 源程序文件&#xff08;后綴為.c&#xff09; 目標文件&#xff08;Windows環境后綴為.obj&#xff09; 可執行文件&#xff08;Windows環境后綴為.exe&#xff09; fputc FILE* pf fopen("test.txt","w");if (pf NULL){printf("%s\n"…

深入理解Qt計算器應用的構建過程

新書上架~&#x1f447;全國包郵奧~ python實用小工具開發教程http://pythontoolsteach.com/3 歡迎關注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目錄 一、數字按鈕的信號與槽函數連接 二、運算符按鈕的信號與槽函數連接 三、特殊按鈕的信號與…

紅外超聲波雷達測距(water)

文章目錄 一 RS-232二 RS485三 Modbus四 stm32多路超聲波測距4.1 設計方案4.2 代碼 參考資料總結 實驗要求 一. 采用stm32F103和HC-SR04超聲波模塊&#xff0c; 使用標準庫或HAL庫 定時器中斷&#xff0c;完成1或2路的超聲波障礙物測距功能。 1&#xff09;測試數據包含噪聲&am…

Bezier Python 用法:深入探索與實用指南

Bezier Python 用法&#xff1a;深入探索與實用指南 在數字圖形學和計算機編程中&#xff0c;貝塞爾曲線&#xff08;Bezier Curves&#xff09;是一種重要的參數曲線&#xff0c;被廣泛應用于二維圖形應用程序中&#xff0c;如字體輪廓、矢量圖形和動畫等。Python作為一種功能…

EukRep:區分真核和原核序列

https://github.com/patrickwest/EukRep 安裝 conda create -y -n eukrep-env -c bioconda scikit-learn0.19.2 eukrep mamba install -c conda-forge numpy1.19.5 使用 EukRep -i <Sequences in Fasta format> -o <Eukaryote sequence output fasta file>

【Linux】線程ID

大致草稿—————————— 思維導圖 學習目標 一、線程ID的理解 1.1 引出對tid的理解 我們先來創建一個線程復習一下線程的函數&#xff1a; pthread_t tid; // 創建一個線程 pthread_create(&tid, nullptr, threadrun, (void*)"thread-1"); // 打印出…

二分查找學習:優雅的二分查找——“Leetcode 35. 搜索插入位置”

例題 給定一個排序數組和一個目標值&#xff0c;在數組中找到目標值&#xff0c;并返回其索引。如果目標值不存在于數組中&#xff0c;返回它將會被按順序插入的位置。 請必須使用時間復雜度為 O(log n) 的算法。 示例 1: 輸入: nums [1,3,5,6], target 5 輸出: 2 示例 2…

怎么花草識別?方法有三種!

怎么花草識別&#xff1f;在這個五彩斑斕的世界里&#xff0c;花草是我們生活中不可或缺的一部分。它們點綴著我們的環境&#xff0c;為我們帶來無盡的美麗與驚喜。然而&#xff0c;面對眾多的花草種類&#xff0c;你是否曾感到困惑和迷茫&#xff0c;不知道如何識別它們&#…

VIO System 丨適用于控制器開發前期的測試系統

VIO綜述 嵌入式軟件的HIL測試需要復雜的測試系統及完整的ECU硬件&#xff0c;這導致通常只能在開發流程的后期階段進行測試。全新推出的低成本解決方案VIO System&#xff0c;使得在開發前期不僅可以進行總線通訊測試&#xff0c;也可以同時進行I/O信號測試。 該系統旨在通過…

用 Vim 打造舒適高效的編程體驗

作為程序員,Vim 無疑是最常使用的編輯器之一。它之所以如此受歡迎,得益于其強大的功能和高度可定制的特性。今天,讓我帶大家一起探索如何通過簡單的 .vimrc 配置,打造一個個性化的 Vim 編程環境。 啟用語法高亮 我們首先要確保 Vim 能夠正確地識別和高亮代碼語法。只需在 .vi…

LabVIEW版本控制

LabVIEW作為一種流行的圖形化編程環境&#xff0c;在軟件開發中廣泛應用。有效地管理版本控制對于確保軟件的可靠性和可維護性至關重要。LabVIEW提供了多種方式來管理VI和應用程序的修訂歷史&#xff0c;以滿足不同規模和復雜度的項目需求。 LabVIEW中的VI修訂歷史 LabVIEW內置…

docker安裝Mysql5.7版本

首先Linux系統已經安裝好了docker應用。 1.搜索鏡像 docker search mysql 2.拉取5.7的鏡像 總之,選starts最多的那個就對了。 docker pull mysql:5.7 ~ docker pull mysql:5.7 5.7: Pulling from library/mysql fc7181108d40: Downloading [============> …

mysql創建數據表----centos7.9

mysql創建數據表 查看存在的表 show tables;我這里還未創建任何表所以是這樣的 如有是這樣 若沒有表需要先創建一個表 CREATE DATABASE tb_your_name&#xff1b;創建字段及屬性 CREATE TABLE tb_laws_regulations (id INT AUTO_INCREMENT PRIMARY KEY, -- 文件唯…

柯橋外貿俄語哪里可以學,零基礎俄語培訓

Де?лать 做 из му?хи 從蒼蠅 слона? 大象 我覺得漢語里有一個很合適的詞來形容&#xff1a; Де?лать из му?хи слона? 就是 小題大做&#xff0c;本來是一件很小的事&#xff0c;卻把它形容成天大的事一樣 Хвтит де?…

【UE5.1 角色練習】10-物體抬升、拋出技能 - part2

目錄 前言 效果 步驟 一、讓物體緩慢的飛向手掌 二、向著鼠標方向發射物體 前言 在上一篇&#xff08;【UE5.1 角色練習】08-物體抬升、拋出技能 - part1&#xff09;的基礎上繼續完成角色將物體吸向手掌&#xff0c;然后通過鼠標點擊的方向來發射物體的功能。 效果 步驟…

c#實現BPM系統網絡傳輸接口,http協議,post

BPM通過http協議實現網絡傳輸&#xff0c;語言使用.net(c#)&#xff0c;在這里只提供一個接口&#xff0c;具體代碼如下,請參照&#xff1a; public string MakeRequest(string parameters) { ServicePointManager.ServerCertificateValidationCallback new Syst…

代碼隨想錄算法訓練營第三十二 | ● 122.買賣股票的最佳時機II ● 55. 跳躍游戲 ● 45.跳躍游戲II

122.買賣股票的最佳時機II 講解鏈接&#xff1a;https://programmercarl.com/1005.K%E6%AC%A1%E5%8F%96%E5%8F%8D%E5%90%8E%E6%9C%80%E5%A4%A7%E5%8C%96%E7%9A%84%E6%95%B0%E7%BB%84%E5%92%8C.html 簡單思路&#xff1a;逐個計算連續兩天的股票差值&#xff0c;sum初始為零&…

Spring Task 定時任務

文章目錄 Spring Task 定時任務pom 包配置啟動類開啟定時創建定時任務實現類定時任務 1:定時任務 2: 參數說明fixedRate 說明cron 說明 并行任務 Spring Task 定時任務 在項目開發中&#xff0c;經常需要定時任務來幫助我們來做一些內容&#xff0c;比如定時派息、跑批對賬、業…

【并查集】專題練習

題目列表 - 洛谷 | 計算機科學教育新生態 (luogu.com.cn) 模板 836. 合并集合 - AcWing題庫 #include<bits/stdc.h> using lllong long; //#define int ll const int N1e510,mod1e97; int n,m; int p[N],sz[N]; int find(int a) {if(p[a]!a) p[a]find(p[a]);return p[a…