摘要:所以要快速響應(yīng)用戶,尤其是無線端,我們有必要了解瀏覽器渲染性能。這樣就造成了同步布局事件,是非常消耗性能的。過早地同步執(zhí)行樣式計算和布局是潛在的頁面性能的瓶頸之一。因為創(chuàng)建渲染層是有代價
該文章于三天前發(fā)表在 github,若有問題可提至 github.
這篇文章主要關(guān)注的是資源加載之后的性能,因為大多數(shù)用戶關(guān)注的不是應(yīng)用如何加載而是具體的使用。所以要快速響應(yīng)用戶,尤其是無線端,我們有必要了解瀏覽器渲染性能。
RAIL 性能模型首先一個需要思考的問題,怎樣的網(wǎng)站是順暢的?我們可能可以給一個大概的感覺,如:秒級響應(yīng)等。其實,也可以給出一個很討巧的答案:用戶覺得順暢的網(wǎng)站它就是順暢的。因為幾乎所有網(wǎng)站都希望將用戶留在頁面上,當(dāng)然以用戶為中心建立性能模型是必要的。下面是 Google 提出的一個以用戶為中心的性能模型,里面的數(shù)據(jù)不是 Google 首創(chuàng),有一些論文做類似研究(如:100ms 響應(yīng)用戶是一個很合適的時間等)。
上圖是 RAIL 的具體含義,這里有一些關(guān)鍵性的數(shù)據(jù)指標(biāo):
Respond:0 - 100ms,視窗一般需要在這個時間段響應(yīng)用戶,超過這個時間段,用戶就會感覺到延時。
Animation:0~16ms,屏幕每秒刷新60次,16ms 代表的是每一幀的時間。用戶是非常關(guān)注動畫的,當(dāng)動畫失幀很容易引起用戶察覺。所以動畫一般要控制在60FPS。
Idle:最大化主進程的空閑時間,這樣可以及時響應(yīng)用戶輸入。
Load:內(nèi)容需要在1000ms 內(nèi)加載出來,超過1000ms 會覺得加載緩慢。
應(yīng)用要達到上面的性能模型需要從哪些方面入手呢?如果我們知道瀏覽器是如何渲染一個頁面的,并且去優(yōu)化渲染過程中的關(guān)鍵步驟,是不是就能事半功倍呢?
關(guān)鍵渲染路徑上圖是瀏覽器渲染的關(guān)鍵路徑,首先,讓我們從瀏覽器解析一個頁面開始吧。
轉(zhuǎn)化: 瀏覽器從磁盤或網(wǎng)絡(luò)讀取 HTML 的原始字節(jié),瀏覽器會將這段原始文件按照相應(yīng)編碼規(guī)范進行解碼(現(xiàn)在一般為 utf-8)。
符號化:根據(jù) W3C 標(biāo)準(zhǔn)轉(zhuǎn)化為對應(yīng)的符號(一般在尖括號內(nèi))。
DOM 構(gòu)建:HTML 解析器會解析其中的 tag 標(biāo)簽,生成 token ,遇到 CSS 或 JS 會發(fā)送相應(yīng)請求。HTML 解析時阻塞主進程的,CSS 一般也是阻塞主進程的(媒體查詢時例外),也就是說它們在解析過程中是無法做出響應(yīng)的。而 JS 手動添加 async 后達到異步加載,根據(jù) token 生成相應(yīng) DOM 樹。
。
CSSDOM 構(gòu)建,添加 CSS 樣式生成 CSSDOM 樹。
渲染樹構(gòu)建,從 DOM 樹的根節(jié)點開始,遍歷每個可見的節(jié)點,給每個可見節(jié)點找到相應(yīng)匹配的 CSSOM 規(guī)則,并應(yīng)用這些規(guī)則,連帶其內(nèi)容及計算的樣式。
樣式計算,瀏覽器會將所有的相對位置轉(zhuǎn)換成絕對位置等一系列的樣式計算。
布局,瀏覽器將元素進行定位、布局。
繪制,繪制元素樣式,顏色、背景、大小、邊框等。
合成,將各層合成到一起、顯示在屏幕上。
如果我們是做一個動畫,一般會用 JS 更改相應(yīng)樣式,接著瀏覽器就會經(jīng)歷 JS 運行、樣式計算、布局、繪制、合成等多個重要步驟(后面還會講到這個步驟實際過程中可以更長或者更短)。那么要做的優(yōu)化就是在這幾個步驟中進行優(yōu)化并且盡量去掉中間的耗時步驟。
優(yōu)化JavaScript的執(zhí)行上圖描述的四個場景都是有可能對響應(yīng)用戶輸入或者動畫造成影響的。函數(shù)的輸入事件處理、不合時機的 JS 、長時間的 JS 運行以及垃圾回收。
函數(shù)的輸入事件處理首先,我們要知道的一個事實就是瀏覽器是由多個處理進程的:Compositor、Tile Worker、Main。當(dāng)用戶進行輸入操作(滾動、點擊等),如滾動時,Compositor 進程會接收到這個事件(實際它可以接受任何用戶輸入事件),如果可以的話,它將不會通知主進程,直接說:滾吧,牛寶寶。于是,頁面就滾動了。當(dāng)然,這其中包含更新層定位以及讓 GPU 繪制幀,而主線程處于空閑狀態(tài)。但是,事情往往并非如此。如果輸入事件上綁定了 JS 處理事件的話,Compositor 進程就沒辦法主動跳過主進程了。
如上圖,當(dāng) JS 處理事件過長時,輸入事件的響應(yīng)會一直處于阻塞狀態(tài),直到 JS 處理完成。當(dāng)響應(yīng)超過 100ms 時,用戶就會感受到延時。所以當(dāng)處理用戶事件時,我們應(yīng)該做到:
避免長時間的 JS 執(zhí)行。
避免在處理中改變樣式。因為樣式改變會引起后面布局、繪制、合成等操作。
對用戶輸入進行消抖。
優(yōu)化處理其他優(yōu)化:
使用 requestAnimationFrame,將 setTimeout 換成 requestAnimationFrame,因為 setTimeout 時間控制可能造成在一幀的中間,目前各瀏覽器對 requestAnimationFrame 的支持已經(jīng)比較好了。
使用 Web Workers,將復(fù)雜計算的 JS 采用 Web Workers 進行處理。
減少垃圾回收,垃圾回收是一個容易被忽略的問題,因為垃圾回收的時間是不受控制的,它可能在一個動畫的中途,阻塞動畫的執(zhí)行,更理想的情況是在循環(huán)中復(fù)用對象。
樣式計算添加或移除一個 DOM 元素、修改元素屬性和樣式類、應(yīng)用動畫效果等操作,都會引起 DOM 結(jié)構(gòu)的改變,從而導(dǎo)致瀏覽器需要重新計算每個元素的樣式、對頁面或其一部分重新布局(多數(shù)情況下)。
計算樣式的第一步是創(chuàng)建一套匹配的樣式選擇器,瀏覽器就是靠它們來對一個元素應(yīng)用樣式的。第二步是根據(jù)匹配的樣式選擇器來獲取對應(yīng)的具體樣式規(guī)則,計算出最終具體有哪些樣式是要應(yīng)用在 DOM 元素上的。所以樣式的優(yōu)化也是這兩步:
如何減小選擇器的復(fù)雜性?
.box:nth-last-child(-n+1) .title { /* styles */ } .final-box-title { /* styles */ }
上面代碼都是選擇同一個元素,當(dāng)元素很多時,第二個選擇器的性能會明顯優(yōu)于第一個。BEM 規(guī)范有做類似事情,按照特性直接由一個選擇器選擇元素的性能往往會更優(yōu)。
減少樣式的計算量因為元素的計算量和被改變的元素的數(shù)量成正比,所以你只需要注意一點,減少無效元素。
多層無意義的標(biāo)簽
像上面的例子,有時候創(chuàng)建了一些冗余的標(biāo)簽。當(dāng)改變外層的樣式時,冗余的標(biāo)簽也需要進行樣式計算,浪費性能。
布局瀏覽器計算 DOM 元素的幾何信息的過程:元素大小和在頁面中的位置。每個元素都有一個顯式或隱式的大小信息,決定于其 CSS 屬性的設(shè)置、或是元素本身內(nèi)容的大小、或者是其父元素的大小。在 Blink/WebKit 內(nèi)核的瀏覽器和 IE 中,這個過程稱為 Layout。在基于 Gecko 的瀏覽器(比如 Firefox)中,這個過程稱為 Reflow。
避免觸發(fā)布局目前,transform 和 opacity 只會引起合成,不會引起布局和重新繪制。整個流程中比較耗費性能的布局和繪制流程將直接跳過,性能顯然是很好的。其他的 CSS 屬性改變引起的流程會有所不同,有些屬性也會跳過布局,具體可以查看 CSS Triggers。所以,優(yōu)化的第一步就是盡可能避免觸發(fā)布局。
使用Flexbox布局Flexbox 布局方案性能會優(yōu)于以前的布局方案,而且目前瀏覽器對 Flexbox 支持度相當(dāng)高了:
首先是執(zhí)行 JS 腳本,然后是樣式計算,然后是布局。但是,我們還可以強制瀏覽器在執(zhí)行 JS 腳本之前先執(zhí)行布局過程,這就是所謂的強制同步布局。在 JS 腳本運行的時候,它能獲取到的元素樣式屬性值都是上一幀畫面的,都是舊的值。因此,如果你想在這一幀開始的時候,讀取一個元素的 height 屬性,你可以會寫出這樣的 JS 代碼:
function logBoxHeight() { box.classList.add("super-big"); // Gets the height of the box in pixels // and logs it out. console.log(box.offsetHeight); }
為了給你返回 box 的 height 屬性值,瀏覽器必須首先應(yīng)用 box 的屬性修改(因為對其添加了 super-big 樣式),接著執(zhí)行布局過程。在這之后,瀏覽器才能返回正確的 height 屬性值。這樣就造成了同步布局事件,是非常消耗性能的。大多數(shù)情況下,你應(yīng)該都不需要先修改然后再讀取元素的樣式屬性值,使用上一幀的值就足夠了。過早地同步執(zhí)行樣式計算和布局是潛在的頁面性能的瓶頸之一。
function logBoxHeight() { // Gets the height of the box in pixels // and logs it out. console.log(box.offsetHeight); box.classList.add("super-big"); }避免快速連續(xù)的布局
還有一種情況比強制同步布局更糟:連續(xù)快速的多次執(zhí)行它。
function resizeAllParagraphsToMatchBlockWidth() { // Puts the browser into a read-write-read-write cycle. for (var i = 0; i < paragraphs.length; i++) { paragraphs[i].style.width = box.offsetWidth + "px"; } }
上述代碼對一組段落標(biāo)簽執(zhí)行循環(huán)操作,設(shè)置 p 標(biāo)簽的width屬性值,使其與 box 元素的寬度相同。看上去這段代碼是沒問題的,但問題在于,在每次循環(huán)中,都讀取了 box 元素的一個樣式屬性值,然后立即使用該值來更新 p 元素的 widt h屬性。在下一次循環(huán)中讀取 box 元素 offsetwidth 屬性的時候,瀏覽器必須先使得上一次循環(huán)中的樣式更新操作生效,也就是執(zhí)行布局過程,然后才能響應(yīng)本次循環(huán)中的樣式讀取操作。布局過程將在每次循環(huán)中發(fā)生。優(yōu)化代碼:
// Read. var width = box.offsetWidth; function resizeAllParagraphsToMatchBlockWidth() { for (var i = 0; i < paragraphs.length; i++) { // Now write. paragraphs[i].style.width = width + "px"; } }
如果你想確保編寫的讀寫操作是安全的,你可以使用 FastDOM。它能幫你自動完成讀寫操作的批處理,還能避免意外地觸發(fā)強制同步布局或快速連續(xù)的布局。
繪制 提升移動或漸變元素的繪制層繪制并非總是在內(nèi)存中的單層畫面里完成的。實際上,瀏覽器在必要時將會把一幀畫面繪制成多層畫面,然后將這若干層畫面合并成一張圖片顯示到屏幕上。通過渲染層提升可以減小繪制區(qū)域,我們可以用調(diào)試工具查看到繪制層:
在頁面中新建一個渲染層最好的方式就是使用 will-change 屬性,同時再與 transform 屬性一起使用,就會創(chuàng)建一個新的組合層:
.element { will-change: transform; }
對于那些目前還不支持 will-change 屬性、但支持創(chuàng)建渲染層的瀏覽器,可以使用一個 3D transform 屬性來強制瀏覽器創(chuàng)建一個新的渲染層:
.element { transform: translateZ(0); }
注意: 別盲目創(chuàng)建渲染層,一定要分析其實際性能表現(xiàn)。因為創(chuàng)建渲染層是有代價的,每創(chuàng)建一個新的渲染層,就意味著新的內(nèi)存分配和更復(fù)雜的層的管理。并且在移動端 GPU 和 CPU 的帶寬有限制,創(chuàng)建的渲染層過多時,合成也會消耗跟多的時間。
仔細規(guī)劃動畫和簡化繪制的復(fù)雜度有時候,盡管把元素提升到了一個多帶帶的渲染層,瀏覽器會把兩個相鄰區(qū)域的渲染任務(wù)合并在一起進行,這將導(dǎo)致整個屏幕區(qū)域都會被繪制。所以可以使用調(diào)試工具查看,仔細規(guī)劃動畫。
不同的 CSS 屬性繪制的成本是不一樣的,繪制一個陰影就比繪制邊框更費時。當(dāng)然,這個瀏覽器也在不停優(yōu)化中,現(xiàn)在的耗時渲染屬性隨時都可能被改變,所以需要多關(guān)注一下。
渲染層的合并,就是把頁面中完成了繪制過程的部分合并成一層,然后顯示在屏幕上。下面和合成相關(guān)的兩點前面也有提到過。
使用transform/opacity實現(xiàn)動畫效果前面已經(jīng)提到過 transform/opacity 的優(yōu)勢,應(yīng)用了 transforms/opacity 屬性的元素必須獨占一個渲染層。為了對這個元素創(chuàng)建一個自有的渲染層,你必須提升該元素。
管理渲染層、避免過多數(shù)量的層創(chuàng)建一個新的渲染層需要消耗額外的內(nèi)存和管理資源。而在內(nèi)存資源有限的設(shè)備上,由于過多的渲染層來帶的開銷而對頁面渲染性能產(chǎn)生的影響,甚至遠遠超過了它在性能改善上帶來的好處。由于每個渲染層的紋理都需要上傳到 GPU 處理,因此我們還需要考慮 CPU 和 GPU 之間的帶寬問題、以及有多大內(nèi)存供 GPU 處理這些紋理的問題。
其他關(guān)注趨勢,今天很多的性能瓶頸很可能在將來都不再是問題。如之前關(guān)注的一項技術(shù) Web Animations,是否能用 JS 達到原生動畫效果。Houdini,你可以添加更多的 JS 代碼到動畫中而不用擔(dān)心性能問題。
利用工具 Chrome DevTools
,上面的規(guī)則只是優(yōu)化的方向,善于利用工具分析。移動端利用 inspector 也是非常方便的,并且還可以對數(shù)據(jù)進行保存,對比分析等。幾乎一切需要的分析工具,DevTools 都有。
不要進行微優(yōu)化,有時花上很短的帶來的性能提升卻很小,對于日常快速迭代的業(yè)務(wù)是沒必要這樣做的。
相關(guān)鏈接Paul Lewis 相關(guān)文章,Chrome 開發(fā)團隊,旨在幫助開發(fā)者提高他們的應(yīng)用和站點。
Performance Calendar,從2009年到2015年,列出了每一年值得關(guān)注的優(yōu)化方案。
Performance,Google Developers 官方站點對性能優(yōu)化的文章。
webkit,webkit 的博客站點,介紹了比較底層的渲染等文章。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/79705.html
摘要:性能優(yōu)化的知識非常龐雜,這里我們來介紹幾種常用的性能優(yōu)化方式。一中的前端性能優(yōu)化原則多使用內(nèi)存緩存等方法減少計算減少網(wǎng)絡(luò)請求二針對上述兩項原則,我們可以從兩個方向入手來進行前端的性能優(yōu)化。只許一次操作,大大提高性能。 JS性能優(yōu)化的知識非常龐雜,這里我們來介紹幾種常用的性能優(yōu)化方式。 一、JS中的前端性能優(yōu)化原則: 多使用內(nèi)存、緩存等方法 減少CPU計算、減少網(wǎng)絡(luò)請求 二、針對...
摘要:關(guān)于網(wǎng)頁性能網(wǎng)頁性能管理是一個很大的話題,最近在復(fù)習(xí)相關(guān)的知識,小結(jié)一下。這兩個規(guī)則的實質(zhì)都是提高頁面的性能,避免發(fā)生不必要的重新渲染。頁面性能優(yōu)化重排和重繪會不斷觸發(fā),這是不可避免的。但是,它們非常耗費資源,是導(dǎo)致網(wǎng)頁性能低下的根本原因。 關(guān)于網(wǎng)頁性能 網(wǎng)頁性能管理是一個很大的話題,最近在復(fù)習(xí)相關(guān)的知識,小結(jié)一下。 頁面加載順序 網(wǎng)頁生成的過程大致如下: HTML代碼轉(zhuǎn)化成DOM...
摘要:如果網(wǎng)頁動畫能夠做到每秒幀,就會跟顯示器同步刷新,達到最佳的視覺效果。下面的一條是,低于這條線,可以達到每秒幀上面的一條是,低于這條線,可以達到每秒次渲染。圖中幀柱的高度表示了該幀的總耗時,幀柱中的顏色分別對應(yīng)該幀中包含的不停類型的事件。 原文地址:http://horve.github.io/2015/10/26/timeli... 隨著webpage可以承載的表現(xiàn)形式更加多樣化,通...
摘要:端優(yōu)談?wù)勱P(guān)于前端的緩存的問題我們都知道對頁面進行緩存能夠有利于減少請求發(fā)送,從而達到對頁面的優(yōu)化。而作為一名有追求的前端,勢必要力所能及地優(yōu)化我們前端頁面的性能。這種方式主要解決了淺談前端中的過早優(yōu)化問題過早優(yōu)化是萬惡之源。 優(yōu)化向:單頁應(yīng)用多路由預(yù)渲染指南 Ajax 技術(shù)的出現(xiàn),讓我們的 Web 應(yīng)用能夠在不刷新的狀態(tài)下顯示不同頁面的內(nèi)容,這就是單頁應(yīng)用。在一個單頁應(yīng)用中,往往只有一...
閱讀 2083·2023-04-26 02:41
閱讀 2146·2021-09-24 09:47
閱讀 1546·2019-08-30 15:53
閱讀 1205·2019-08-30 13:01
閱讀 1885·2019-08-29 11:27
閱讀 2857·2019-08-28 17:55
閱讀 1740·2019-08-26 14:00
閱讀 3377·2019-08-26 10:18