自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

圖解Chrome:HTML/CSS/JS是如何在瀏覽器中,渲染成你看到的頁面?

開發(fā) 開發(fā)工具
Chrome 算是程序員的標(biāo)配了,從全球的市場份額來看,它在全球市場的份額已經(jīng)超過 60%。

Chrome 算是程序員的標(biāo)配了,從全球的市場份額來看,它在全球市場的份額已經(jīng)超過 60%。

在 Chrome 10 周年之際,官方發(fā)布了一個(gè)系列文章,用圖解的方式,很清晰的講解了現(xiàn)代瀏覽器的運(yùn)行原理。

渲染器進(jìn)程的內(nèi)部工作原理

本系列分為 4 個(gè)部分,主要講解關(guān)于現(xiàn)代瀏覽器的運(yùn)行原理,本文為該系列的第 3 篇。在之前的文章中,我們介紹了多進(jìn)程架構(gòu)和導(dǎo)航的完整流程,而在這篇文章中,我們將探究在渲染器進(jìn)程的內(nèi)部,到底發(fā)生了什么。

渲染器進(jìn)程涉及到 Web 性能相關(guān)的多個(gè)方面,由于渲染器進(jìn)程中處理了很多的邏輯,不是一篇文章可以全面講解的,因此本文僅作為一個(gè)概述。如果你有興趣深入研究,可以在《Why Performance Matters》這篇文章里找到更多的資料。

渲染器進(jìn)程處理Web內(nèi)容

所有選項(xiàng)卡內(nèi)發(fā)生的邏輯,都由渲染器進(jìn)程負(fù)責(zé)。在渲染器進(jìn)程中,主線程處理了服務(wù)器發(fā)送給用戶的大部分代碼。如果你使用到 Web Workder 或者Service Worker,那 JavaScript 中的這部分代碼,將由工作線程處理。Compositor(合成器) 和 Raster(光柵) 線程也在渲染器內(nèi)運(yùn)行,從而實(shí)現(xiàn)高效、流暢的渲染頁面。

渲染器進(jìn)程的核心工作是將 HTML,CSS 和 JavaScript 轉(zhuǎn)換為用戶可以與之交互的網(wǎng)頁。

上圖中,描述了具有主線程、工作線程、Compositor 線程、Raster 線程的渲染器進(jìn)程,以及他們之間的關(guān)系。

解析

構(gòu)建 DOM

當(dāng)渲染器進(jìn)程收到一個(gè)導(dǎo)航請(qǐng)求,并開始接收 HTML 數(shù)據(jù),主線程將開始處理文本字符串(HTML),將其解析成 DOM(Document Object Model)。

DOM 是 Web 頁面的內(nèi)部的邏輯樹文檔結(jié)構(gòu),Web 開發(fā)人員可以通過 JavaScript 腳本與之交互數(shù)據(jù),以及通過標(biāo)準(zhǔn) API 來操作 DOM 節(jié)點(diǎn)。

將 HTML 文檔解析成 DOM 是完全依照于 HTML 協(xié)議。并且在 HTML 協(xié)議中,瀏覽器不會(huì)對(duì)錯(cuò)誤的 HTML 進(jìn)行錯(cuò)誤提示。例如,缺少結(jié)束的 </p> 標(biāo)簽時(shí),這依然是一個(gè)有效的 HTML。類似 Hi! <b>I'm <i>Chrome</b>!</i> 中,b 標(biāo)簽在 i 標(biāo)簽之前關(guān)閉這樣的錯(cuò)誤,會(huì)被 HTML 理解為 Hi! <b>I'm <i>Chrome</i></b><i>!</i>。這是因?yàn)?HTML 規(guī)范的主要原則是優(yōu)雅的處理這些錯(cuò)誤,而不是嚴(yán)格檢查。

如果你對(duì)這些規(guī)范感到好奇,可以閱讀 HTML 規(guī)范中的 “解析器中的錯(cuò)誤處理和奇怪案例介紹” 部分。

解析器中的錯(cuò)誤處理和奇怪案例介紹:

https://html.spec.whatwg.org/multipage/parsing.html#an-introduction-to-error-handling-and-strange-cases-in-the-parser

子資源加載

一個(gè)完整的 Web 站點(diǎn)通常會(huì)包含圖片、CSS 和 JS 等外部資源,這些文件都需要從網(wǎng)絡(luò)或者本地緩存中加載。主線程可以在解析構(gòu)建 DOM 的時(shí)候,將他們逐個(gè)請(qǐng)求,但是為了加快速度,會(huì)同時(shí)使用 “預(yù)加載掃描(Preload Scanner)”。

如果 “預(yù)加載掃描” 發(fā)現(xiàn)有類似 <img> 或 <link> 這樣的標(biāo)簽時(shí),會(huì)由 HTML 解析器對(duì)該資源生成一個(gè) Tokens,然后在瀏覽器進(jìn)程中,通過網(wǎng)絡(luò)或者本地緩存來加載資源。

上圖描述了,主線程解析 HTML 并構(gòu)建 DOM 樹的過程。

JS 可以阻止解析

當(dāng) HTML 解析器遇到 <script> 標(biāo)簽的時(shí)候,它會(huì)暫停解析 HTML 文檔,然后對(duì)這個(gè) JS 腳本進(jìn)行加載、解析和執(zhí)行。

這么設(shè)計(jì)的原因,是因?yàn)?JS 可以使用類似 document.write() 方法來改變 DOM 的結(jié)構(gòu)。這就是 HTML 解析器在重新解析 HTML 之前,必須等待 JS 腳本執(zhí)行的原因。

如果你對(duì) JS 執(zhí)行中發(fā)生的事情細(xì)節(jié)有興趣,V8 團(tuán)隊(duì)有一篇文章深入的對(duì)此進(jìn)行了講解,有興趣可以看看。

V8 團(tuán)隊(duì)深入研究:

https://mathiasbynens.be/notes/shapes-ics

提示瀏覽器如何加載資源

HTML 遇到 JS 腳本則暫停對(duì) HTML 的解析,這并不是絕對(duì)的。

Web 開發(fā)人員可以通過多種方式的配置,告知瀏覽器如何更優(yōu)雅的加載資源。如果你的 JS 腳本中,沒有使用到類似 document.write() 這樣的方法,你可以在 script 標(biāo)簽中添加 async 或 defer 標(biāo)記,然后瀏覽器會(huì)異步加載和運(yùn)行此 JS 腳本,不會(huì)阻斷解析。如果需要,也可以使用 JavaScript Modules,還可以通過 <link rel="preload"> 標(biāo)簽向?yàn)g覽器明確標(biāo)記此為重要的資源,將在頁面加載完成之后被立刻使用,對(duì)于這類資源,它會(huì)在頁面加載生命周期的早期,被優(yōu)先加載。

樣式渲染(Style)

僅僅解析成 DOM,還不足以完成頁面渲染,因?yàn)檫€可以通過在 CSS 中,設(shè)置元素的樣式來豐富渲染效果。

主線程將解析 CSS,并將效果渲染到指定的 DOM 節(jié)點(diǎn)上,關(guān)于 CSS 選擇器如何定位到指定的 DOM 節(jié)點(diǎn),可以通過 DevTools 來查看相關(guān)信息。

上圖中,主線程解析 CSS 并添加渲染樣式。

即使你不使用任何 CSS 樣式,每個(gè) DOM 節(jié)點(diǎn)依然存在默認(rèn)的渲染樣式。例如,h1 標(biāo)簽在視覺上就大于 h2 標(biāo)簽,并且每個(gè)元素還有默認(rèn)的邊距。這是因?yàn)闉g覽器具有默認(rèn)樣式表。

如果你對(duì) Chrome 的默認(rèn) CSS 是什么樣的有興趣,可以在源碼中看到具體細(xì)節(jié)。

Chrome 的默認(rèn) CSS:

https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/css/html.css

布局(Layout)

到現(xiàn)在,渲染器進(jìn)程知道每個(gè) DOM 的結(jié)構(gòu)和樣式了,但是這依然不足以渲染頁面。想象一下,你正視圖通過文字向朋友描述一副畫,“有一個(gè)大的紅色圓圈和一個(gè)小的藍(lán)色方塊”,這些信息不足以讓你的朋友還原這幅畫。

這就牽扯到布局(Layout),布局是對(duì)元素定位的過程,主線程遍歷 DOM 并計(jì)算樣式,然后創(chuàng)建布局樹(Layout Tree),在布局樹中,包含 X、Y 坐標(biāo)和邊框大小等信息。布局樹是一個(gè)與 DOM 樹類似的結(jié)構(gòu),但是它僅僅包含了頁面上可見內(nèi)容相關(guān)的信息。

舉個(gè)例子,如果某個(gè)元素設(shè)置了 display:none,則該元素將不會(huì)出現(xiàn)在布局樹中,但是它會(huì)出現(xiàn)在 DOM 樹中,而如果該元素被設(shè)置為 visibility:hidden 則它會(huì)存在于布局樹中。類似的例子還有 p::before{content:"Hi!"}  這樣的偽類,它會(huì)存在于布局樹中,而不會(huì)存在于 DOM 樹中。

如上圖所示,在主線程中渲染樣式,并生成布局樹和 DOM 樹。

計(jì)算頁面布局是一個(gè)很復(fù)雜的工作,即使最簡單的從上到下的塊流結(jié)構(gòu),也必須考慮字體的大小以及如何劃分每一塊,因?yàn)樗鼈儠?huì)影響當(dāng)前段落的大小和形狀,然后影響下一塊所在的位置。

CSS 樣式可以設(shè)置元素浮動(dòng)到某一側(cè)、隱藏 overflow 的元素,或者改變排版方向。布局是一個(gè)非常復(fù)雜的工作,在 Chrome 中,有一個(gè)完整的工程師團(tuán)隊(duì)負(fù)責(zé)布局。如果你的對(duì)他們工作的細(xì)節(jié)感興趣,可以參閱 BinkOn 會(huì)議的記錄。

BinkOn:

https://www.youtube.com/watch?v=Y5Xa4H2wtVA

繪制(Paint)

擁有 DOM、CSS 和 LayoutTree 仍然不足以渲染頁面。假設(shè)你正在嘗試重繪一幅畫,你除了需要知道元素的大小、外觀和位置之外,還需要知道它們的繪制順序。

例如:z-index 屬性將改變?cè)氐膶蛹?jí),在這種情況下,按 HTML 中編寫的元素順序進(jìn)行繪制,將導(dǎo)致渲染結(jié)果和預(yù)期不符。

如上圖所示,因?yàn)闆]有正確的考慮 z-index,將導(dǎo)致頁面被錯(cuò)誤的渲染。

在這個(gè)繪制的過程中,主線程遍歷布局樹,然后創(chuàng)建繪制記錄。繪制記錄是一個(gè)繪制過程的注釋,例如“背景優(yōu)先,然后是文本,***是矩形”。如果你曾經(jīng)使用 JS 在 <canvas> 上繪制元素,那么你對(duì)此過程應(yīng)該會(huì)很熟悉。

如上圖所示,主線程遍歷布局樹,并生成繪制記錄。

更新渲染管道的成本很高

渲染管道(Rendering Pipeline)中最重要的任務(wù),就是在每個(gè)步驟開始前,根據(jù)前一次操作的結(jié)果,來創(chuàng)建新的數(shù)據(jù)。例如,如果布局樹中的某些內(nèi)容發(fā)生更改,則需要為文檔的受影響部分重新生成“繪制”順序。

渲染管道(Rendering Pipeline)中最重要的任務(wù),就是在每個(gè)步驟開始前,根據(jù)前一次操作的結(jié)果,來創(chuàng)建新的數(shù)據(jù)。例如,如果布局樹中的某些內(nèi)容發(fā)生變動(dòng),則需要為文檔中受影響的部分,重新生成“繪制記錄”。

為元素設(shè)置的動(dòng)畫,瀏覽器必須在每一幀之間執(zhí)行這些操作。我們大多數(shù)顯示器每秒刷新 60 次(60fps),如果你對(duì)每一幀都做了處理,那動(dòng)畫對(duì)人眼而言就是平滑的,但是如果某些幀沒有被處理到或者丟失了,則會(huì)導(dǎo)致動(dòng)畫不連貫,出現(xiàn)頁面的“卡頓”。

哪怕渲染的計(jì)算可以跟上屏幕刷新,可因?yàn)榇擞?jì)算過程發(fā)生在主線程上,當(dāng)執(zhí)行 JavaScript 腳本時(shí),可能導(dǎo)致渲染過程被阻斷。

即使渲染的計(jì)算可以跟上屏幕的刷新速度,可因?yàn)榇擞?jì)算是在主線程上執(zhí)行的,這就意味著 JS 代碼的執(zhí)行,也可能導(dǎo)致它被阻斷。

如上圖,時(shí)間軸上的動(dòng)畫幀,被 JS 阻止了一幀。

為此,你可以將 JavaScript 操作劃分成小塊,并在每幀上執(zhí)行requestAnimationFrame(),還可以在 Web Workers 中運(yùn)行 JavaScript,以避免阻塞主線程。

如圖所示,在動(dòng)畫幀的時(shí)間軸上,運(yùn)行較小的 JavaScript 塊。

合成(Compositing)

如何繪制一個(gè)頁面?

現(xiàn)在瀏覽器知道文檔的結(jié)構(gòu),每個(gè)元素的樣式,頁面的形狀和繪制順序,它是如何繪制頁面的?將此信息轉(zhuǎn)換為屏幕上的像素稱為光柵化(rasterizing)。

光柵化是將幾何數(shù)據(jù)經(jīng)過一系列變換后最終轉(zhuǎn)換為像素,從而呈現(xiàn)在顯示設(shè)備上的過程。

也許處理這種情況的一種無腦方案,是在視口(ViewPort)內(nèi)部將每個(gè)組件都光柵化。如果用戶滾動(dòng)頁面,則移動(dòng)光柵幀,并通過更多光柵元素填充缺少的部分。

這就是 Chrome ***發(fā)布時(shí)處理光柵化的方式,但是,現(xiàn)代瀏覽器運(yùn)行一個(gè)更復(fù)雜的被稱為合成(Compositing)的進(jìn)程。

什么是合成(Compositing)

合成是一種將頁面的各個(gè)元素進(jìn)行分層,分別光柵化,并在合成器線程中以一個(gè)單獨(dú)的線程合成新頁面的技術(shù)。如果頁面發(fā)生滾動(dòng),由于圖層已經(jīng)光柵化,因此它需要做的就是合成一個(gè)新幀。通過移動(dòng)圖層同時(shí)合成新幀,可以以相同的方式實(shí)現(xiàn)動(dòng)畫。

你可以在 DevTools 中的 Layout panel 來查看看圖層。

分層

為了確定每個(gè)元素所在的層,主線程遍歷布局樹以創(chuàng)建層樹(Layer Tree)。如果頁面的某元素應(yīng)該是一個(gè)單獨(dú)的圖層(例如側(cè)滑菜單),那么你可以在 CSS 中,使用 will-change 屬性提示瀏覽器。

如上圖,在主線程中遍歷布局樹,并生成層樹。

雖然理想情況下,應(yīng)該為每個(gè)元素生成圖層,但是對(duì)過多的小圖層進(jìn)行合并,可能會(huì)比對(duì)頁面的每幀上柵格化小元素更慢,因此測量應(yīng)用程序的渲染性能就非常重要。有關(guān)主題的更多信息,請(qǐng)參閱 Stick to Compositor-Only Properties 和 Manage Layer Count。

Stick to Compositor-Only Properties 和 Manage Layer Count:

https://developers.google.com/web/fundamentals/performance/rendering/stick-to-compositor-only-properties-and-manage-layer-count

光柵和合成,脫離主線程

一旦創(chuàng)建了層樹并確定了繪制順序,主線程就會(huì)將該信息提交給合成器線程。合成器線程會(huì)光柵化每個(gè)圖層,一個(gè)圖層可能想一個(gè)完整的頁面那么大,因此合成器線程將他們分成圖塊,并將每個(gè)圖塊發(fā)送到光柵線程。光柵線程格式化每個(gè)元素,并將他們存儲(chǔ)在 GPU 內(nèi)存中。

圖17:光柵線程創(chuàng)建光柵位圖并發(fā)送到GPU

合成器線程可以優(yōu)先考慮不同的光柵線程,以便 ViewPort(或附近)的元素可以被優(yōu)先光柵化。圖層還具有多個(gè)不同分辨率的傾斜度,以便對(duì)內(nèi)容的放大等操作。

一旦元素被光柵化,合成器線程會(huì)收集被稱為 “繪制矩形(Draw Quads)” 的信息,用以創(chuàng)建一個(gè)合成幀(Compositor Frame)。

然后通過 IPC 將合成幀提交給瀏覽器進(jìn)程。此時(shí),可以從 UI 線程添加另一個(gè)合成幀用于瀏覽器的 UI 更新,或者從其他渲染器進(jìn)程中添加擴(kuò)展。這些合成幀被發(fā)送到 GPU 中,用以在屏幕上顯示。如果觸發(fā)滾動(dòng)事件,合成器線程會(huì)創(chuàng)建另一個(gè)合成幀發(fā)送到 GPU。

上圖中,合成器線程創(chuàng)建合成幀。將此幀發(fā)送到瀏覽器進(jìn)程然后發(fā)送到 GPU。

合成(Compositor)的好處,是它可以在不影響主線程的情況下完成。合成器線程不需要等待樣式計(jì)算或者 JS 腳本執(zhí)行,這就是為什么 “僅合成動(dòng)畫” 被認(rèn)為是平滑性能的***選擇。如果需要再次計(jì)算不會(huì)或者重新繪制,則必須涉及到主線程。

合成動(dòng)畫:

https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/

小結(jié)

在這篇文章中,我們研究了從解析到合成的渲染流程,希望你現(xiàn)在有興趣探究有關(guān)網(wǎng)站性能優(yōu)化的更多內(nèi)容。

在下一篇文章中,將更詳細(xì)的介紹合成器線程,并解釋當(dāng)用戶觸發(fā) mouse move 和 click 時(shí),會(huì)發(fā)生什么。

【本文為51CTO專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請(qǐng)通過微信公眾號(hào)聯(lián)系作者獲取授權(quán)】

 

戳這里,看該作者更多好文

 

責(zé)任編輯:武曉燕 來源: 51CTO專欄
相關(guān)推薦

2019-04-08 10:27:00

渲染瀏覽器DOM

2017-02-16 19:09:56

Kali LinuxGoogle Chro瀏覽器

2022-08-30 09:01:11

瀏覽器渲染前端

2020-11-25 09:47:11

FedoraGoogle Chro瀏覽器

2013-11-18 14:42:53

瀏覽器渲染

2013-11-20 10:47:57

瀏覽器渲染html

2018-01-19 14:39:53

瀏覽器頁面優(yōu)化

2013-11-20 13:04:41

css瀏覽器渲染

2020-04-21 14:00:25

HTMLCSSJS

2016-08-03 15:21:03

UbuntuLinux易用性

2017-02-08 14:30:08

Chrome密碼瀏覽器

2013-11-18 15:09:34

瀏覽器渲染速度

2021-10-19 06:01:24

Chrome瀏覽器網(wǎng)頁瀏覽器

2020-11-06 15:20:45

瀏覽器前端架構(gòu)

2022-09-16 07:33:52

瀏覽器深色模式Firefox

2021-10-13 14:53:50

UbuntuVivaldi瀏覽器

2022-07-07 07:22:01

瀏覽器JavaScript工具

2020-10-30 10:15:21

Chrome V8JavaScript前端

2019-04-24 17:05:51

Windows 10ChromeFirefox

2022-01-11 20:42:28

CSS Chrome瀏覽器
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)