性能预算
const budget = {
fcp: 1 * 1000, // 单位:ms
lcp: 2.5 * 1000, // 单位:ms
fid: 0.1 * 1000, // 单位:ms
tbt: 0.3 * 1000, // 单位:ms
cls: 0.1, // 单位:无
tti: 5 * 1000, // 单位:ms
fp: 1 * 1000, // 目前和fcp相同 单位:ms
fps: 45 // 单位:无, 暂时阈值取值为45};
FPS
直观感受,不同帧率的体验: 50 ~ 60 FPS 相当流畅,30 ~ 50 FPS 舒适度因人而异 30 FPS 以下的动画,让人卡顿和不适感; 帧率波动很大的动画,亦会使人感觉到卡顿。
var rAF = function () {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
}
);
}();
var frame = 0;
var allFrameCount = 0;
var lastTime = Date.now();
var lastFameTime = Date.now();
var loop = function () {
var now = Date.now();
var fs = (now - lastFameTime);
var fps = Math.round(1000 / fs);
lastFameTime = now;
// 不置 0,在动画的开头及结尾记录此值的差值算出 FPS
allFrameCount++;
frame++;
if (now > 1000 + lastTime) {
var fps = Math.round((frame * 1000) / (now - lastTime));
console.log(`${new Date()} 1S内 FPS:`, fps);
frame = 0;
lastTime = now;
};
rAF(loop);
}
loop();
FCP
try {
const po = new PerformanceObserver((list, observer) => {
for (const entry of list.getEntries()) {
// 记录条目和所有相关的详细信息。
if( entry.name === "first-contentful-paint" &&
entry.entryType === "paint") {
record({
type: "fcp",
value: entry.startTime,
budget: budget.fcp,
msg: "首次内容绘制(First Contentful Paint,FCP):"
});
}
}
});
po.observe({ type: "paint", buffered: true });
} catch (e) {
console.log(e);
}
}
FP
try {
const po = new PerformanceObserver((list, observer) => {
for (const entry of list.getEntries()) {
// 记录条目和所有相关的详细信息。
if(entry.name === "first-paint" && entry.entryType === "paint") {
record({
type: "fp",
value: entry.startTime,
budget: budget.fp,
msg: "首次绘制即白屏时间(First Paint,FP):"
});
}
}
});
po.observe({ type: "paint", buffered: true });
} catch (e) {
console.log(e);
}
}
LCP
try {
const po = new PerformanceObserver((list, observer) => {
for (const entry of list.getEntries()) {
// 记录条目和所有相关的详细信息。
if( entry.entryType === "largest-contentful-paint") {
record({
type: "lcp",
value: entry.startTime,
budget: budget.lcp,
msg: "最大内容绘制(Largest Contentful Paint , LCP):"
});
}
}
});
po.observe({ type: "largest-contentful-paint", buffered: true });
} catch (e) {
console.log(e);
}
}
FID
try {
const po = new PerformanceObserver((list, observer) => {
for (const entry of list.getEntries()) {
// 记录条目和所有相关的详细信息。
if(entry.entryType === "first-input") {
FI(entry, budget);
}
}
});
po.observe({ type: "first-input", buffered: true });
} catch (e) {
console.log(e);
}
}
function FI(firstInput, budget) {
// 测量第一次输入延迟(FID)。
const firstInputDelay = firstInput.processingStart - firstInput.startTime;
record({
type: "fid",
value: firstInputDelay,
budget: budget.fid,
msg: "第一次输入延迟(First Input Delay, FID):"
});
}
CLS
try {
const takeCls = calcCls();
const po = new PerformanceObserver((list, observer) => {
for (const entry of list.getEntries()) {
// 记录条目和所有相关的详细信息。
if(entry.entryType === "layout-shift") {
takeCls(entry, budget);
}
}
});
po.observe({ type: "layout-shift", buffered: true }); // 布局移位
} catch (e) {
console.log(e);
}
}
function calcCls() {
let cls = 0;
function take(entry, budget) {
// 计算
cls += entry.value;
record({
type: "cls",
value: cls,
budget: budget.cls,
msg: "累计布局偏移(Cumulative Layout Shift, CLS): "
});
return cls;
}
return take;
}
TTI 和 TBT
import ttiPolyfill from "tti-polyfill";
try {
const takeTtiAndTbt = calcByLongtask(budget);
const po = new PerformanceObserver((list, observer) => {
for (const entry of list.getEntries()) {
// 记录条目和所有相关的详细信息。
if(entry.entryType === "longtask") { // TTI 和 TBT
takeTtiAndTbt(entry, budget);
}
}
});
po.observe({ type: "longtask" });
} catch (e) {
console.log(e);
}
}
function calcByLongtask(budget) {
let longtasks = [];
let longTaskTimeId = null;
let zeroFctTimeId = null;
zeroFctTimeId = setTimeout(() => {
// 当没有长任务执行的时候。
if (resultData.tti === 0) {
ttiPolyfill
.getFirstConsistentlyInteractive({ minValue: resultData.fcp })
.then(tti => {
record({
type: "tti",
value: tti,
budget: budget.tti,
msg: "交互时间3(Time to Interactive , TTI): "
});
});
record({
type: "tbt",
value: 0,
budget: budget.tbt,
msg: "总阻塞时间3(Total Blocking Time, TBT): "
});
}
}, 5 * 1000);
function take(entry, budget) {
if (resultData.fcp === 0 || resultData.tti !== 0) {
return;
}
clearTimeout(zeroFctTimeId);
if (resultData.tbt === 0 && resultData.tti === 0) {
longtasks.push(entry);
}
ttiPolyfill
.getFirstConsistentlyInteractive({ minValue: resultData.fcp })
.then(tti => {
record({
type: "tti",
value: tti,
budget: budget.tti,
msg: "交互时间2(Time to Interactive , TTI): "
});
let tbt =
longtasks
.map(item => item.duration)
.reduce((total, next) => total + next) -
longtasks.length * 50;
record({
type: "tbt",
value: tbt,
budget: budget.tbt,
msg: "总阻塞时间2(Total Blocking Time, TBT): "
});
});
}
return take;
}