咱們都知道網站性能很重要。那在說到網站性能、網站速度很快的時候,咱們具體指的是什麼呢?git
首先咱們要知道,網站打開速度是一個相對的概念。同一個網站,在高端機上或者網絡優良的狀況下,能夠是很快的,在低端機,網絡差的狀況卻很慢。github
下圖是一個網站的打開時間分佈圖,X 軸是耗時區間,Y 軸是用戶量。首先從這個圖中,咱們能夠知道,網站打開速度並非一個固定的數值,並且一個區分分佈。雖然大部分用戶打開時間在 2s 內,但其實仍是存在少許用戶,他們的打開時間大於 10 秒。web
還有一些其餘的概念:canvas
那麼咱們該若是衡量網站性能呢,站在用戶的角度上想,咱們須要回答一下幾個問題?瀏覽器
是否發生? | 導航發生了嗎?服務器響應了嗎? |
是否有用? | 頁面是否渲染出足夠可用內容 |
是否可用? | 用戶能夠與該頁面進行交互,仍是仍在加載中? |
是否使人愉快? | 交互是否順暢天然,沒有滯後和卡頓? |
先用一個直觀的圖片來展現,頁面顯示的各個階段。緩存
FP (First Paint) 首次繪製,表明瀏覽器第一次像屏幕傳輸像素的時間,也就是頁面在屏幕上首次發生視覺變化的時間。安全
FCP (First Contentful Paint) 首次內容繪製,表明瀏覽器第一次繪製內容的時間。服務器
如何計算markdown
function showPaintTimings() {
if (window.performance) {
let performance = window.performance;
let performanceEntries = performance.getEntriesByType('paint');
performanceEntries.forEach((performanceEntry, i, entries) => {
console.log(
'The time to ' +
performanceEntry.name +
' was ' +
performanceEntry.startTime +
' milliseconds.'
);
});
} else {
console.log("Performance timing isn't supported.");
}
}
showPaintTimings();
// The time to first-paint was 949.5900000038091 milliseconds.
// The time to first-contentful-paint was 949.5900000038091 milliseconds.
複製代碼
FP 不包含默認背景繪製,可是包含非默認背景繪製。網絡
只有繪製文本、圖片(包含背景圖)、非白色的 svg 或者 canvas 是才被算做 FCP
FMP (First Meaningful Paint) 首次有效繪製,是頁面主要內容繪製的時間點。
在打開一個網頁的時候,隨着網頁的加載與解析,瀏覽器會將佈局對象(Layout Object)逐步添加到佈局樹(Layout Tree)上進行佈局。
將兩張圖結合起來解讀
從以上對於「谷歌搜索結果頁」加載過程的例子中能夠發現,佈局對象的數量與頁面完成度高度相關。咱們得出如下結論:
FMP = 頁面在加載和渲染過程當中最大布局變更以後的那個繪製時間
複製代碼
基於剛剛得出的結論:FMP 的時間點爲 DOM 結構變化最劇烈的時間點。DOM 結構變化的時間點能夠經過 MutationObserver API 來得到。
// 用於存放每次 dom 變話時,時間和 dom 數量
const list = [];
// 起止時間
const startTime = Date.now();
// 建立監聽
const observer = new MutationObserver(callback);
// 監聽 dom 變動
observer.observe(document, {
childList: true,
subtree: true,
});
// dom 變動回調
function callback() {
const duration = Date.now() - startTime;
const body = document.querySelector('body');
list.push({
number: body ? count(body) : 0,
duration,
});
}
// 計算 dom 數量
function count(element) {
let number = 0;
const childrenLength = element.children ? element.children.length : 0;
if (childrenLength > 0) {
const children = element.children;
for (let length = childrenLength - 1; length >= 0; length--) {
number += count(children[length]);
}
}
number += 1;
return number;
}
// 找出變化作大的時間點, 計作 FMP
function getFmp() {
let result;
for (let i = 1; i < list.length; i++) {
const diff = list[i].number - list[i - 1].number;
if (!result.diff || diff > result.diff) {
result = {
duration: list[i].duration,
diff,
};
}
}
return result?.duration || 0;
}
複製代碼
當前方法能夠在大部分狀況下得出 FMP 值,可是在一些其餘場景下,仍是存在誤差。
圖 3 是微博頁面在加載和渲染的可視化過程
圖 4 展現了當加載微博頁面時,被逐步加載到佈局樹中的佈局對象的數量。
經過圖 3 圖 4 能夠看出,主要元素加載時間在 6.047 秒,可是佈局對象發生變化最大的時候,是 24.25 秒。在 24.25 秒的時候,頁面底部到可見區域外,大量元素被添加到佈局樹上。
爲了優化上述現象,咱們引入佈局意義概念。
佈局意義 = 添加的佈局對象數量 / max(1, 頁面高度 / 屏幕高度)
複製代碼
// 計算頁面比率
function getRatio() {
const clinetH = document.body.clientHeight;
const screenH = window.screen.availHeight;
return Math.max(clinetH / screenH, 1);
}
// 更新 callback 方法
function callback() {
const duration = Date.now() - startTime;
const body = document.querySelector('body');
list.push({
number: body ? count(body) / getRatio(): 0,
duration,
});
}
複製代碼
圖 5 展現了當加載微博頁面時,佈局意義變化狀況。佈局意義最大變化發生在 5.89 秒,在 FMP(6.047)秒以前,符合預期。
TTI (Time to Interactive) 可交互時間,表明網頁第一次達到可交互的時間點。首頁是頁面的 ui 是可交互的狀態,而且無場長任務運行,即頁面是流暢的。
下圖演示瞭如何查找 TTI。
如何計算
谷歌團隊開發了一個polyfill,用於檢測 TTI,適用於全部支持 Long Tasks API 的瀏覽器。
import ttiPolyfill from './path/to/tti-polyfill.js';
ttiPolyfill.getFirstConsistentlyInteractive(opts).then((tti) => {
// Use `tti` value in some way.
});
複製代碼
長任務(Long Tasks API):任何連續不間斷的且主 UI 線程繁忙 50 毫秒及以上的時間區間。
頁面」是否使人愉快「,主要有幾個角度,動畫是否流暢,用戶交互是否能夠快速影響。而卡頓、交互響應慢的狀況一般由長任務致使的,瞭解長任務的發生頻率,能夠幫助咱們判斷頁面是否流暢。
如何計算
var observer = new PerformanceObserver(function(list) {
var perfEntries = list.getEntries();
for (var i = 0; i < perfEntries.length; i++) {
// Process long task notifications:
// report back for analytics and monitoring
// ...
}
});
// register observer for long task notifications
observer.observe({ entryTypes: ['longtask'] });
// Long script execution after this will result in queueing
// and receiving "longtask" entries in the observer.
複製代碼
下圖1、圖二是 Navigation Timing API 初版和第二版,該 API 提供了可用於衡量一個網站性能的數據。記錄了頁面重定向、DNS 查詢、TCP、SSL 鏈接、內容請求、DOM 解析等的時間點。
指標 | 說明 |
---|---|
startTime | 0 |
unloadEventStart | 上一個頁面 unload 事件的觸發時間,只有同源跳轉能夠記錄,非同源返回 0 |
unloadEventEnd | 上一個頁面 unload 事件的結束時間 |
redirectStart | 第一個 http 重定向請求發起時間,只有同源跳轉能夠記錄,非同源返回 0 |
redirectEnd | 最後一個 http 重定向請求發起時間 |
fetchStart | 請求開始時間,發生在檢查本地緩存以前 |
domainLookupStart | 域名解析(DNS)開始時間,若是存在本地緩存,則該值等同於 fetchStart |
domainLookupEnd | 域名解析(DNS)結束時間,若是存在本地緩存,則該值等同於 fetchStart |
connectStart | 請求鏈接被髮送到網絡的時間 |
secureConnectionStart | 安全鏈接握手開始的時間 |
connectEnd | 網絡連接創建的時間 |
requestStart | 瀏覽器發送文檔請求的開始時間 |
responseStart | 瀏覽器接收到響應的第一個字節的時間 |
responseEnd | 瀏覽器接收到響應的最後一個字節的時間 |
domInteractive | 主文檔的解析器結束工做,Document.readyState 改變爲 interactive 的時間,至關於 readystatechange 時間觸發的時間 |
domContentLoadedEventStart | 全部的須要被運行的腳本已經被解析,即 DOMContentLoaded 事件觸發時間 |
domContentLoadedEventEnd | 全部的須要被運行的腳本已經執行完畢 |
domComplete | 主文檔的解析器結束工做,Document.readyState 變爲 complete |
loadEventStart | load 事件觸發時間 |
loadEventEnd | load 時間完成時間 |
階段 | 計算方式 |
---|---|
DNS 查詢 | domainLookupEnd - domainLookupStart |
TCP 鏈接 | connectEnd - connectStart |
SSL 建連 | connectEnd - secureConnectionStart |
首字節網絡請求(TTFB) | responseStart - requestStart |
內容傳輸 | responseEnd - responseStart |
DOM 解析 | domInteractive - responseEnd |
白屏時間 | domInteractive - t.fetchStart |
資源加載 | loadEventStart - domContentLoadedEventEnd |
DOM Ready | domContentLoadedEventEnd - fetchStart |
相關文章