頁面不僅要快速加載,而且要順暢地運行;滾動應(yīng)與手指的滑動一樣快,并且動畫和交互應(yīng)如絲綢般順滑。
目前大多數(shù)設(shè)備的屏幕刷新率為 60 次/秒。因此,如果在頁面中有一個動畫或漸變效果,或者用戶正在滾動頁面,那么瀏覽器渲染動畫或頁面的每一幀的速率也需要跟設(shè)備屏幕的刷新率保持一致。
其中每個幀的預(yù)算時間僅比 16 毫秒多一點 (1 秒/ 60 = 16.66 毫秒)。但實際上,瀏覽器有整理工作要做,因此所有工作需要在 10 毫秒內(nèi)完成。如果無法符合此預(yù)算,幀率將下降,并且內(nèi)容會在屏幕上抖動(judders)。此現(xiàn)象通常稱為卡頓(jank),會對用戶體驗產(chǎn)生負(fù)面影響。
像素管道(The pixel pipeline)
在工作時需要了解并注意五個主要區(qū)域,這些是擁有最大控制權(quán)的部分,也是像素至屏幕管道中的關(guān)鍵點:
JavaScript。一般來說,會使用 JavaScript 來實現(xiàn)一些視覺變化的效果。比如用 jQuery 的 animate 函數(shù)做一個動畫、對一個數(shù)據(jù)集進(jìn)行排序或者往頁面里添加一些 DOM 元素等。除了 JavaScript,還有其他一些常用方法也可以實現(xiàn)視覺變化效果,比如:CSS Animations、Transitions 和 Web Animation API。
樣式計算Sytle calculations。This is the process of figuring out which CSS rules apply to which elements based on matching selectors, for example, .headline or .nav > .nav__item. From there, once rules are known, they are applied and the final styles for each element are calculated.
布局。在知道對一個元素應(yīng)用哪些規(guī)則之后,瀏覽器即可開始計算它要占據(jù)的空間大小及其在屏幕的位置。網(wǎng)頁的布局模式意味著一個元素可能影響其他元素,例如 <body> 元素的寬度一般會影響其子元素的寬度以及樹中各處的節(jié)點,因此對于瀏覽器來說,布局過程是經(jīng)常發(fā)生的。
繪制。繪制是填充像素的過程。它涉及繪出文本、顏色、圖像、邊框和陰影,包括元素的每個可視部分。繪制一般是在多個表面(通常稱為層layers)上完成的。
合成。由于頁面的各部分可能被繪制到多層,由此它們需要按正確順序繪制到屏幕上,以便正確渲染頁面。對于與另一元素重疊的元素來說,這點特別重要,因為一個錯誤可能使一個元素錯誤地出現(xiàn)在另一個元素的上層。
管道的每個部分都有機(jī)會產(chǎn)生卡頓,因此務(wù)必準(zhǔn)確了解代碼觸發(fā)管道的哪些部分。
不一定每幀都總是會經(jīng)過管道每個部分的處理。實際上,不管是使用 JavaScript、CSS 還是網(wǎng)絡(luò)動畫,在實現(xiàn)視覺變化時,管道針對指定幀的運行通常有三種方式:
1. JS / CSS > 樣式 > 布局 > 繪制 > 合成
如果修改元素的“l(fā)ayout”屬性,即改變了元素的幾何屬性(例如寬度、高度等),那么瀏覽器將必須檢查所有其他元素,然后“自動重排”頁面(reflow the page)。任何受影響的部分都需要重新繪制,而且最終繪制的元素需進(jìn)行合成。
2. JS / CSS > 樣式 > 繪制 > 合成
如果修改“paint only”屬性(例如背景圖片、文字顏色或陰影等),即不會影響頁面布局的屬性,則瀏覽器會跳過布局,但仍將執(zhí)行繪制。
3. JS / CSS > 樣式 > 合成
如果更改一個既不用重新布局也不要重新繪制的屬性,則瀏覽器將只執(zhí)行合成。這個最后的方式開銷最小,最適合于應(yīng)用生命周期中的高壓力點,例如動畫或滾動。
性能是一種避免執(zhí)行的藝術(shù),并且使執(zhí)行的任何操作盡可能高效。 許多情況下,這需要與瀏覽器配合,而不是跟它對著干。 值得謹(jǐn)記的是,上面列出的各項管道工作在計算開銷上有所不同,一些任務(wù)比其他任務(wù)的開銷要大!
JavaScript often triggers visual changes. Sometimes that's directly through style manipulations, and sometimes it's calculations that result in visual changes, like searching or sorting data.時機(jī)不當(dāng)或長時間運行的 JavaScript 可能是導(dǎo)致性能問題的常見原因,應(yīng)當(dāng)設(shè)法盡可能減少其影響。
JavaScript 性能分析可以說是一門藝術(shù),因為編寫的 JavaScript 代碼與實際執(zhí)行的代碼完全不像?,F(xiàn)代瀏覽器使用 JIT 編譯器和各種各樣的優(yōu)化和技巧來實現(xiàn)盡可能快的執(zhí)行,這極大地改變了代碼的動態(tài)性。
一些幫助應(yīng)用很好地執(zhí)行 JavaScript的事情:
對于動畫效果的實現(xiàn),避免使用 setTimeout 或 setInterval,使用 requestAnimationFrame。
將長時間運行的 JavaScript 從主線程移到 Web Worker。
使用小任務(wù)來執(zhí)行對多個幀的 DOM 更改。
使用 Chrome DevTools 的 Timeline 和 JavaScript 分析器來評估 JavaScript 的影響。
當(dāng)屏幕正在發(fā)生視覺變化時,最好在幀的開頭執(zhí)行操作。保證 JavaScript 在幀開始時運行的唯一方式是使用 requestAnimationFrame。
/** * If run as a requestAnimationFrame callback, this * will be run at the start of the frame. */function updateScreen(time) { // Make visual updates here.} requestAnimationFrame(updateScreen);
框架或示例可能使用 setTimeout 或 setInterval 來執(zhí)行動畫之類的視覺變化,但這種做法的問題是,回調(diào)函數(shù)在幀中的某個時點運行,可能剛好在幀的末尾,而這經(jīng)常會使我們丟失幀,導(dǎo)致卡頓。(composite等js的運行需要時間,會阻塞UI更新)。
事實上,jQuery 目前的默認(rèn) animate 行為是使用 setTimeout!強(qiáng)烈建議打上補(bǔ)丁程序以使用 requestAnimationFrame。
JavaScript 在瀏覽器的主線程上運行,恰好與樣式計算、布局以及許多情況下的繪制一起運行。如果 JavaScript 運行時間過長,就會阻塞這些其他工作,可能導(dǎo)致幀丟失。
因此,要妥善處理 JavaScript 何時運行以及運行多久。例如,如果在滾動之類的動畫中,最好是想辦法使 JavaScript 保持在 3-4 毫秒的范圍內(nèi)。超過此范圍,就可能要占用太多時間。如果在空閑期間,則可以不必那么斤斤計較所占的時間。
在許多情況下,可以將純計算工作移到 Web Worker,例如,不需要 DOM 訪問權(quán)限,數(shù)據(jù)操作或遍歷(例如排序或搜索),往往很適合這種模型,加載和模型生成也是如此。
var dataSortWorker = new Worker("sort-worker.js?1.1.11"); dataSortWorker.postMesssage(dataToSort);// The main thread is now free to continue working on other things...dataSortWorker.addEventListener('message', function(evt) { var sortedData = evt.data; // Update data on screen...});
并非所有工作都適合此模型:Web Worker 沒有 DOM 訪問權(quán)限。如果操作必須在主線程上執(zhí)行,可以考慮一種批量方法,將大型任務(wù)分割為小任務(wù),每個小任務(wù)所占時間不超過幾毫秒,并且在每幀的 requestAnimationFrame 處理程序內(nèi)運行。
var taskList = breakBigTaskIntoMicroTasks(monsterTaskList); requestAnimationFrame(processTaskList);function processTaskList(taskStartTime) { var taskFinishTime; do {// Assume the next task is pushed onto a stack.var nextTask = taskList.pop();// Process nextTask. processTask(nextTask);// Go again if there’s enough time to do the next task.taskFinishTime = window.performance.now(); } while (taskFinishTime - taskStartTime < 3); if (taskList.length > 0) requestAnimationFrame(processTaskList); }
此方法會產(chǎn)生 UX 和 UI 后果,您將需要使用進(jìn)度或活動指示器來確保用戶知道任務(wù)正在被處理。在任何情況下,此方法都不會占用應(yīng)用的主線程,從而有助于主線程始終對用戶交互作出快速響應(yīng)。
在評估一個框架、庫或自己的代碼時,務(wù)必逐幀評估運行 JavaScript 代碼的開銷。當(dāng)執(zhí)行性能關(guān)鍵的動畫工作(例如變換或滾動)時,這點尤其重要。
測量 JavaScript 開銷和性能情況的最佳方法是使用 Chrome DevTools。通常,您將獲得如下的簡單記錄:
The Main section provides a flame chart of JavaScript calls so you can analyze exactly which functions were called and how long each took.
如果發(fā)現(xiàn)有長時間運行的 JavaScript,則可以在 DevTools 用戶界面的頂部啟用 JavaScript 分析器:
以這種方式分析 JavaScript 會產(chǎn)生開銷,因此一定只在想要更深入了解 JavaScript 運行時特性時才啟用它。啟用此復(fù)選框后,現(xiàn)在可以執(zhí)行相同的操作,您將獲得有關(guān) JavaScript 中調(diào)用了哪些函數(shù)的更多信息:
有了這些信息之后,就可以評估 JavaScript 對應(yīng)用性能的影響,并開始找出和修正函數(shù)運行時間過長的熱點(hotspots)。如前所述,應(yīng)當(dāng)設(shè)法移除長時間運行的 JavaScript,或者若不能移除,則將其移到 Web Worker 中,騰出主線程繼續(xù)執(zhí)行其他任務(wù)。
知道瀏覽器執(zhí)行一個函數(shù)版本比另一個函數(shù)要快 100 倍可能會很酷,比如請求元素的offsetTop比計算getBoundingClientRect()要快,但是,您在每幀調(diào)用這類函數(shù)的次數(shù)幾乎總是很少。因此,把重點放在 JavaScript 性能的這個方面通常是白費勁。您一般只能節(jié)省零點幾毫秒的時間。
如果您開發(fā)的是游戲或計算開銷很大的應(yīng)用,則可能屬于本指南的例外情況,因為您一般會將大量計算放入單個幀,在這種情況下各種方法都很有用。
簡而言之,慎用微優(yōu)化,因為它們通常不會映射到您正在構(gòu)建的應(yīng)用類型。2/8法則,先從瓶頸處著手優(yōu)化。
通過添加和刪除元素,更改屬性、類或通過動畫來更改 DOM,都會導(dǎo)致瀏覽器重新計算元素樣式,在很多情況下還會對頁面或頁面的一部分進(jìn)行布局(即自動重排)。This process is called computed style calculation.
計算樣式的第一部分是創(chuàng)建一組匹配選擇器,這實質(zhì)上是瀏覽器計算出給指定元素應(yīng)用哪些classes, pseudo-selectors and IDs。
第二部分涉及從匹配選擇器中獲取所有樣式規(guī)則,并計算出此元素的最終樣式。在 Blink(Chrome 和 Opera 的渲染引擎)中,這些過程的開銷至少在目前是大致相同的:
Roughly 50% of the time used to calculate the computed style for an element is used to match selectors,而另一半時間用于從匹配的規(guī)則中構(gòu)建 RenderStyle(computed style representation)。
降低選擇器的復(fù)雜性;使用以類為中心的方法,例如 BEM規(guī)范(Block-Element_Modifer)。
減少必須計算其樣式的元素數(shù)量。
在最簡單的情況下,在 CSS 中只有一個類的元素:
.title {
/* styles */
}
但是,隨著項目的增長,將可能產(chǎn)生更復(fù)雜的 CSS,最終的選擇器可能變成這樣:
.box:nth-last-child(-n+1) .title {
/*
styles */
}
為了知道是否需要應(yīng)用樣式,瀏覽器實際上必須詢問“這是否為有 title 類的元素,其父元素恰好是負(fù)第 N 個子元素加上 1 個帶 box 類的元素?”計算此結(jié)果可能需要大量時間,具體取決于所用的選擇器和相應(yīng)的瀏覽器。特定的選擇器可以更改為一個類:
.final-box-title {
/*
styles */
}
開發(fā)者可能對該類的名稱有疑問,但此工作對于瀏覽器而言要簡單得多。在上一版本中,為了知道該元素是否為其類型的最后一個,瀏覽器首先必須知道關(guān)于其他元素的所有情況,以及其后面是否有任何元素會是第 N 個最后子元素,這比簡單地將類選擇器與元素匹配的開銷要大得多。
生成render tree時,對于每個DOM元素,必須在所有Style Rules中找到符合的 selector 并將對應(yīng)的樣式規(guī)則進(jìn)行合并。
css選擇器的解析是從右往左的,這樣公共樣式就在CSSOM樹的父節(jié)點上,更具體的樣式(選擇器更具體)會在子節(jié)點上,節(jié)點分支和遍歷次數(shù)都會變少。如果采用 left-to-right 的方式讀取css規(guī)則,那么大多數(shù)規(guī)則讀到最后才會發(fā)現(xiàn)是不匹配,做了很多無用功;而采取 right-to-left 的方式,只要發(fā)現(xiàn)最右邊選擇器不匹配,就直接舍棄,避免很多無效匹配。
另一個性能考慮,在元素更改時需要計算的工作量對于許多樣式更新而言是更重要的因素。
In general terms, the worst case cost of calculating the computed style of elements is the number of elements multiplied by the selector count, because each element needs to be at least checked once against every style rule to see if it matches.
注:以前曾經(jīng)是這樣:如果改變了(例如)body 元素上的一個類,則該頁的所有子元素將需要重新計算其計算樣式?,F(xiàn)在有點不一樣:對于更改時會導(dǎo)致重新計算樣式的元素,某些瀏覽器維護(hù)一小組每個這種元素獨有的規(guī)則。這意味著,根據(jù)元素在樹中的位置以及所改變的具體屬性,元素不一定需要重新計算。
樣式計算可能經(jīng)常是直接針對少量目標(biāo)元素,而不是聲明整個頁面無效。在現(xiàn)代瀏覽器中,這往往不再是個問題,因為瀏覽器并不一定需要檢查一項更改可能影響的所有元素。另一方面,較早的瀏覽器不一定針對此類任務(wù)進(jìn)行了優(yōu)化。應(yīng)當(dāng)盡可能減少聲明為無效的元素的數(shù)量。
注:如果您熱衷于網(wǎng)頁組件,有一點值得注意,樣式計算在這方面稍有不同,因為默認(rèn)情況下樣式不會跨越 Shadow DOM 的邊界,并且范圍限于單個組件,而不是整個樹。但是,總體來看,同樣的概念仍然適用:規(guī)則簡單的小樹比規(guī)則復(fù)雜的大樹會得到更高效地處理。
測量樣式重新計算的開銷
測量樣式重新計算的最簡單、最好的方法是使用 Chrome DevTools 的 Timeline 模式。首先,打開 DevTools,轉(zhuǎn)至 Timeline 選項卡,選中記錄并與您的網(wǎng)站交互。停止記錄后,將看到下圖所示情況。
頂部的條表示每秒幀數(shù),如果看到柱形超過較低的線,即 60fps 線,則存在長時間運行的幀。
如果一些滾動之類的交互或其他交互時出現(xiàn)長時間運行的幀,則應(yīng)當(dāng)進(jìn)一步審查。
如果出現(xiàn)較大的紫色塊,如上例所示,請點擊記錄了解到更多細(xì)節(jié)。
在這次抓取中,有一個長時間運行的重新計算樣式事件,其時間剛好超過 18 毫秒,并且恰好發(fā)生在滾動期間,導(dǎo)致用戶體驗到明顯的抖動。
如果點擊事件本身,將看到一個調(diào)用棧,精確指出了您的 JavaScript 中導(dǎo)致觸發(fā)樣式更改的位置。此外,還獲得樣式受更改影響的元素數(shù)量(本例中剛好超過 400 個元素),以及執(zhí)行樣式計算所花的時間。您可以使用此信息來開始嘗試在代碼中查找修正點。
BEM的編碼方法實際上納入了上述選擇器匹配的性能優(yōu)勢,因為它建議所有元素都有單個類,并且在需要層次結(jié)構(gòu)時也納入了類的名稱:
.list { }
.list__list-item { }
如果需要一些修飾符,像在上面我們想為最后一個子元素做一些特別的東西,就可以按如下方式添加:
.list__list-item--last-child
{}
如果您在尋找一種好方法來組織您的 CSS,則 BEM 真的是個很好的起點,不僅從結(jié)構(gòu)的角度如此,還因為樣式查找得到了簡化。
布局是瀏覽器計算各元素幾何信息的過程:元素的大小以及在頁面中的位置。 根據(jù)所用的 CSS、元素的內(nèi)容或父級元素,每個元素都將有顯式或隱含的大小信息。此過程在 Chrome、Opera、Safari 和 Internet Explorer 中稱為布局 (Layout)。 在 Firefox 中稱為自動重排 (Reflow),但實際上其過程是一樣的。
與樣式計算相似,布局開銷的直接考慮因素如下:
需要布局的元素數(shù)量。
這些布局的復(fù)雜性。
布局的作用范圍一般為整個文檔。
DOM 元素的數(shù)量將影響性能,應(yīng)盡可能避免觸發(fā)布局。
評估布局模型的性能;新版 Flexbox比舊版 Flexbox 或基于浮動的布局模型更快。
Avoid forced synchronous layouts and layout thrashing; read style values then make style changes.
當(dāng)更改樣式時,瀏覽器會檢查更改是否需要計算布局,以及是否需要更新渲染樹。對“幾何屬性”(如寬度、高度、左側(cè)或頂部)的更改都需要布局計算。
.box {
width: 20px;
height: 20px;
}
/** Changing
width and height triggers layout. */
.box--expanded {
width: 200px;
height: 350px;
}
布局幾乎總是作用到整個文檔。 如果有大量元素,將需要很長時間來算出所有元素的位置和尺寸。
如果無法避免布局,關(guān)鍵還是要使用 Chrome DevTools 來查看布局要花多長時間,并確定布局是否是造成瓶頸的原因。首先,打開 DevTools,選擇“Timeline”標(biāo)簽,點擊“record”按鈕,然后與您的網(wǎng)站交互。當(dāng)您停止記錄時,將看到網(wǎng)站表現(xiàn)情況的詳細(xì)分析:
在仔細(xì)研究上例中的框架時,我們看到超過 20 毫秒用在布局上,當(dāng)我們在動畫中設(shè)置 16 毫秒來獲取屏幕上的幀時,此布局時間太長。您還可以看到,DevTools 將說明樹的大小(本例中為 1618 個元素)以及需要布局的節(jié)點數(shù)。
網(wǎng)頁有各種布局模型,一些模式比其他模式受到更廣泛的支持。最早的 CSS 布局模型使我們能夠在屏幕上對元素進(jìn)行相對、絕對定位或通過浮動元素定位。
下面的屏幕截圖顯示了在 1,300 個框上使用浮動的布局開銷。當(dāng)然,這是一個人為的例子,因為大多數(shù)應(yīng)用將使用各種手段來定位元素。
如果我們更新此示例以使用 Flexbox(Web 平臺的新模型),則出現(xiàn)不同的情況:
現(xiàn)在,對于相同數(shù)量的元素和相同的視覺外觀,布局的時間要少得多(本例中為分別 3.5 毫秒和 14 毫秒)。務(wù)必記住,對于某些情況,可能無法選擇 Flexbox,因為它沒有浮動那么受支持,但是在可能的情況下,至少應(yīng)研究布局模型對網(wǎng)站性能的影響,并且采用最大程度減少網(wǎng)頁執(zhí)行開銷的模型。
在任何情況下,不管是否選擇 Flexbox,都應(yīng)當(dāng)在應(yīng)用的高壓力點期間嘗試完全避免觸發(fā)布局!
將一幀送到屏幕會采用如下順序:
首先 JavaScript 運行,然后計算樣式,然后布局。但是,JavaScript 在更改元素樣式后,獲取其幾何屬性的值,此時會強(qiáng)制瀏覽器應(yīng)用新樣式提前執(zhí)行布局,值后才能獲取幾何屬性值。這被稱為強(qiáng)制同步布局(forced synchronous layout)。
要記住的第一件事是,在 JavaScript 運行時,來自上一幀的所有舊布局值是已知的,并且可供您查詢。因此,如果(例如)您要在幀的開頭寫出一個元素(讓我們稱其為“框”)的高度,可能編寫一些如下代碼:
// Schedule our function to run at the start of the frame.requestAnimationFrame(logBoxHeight);function logBoxHeight() { // Gets the height of the box in pixels and logs it out. console.log(box.offsetHeight); }
如果在請求此框的高度之前,已更改其樣式,就會出現(xiàn)問題:
function logBoxHeight() { box.classList.add('super-big'); //樣式更改后,瀏覽器必須先應(yīng)用新的樣式(重繪)之后才能獲取當(dāng)前的值,有時是多做無用功 // Gets the height of the box in pixels and logs it out. console.log(box.offsetHeight); }
現(xiàn)在,為了獲得框的高度,瀏覽器必須先應(yīng)用樣式更改(由于增加了 super-big 類),然后運行布局,這時才能返回正確的高度。這是不必要的,并且可能開銷很大。
因此,始終應(yīng)先批量讀取樣式并執(zhí)行(瀏覽器可以使用上一幀的布局值),然后執(zhí)行任何賦值操作。
以上函數(shù)應(yīng)為:
function logBoxHeight() { // Gets the height of the box in pixels and logs it out. console.log(box.offsetHeight); box.classList.add('super-big'); }
大部分情況下,并不需要先應(yīng)用新樣式然后查詢值,使用上一幀的值就足夠了。與瀏覽器同步(或比其提前)運行樣式計算和布局可能成為瓶頸。
有一種方式會使強(qiáng)制同步布局更糟:連續(xù)執(zhí)行大量這種強(qiáng)制布局。如下:
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'; } }
此代碼循環(huán)處理一組段落,并設(shè)置每個段落的寬度以匹配一個稱為“box”的元素的寬度。這看起來沒有害處,但問題是循環(huán)的每次迭代讀取一個樣式值 (box.offsetWidth),然后立即使用此值來更新段落的寬度 (paragraphs[i].style.width)。在循環(huán)的下次迭代時,瀏覽器必須考慮樣式已更改這一事實,因為 offsetWidth 是上次請求的(在上一次迭代中),所以它必須應(yīng)用更改的樣式,然后運行布局。每次迭代都將出現(xiàn)此問題!
此示例的修正方法還是先讀取值,然后寫入值:
// Read.var width = box.offsetWidth;function resizeAllParagraphsToMatchBlockWidth() { for (var i = 0; i < paragraphs.length; i++) {// Now write.paragraphs[i].style.width = width + 'px'; } }
如果要保證安全,應(yīng)當(dāng)查看 FastDOM,它會自動批處理讀取和寫入,應(yīng)當(dāng)能防止意外觸發(fā)強(qiáng)制同步布局或布局抖動。
繪制是填充像素的過程,像素最終合成到用戶的屏幕上。 它往往是管道中運行時間最長的任務(wù),應(yīng)盡可能避免此任務(wù)。
除 transform 或 opacity 屬性之外,更改任何屬性始終都會觸發(fā)繪制。
繪制通常是像素管道中開銷最大的部分,應(yīng)盡可能避免繪制。
通過layer promotion和動畫的編排來減少繪制區(qū)域。
使用 Chrome DevTools paint profile來評估繪制的復(fù)雜性和開銷;應(yīng)盡可能降低復(fù)雜性并減少開銷。
如果觸發(fā)布局,則總是會觸發(fā)繪制,因為更改任何元素的幾何屬性意味著其像素需要修正!
如果更改非幾何屬性,例如背景、文本或陰影,也可能觸發(fā)繪制。在這些情況下,不需要布局,并且管道將如下所示:
您可以使用 Chrome DevTools 來快速確定正在繪制的區(qū)域。打開 DevTools,按下鍵盤上的 Esc 鍵。在出現(xiàn)的面板中,轉(zhuǎn)到“rendering”標(biāo)簽,然后選中“Show paint rectangles”。
打開此選項后,每次發(fā)生繪制時,Chrome 將讓屏幕閃爍綠色。如果看到整個屏幕閃爍綠色,或看到不應(yīng)繪制的屏幕區(qū)域,則應(yīng)當(dāng)進(jìn)一步研究。
Chrome DevTools Timeline 中有一個選項提供更多信息:繪制分析器。要啟用此選項,轉(zhuǎn)至 Timeline,然后選中頂部的“Paint”框。需要注意的是,請務(wù)必僅在嘗試分析繪制問題時才打開此選項,因為它會產(chǎn)生開銷,并且會影響性能分析結(jié)果。最好是在想要更深入了解具體繪制內(nèi)容時使用。
完成了上述設(shè)置之后,現(xiàn)在可以運行 Timeline 錄制,并且繪制記錄將包含更多的細(xì)節(jié)。通過點擊一幀的繪制記錄,您將進(jìn)入該幀的繪制分析器:
點擊繪制分析器將調(diào)出一個視圖,您可以查看所繪制的元素、所花的時間,以及所需的各個繪制調(diào)用:
此分析器顯示區(qū)域和復(fù)雜性(實際上就是繪制所花的時間),如果不能選擇避免繪制,這兩個都是可以設(shè)法修正的方面。
繪制并非總是繪制到內(nèi)存中的單個圖像。事實上,在必要時瀏覽器可以繪制到多個圖像或合成層(compositor layers)。
此方法的優(yōu)點是,定期重繪的或通過變形在屏幕上移動的元素,可以在不影響其他元素的情況下進(jìn)行處理。Sketch、GIMP 或 Photoshop 之類的藝術(shù)文件也是如此,各個層可以在彼此的上面處理并合成,以創(chuàng)建最終圖像。
創(chuàng)建新層的最佳方式是使用 will-change CSS 屬性。此方法在 Chrome、Opera 和 Firefox 上有效,并且通過 transform 的值將創(chuàng)建一個新的合成器層:
.moving-element {
will-change: transform;
}
對于不支持 will-change 但受益于層創(chuàng)建的瀏覽器,例如 Safari 和 Mobile Safari,需要使用3D 變形來強(qiáng)制創(chuàng)建一個新層:
.moving-element {
transform: translateZ(0);
}
但需要注意的是:不要創(chuàng)建太多層,因為每層都需要內(nèi)存和管理開銷。
如果已將一個元素提升到一個新層,可使用 DevTools 確認(rèn)這樣做已帶來性能優(yōu)勢。請勿在不分析的情況下提升元素。
然而有時,雖然提升元素,卻仍需要繪制工作。繪制問題的一個大挑戰(zhàn)是,瀏覽器將兩個需要繪制的區(qū)域聯(lián)合在一起,而這可能導(dǎo)致整個屏幕重繪。因此,如果頁面頂層有一個固定標(biāo)頭,而在屏幕底部還有正在繪制的元素,則整個屏幕可能最終要重繪。
減少繪制區(qū)域往往是編排動畫和變換,使其不過多重疊,或設(shè)法避免對頁面的某些部分設(shè)置動畫。
在談到繪制時,一些繪制比其他繪制的開銷更大。例如,繪制任何涉及模糊(例如陰影)的元素所花的時間將比(例如)繪制一個紅框的時間要長。但是,對于 CSS 而言,這點并不總是很明顯:background: red; 和 box-shadow: 0, 4px, 4px, rgba(0,0,0,0.5); 看起來不一定有截然不同的性能特性,但確實很不相同。
利用上述繪制分析器,您可以確定是否需要尋求其他方式來實現(xiàn)效果。問問自己,是否可能使用一組開銷更小的樣式或替代方式來實現(xiàn)最終結(jié)果。
您要盡可能的避免繪制的發(fā)生,特別是在動畫效果中。因為每幀 10 毫秒的時間預(yù)算一般來說是不足以完成繪制工作的,尤其是在移動設(shè)備上。
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。TEL:177 7030 7066 E-MAIL:11247931@qq.com