倉庫完整代碼:first-screen-paint,若是來過,期待留下你的一顆小星星~css
1. First Paint(FP)node
First Paint
的定義是渲染樹首次轉變爲屏幕像素的過程,咱們用FP time
來表達首次渲染時間。在FP
以前咱們看見的屏幕是空白的,那麼FP time
也可理解爲白屏時間。如何計算呢?git
if (window.performance) {
let pf = window.performance;
let pfEntries = pf.getEntriesByType('paint')
let fp = pfEntries.find(each => each.name === 'first-paint')
console.log('first paint time: ', fp && fp.startTime)
}
複製代碼
2. First Contentful Paint(FCP):github
FCP
定義的是從頁面加載到屏幕上首次有渲染內容的過程,這裏的內容能夠是文本、圖像、svg
元素和非白色canvas
元素。在下圖加載時間線中,圖二是FCP
的時間點: 咱們用
FCP time
來表達內容首次渲染時間。如何計算呢?web
if (window.performance) {
let pf = window.performance;
let pfEntries = pf.getEntriesByType('paint')
let fp = pfEntries.find(each => each.name === 'first-contentful-paint')
console.log('first paint time: ', fp && fp.startTime)
}
複製代碼
須要區別於FP,總有FP time ≤ FCP time。canvas
3. First Meaningful Paint(FMP)api
FMP
定義的是從頁面開始加載到渲染出主要內容的過程,這個「主要內容」的定義依賴於各瀏覽器中的實現細節,所以它並無做爲一個標準化的指標。在Chrome的Lighthouse面板中咱們能夠看到這個指標: 瀏覽器
4. Largest Contentful Paint(LCP)markdown
FMP
的範圍很差界定,但LCP
的範圍是恆定的,它定義的是頁面開始加載到渲染出(視口內)最大內容(文本或圖像等)的過程。以下圖加載時間線: app
第一個示例中,Instagram logo是視口中的最大內容,第二個示例中,綠色的文本是視口中的最大內容塊。咱們用
LCP time
表達最大內容渲染時間,如何計算呢?
new PerformanceObserver(list => {
let entries = list.getEntriesByType('largest-contentful-paint');
entries.forEach(item => {
console.log('largest contentful pain time: ', item.startTime)
})
}).observe({ entryTypes: ['largest-contentful-paint'] });
複製代碼
咱們這裏定義的首屏是指頁面無滾動的狀況下,從開始加載到視窗第一屏內容渲染完成的過程,遵循上面幾個概念的定義,咱們能夠稱它爲 last contentful paint
,亦或first screen paint
更貼切一些。在本文,咱們就把首屏渲染時間叫作first screen paint time(FSP time
),要如何來統計呢?
先考慮最簡單的場景:咱們的頁面是純靜態文本型的,即首屏裏面沒有圖片,內容是靜態文本。
咱們要先解決一個問題:如何界定哪些元素是屬於屏內的?
1. getBoundingClientRect
getBoundingClientRect
用於獲取某個元素相對於視窗的位置,理論上咱們只要計算每個元素的位置信息,結合視窗的高度信息,咱們就能判斷元素是否屬於屏內。
但在真實狀況下,一個頁面dom的數量是很龐大的,大量的dom操做自己就會影響整個頁面的性能!況且,getBoundingClientRect
會引發頁面重排(what forces reflow/layout),這並非一個理想的方案;
2. IntersectionObserver + MutationObserver
IntersectionObserver
經過啓動一個觀察器,以一種異步的方式檢查目標元素是否出現於視窗(viewport)中,它返回的數據裏面包含了兩個重要的信息:
接下來咱們須要給每個元素添加一個intersection觀察器,MutationObserver
能夠幫助咱們,它提供了監視dom樹變動的能力,咱們使用它監視document
根節點的子樹的變化,爲新增的每個子節點註冊一個IntersectionObserver
,參考以下代碼:
// 註冊可視性監聽器
const isObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// 屏內元素
if (entry.intersectionRatio > 0) {
// 記錄節點及其時間,這裏也可使用人工打點的方式:performance.now()
console.log(`${entry.target}: ${entry.time}`);
}
});
});
// 註冊DOM樹變動監聽器
const muObserver = new MutationObserver((mutations) => {
if (!mutations) return;
mutations.forEach((mu) => {
if (!mu.addedNodes || !mu.addedNodes.length) return;
mu.addedNodes.forEach((ele) => {
// 只對元素節點進行監聽
if (ele.nodeType === 1) {
// 添加可視性變化監聽器
isObserver.observe(ele);
}
});
});
});
// 監聽document的子樹變化
muObserver.observe(document, {
childList: true,
subtree: true
});
複製代碼
更完整的代碼參考:first-screen-paint
場景2:首屏包含圖片資源,多是圖片元素或背景,須要計算加載最慢那張圖片資源的耗時
問題1:圖片資源是異步加載的,如何獲取資源的請求耗時?
前文咱們介紹了獲取LCP time的方法,用相似的方式,咱們也能獲取圖片資源的耗時,使用PerformanceObserver
api監聽資源的加載耗時,它返回的數據裏面包含了幾個重要的信息:
responseEnd
於startTime
的差值;const pfObserver = new PerformanceObserver((list) => {
const entries = list.getEntriesByType('resource');
entries.forEach((item) => {
// 各類資源的耗時
// 首屏圖片資源白名單:imgUrlWhiteList = []
console.log(`${item.name: ${item.duration}}`);
});
});
// 設定性能監聽類別:資源
pfObserver.observe({ entryTypes: ['resource'] });
複製代碼
問題2:上面代碼中咱們監聽了全部資源的請求,如何取出首屏的圖片資源請求?
MutationObserver
或者IntersectionObserver
監聽器中直接操做dom讀取img
的src
或者data-src
屬性,把圖片URL保存起來;background-image
的值;場景3:首屏內容是動態fetch的,甚至fetch的是圖片資源,就如商城首頁?
數據是動態fetch的,若是是純文本數據,無圖片資源。咱們的DOM樹變動監聽器能夠監聽到數據返回以後的渲染狀況,渲染過程會收集這些節點的可見性變化時間(這個時間確定是在fetch數據返回時間點以後的);若是渲染的是圖片資源,那麼就進入了上一個處理圖片資源的場景。
1. 首屏內容還在加載中,用戶觸發了頁面滾動?
頁面滾動以後,第二屏的內容就會出如今視窗,本來屬於首屏的內容(部份內容可能並未完成渲染)卻沒在視窗中。那麼,按照如上的統計方式,就會統計到當前處於視窗內容的渲染時間,這可能就是一個「偏差」。
咱們須要一個共識:在首屏內容徹底渲染以前頁面觸發了滾動,說明頁面已是一個可交互的狀態,這種狀況下,咱們認爲,用戶觸發滾動時那一幀的內容,已是用戶和開發者雙方都能接受的首屏內容。基於這個前提,咱們的處理方式是:
在頁面滾動時,加一個鎖,中止監聽後續內容的變動,以初次滾動的時間點爲時間界線,統計在此時間點前發出的(依據startTime
)全部資源的請求耗時和dom樹節點的渲染時間;
2. 在場景3下,首屏內容未加載完,用戶觸發了頁面滾動?
在本測試demo中,頁面的主體內容是img元素,按照LCP
(lagest contentful paint)的定義,LCP time
會返回這張圖片渲染的時間;而咱們的首屏內容亦是這張圖片,那麼咱們的FSP time
應該基本等於LCP time
,在下面截圖中,也基本驗證了這一點!
最後,對於上面提到的幾個問題,各位讀者有任何見解也可在評論區留言~
倉庫地址:first-screen-paint,歡迎提issue~
參考: