lighthouse https://m.jd.com --locale zh --quiet --chrome-flags="--headless" --only-categories=performance
npm install express morgan compression --save
site\index.js
const express = require('express');
const logger = require('morgan');
const delayConfig = require('./delayConfig');
const app = express();
app.use(logger('dev'));
app.use((req, res, next) => {
let url = req.url;
let delay = delayConfig[url];
if (delay) {
setTimeout(next, delay);
} else {
next();
}
});
app.use(express.static('public'));
app.listen(80, () => console.log(`server started at 80`));
site\delayConfig.js
const delayConfig = {
"/index.html": 100
}
module.exports = delayConfig;
site\public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lighthouse</title>
</head>
<body>
<canvas style="width:100%;height:500px;"></canvas>
<div>hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello</div>
<script src="/perf.js"></script>
</body>
</html>
site\public\perf.js
(function (ready) {
if (document.readyState === "complete" || document.readyState === "interactive") {
ready();
} else {
document.addEventListener("readystatechange", function () {
if (document.readyState === "complete") {
ready();
}
});
}
})(function perf() {
var data = {
url: window.location.href,
FP: 0,
FCP: 0
};
new PerformanceObserver(function (entryList) {
var entries = entryList.getEntries() || [];
entries.forEach(function (entry) {
if (entry.name === "first-paint") {
data.FP = entry.startTime;
console.log("记录FP: " + data.FP);
} else if (entry.name === "first-contentful-paint") {
data.FCP = entry.startTime;
console.log("记录FCP: " + data.FCP);
}
});
}).observe({ type: "paint", buffered: true });
});
gzip
压缩dns-prefetch
进行DNS进行预解析site\index.js
const express = require('express');
const logger = require('morgan');
+const compression = require('compression')
const delayConfig = require('./delayConfig');
const app = express();
app.use(logger('dev'));
app.use((req, res, next) => {
let url = req.url;
let delay = delayConfig[url];
if (delay) {
setTimeout(next, delay);
} else {
next();
}
});
+app.use(compression());
app.use(express.static('public', {
setHeaders
}));
function setHeaders(res, path) {
res.setHeader('cache-control', 'no-cache')
}
app.listen(80, () => console.log(`server started at 80`));
site\public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link rel="dns-prefetch" href="//static.360buyimg.com" />
<title>lighthouse</title>
</head>
<body>
<img id="banner" style="width:500px;height:300px;" src="/1.png" /><br />
+ <img src="https://pic0.iqiyipic.com/image/20220107/27/cb/v_165289132_m_601_480_270.jpg" />
<img src="/2.png" />
<script src="/perf.js"></script>
</body>
</html>
webfont
加载期间保持可见@font-face {
font-family: 'Pacifico';
font-style: normal;
font-weight: 400;
src: local('Pacifico Regular'), local('Pacifico-Regular'), url(https://fonts.gstatic.com/s/pacifico/v12/FwZY7-Qmy14u9lezJ-6H6MmBp0u-.woff2) format('woff2');
font-display: swap;
}
site\public\worker.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>webworker</title>
</head>
<body>
<script>
function start() {
//sum();
const worker = new Worker('/worker.js');
worker.postMessage(100000000);
worker.addEventListener('message', function (event) {
console.log('sum:', event.data);
});
}
function sum() {
let total = 0;
for (let i = 0; i < 100000000; i++) {
total += i;
}
console.log('sum:', total);
}
start();
</script>
</body>
</html>
site\public\worker.js
self.addEventListener('message', function (event) {
let total = 0;
for (let i = 0; i < event.data; i++) {
total += i;
}
self.postMessage(total);
});
强制同步布局
接口对象 | 属性名 |
---|---|
Element | clientHeight, clientLeft, clientTop, clientWidth, focus, getBoundingClientRect, getClientRects, innerText, offsetHeight, offsetLeft, offsetParent, offsetTop, offsetTop, offsetWidth, outerText, scrollByLines, scrollByPages, scrollLeft, scrollHeight, scrollIntoView, scrollIntoViewIfNeeded, scrollTop, scrollWidth |
MouseEvent | layerX layerY offsetX offsetY |
Window | getComputedStyle scrollBy scrollTo scroll scrollY |
Frame,Document,Image | height width |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>layout</title>
</head>
<body>
<div id="root"></div>
<script>
function reflow() {
let container = document.getElementById('root');
let div1 = document.createElement('div');
div1.innerHTML = 'hello';
container.appendChild(div1);
console.log(container.offsetHeight);
let div2 = document.createElement('div');
div2.innerHTML = 'world';
container.appendChild(div2);
requestAnimationFrame(reflow);
}
requestAnimationFrame(reflow);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>layout</title>
<style>
.box {
width: 100px;
border: 1px solid green;
}
</style>
</head>
<body>
<div class="box">box1</div>
<div class="box">box2</div>
<div class="box">box3</div>
<div class="box">box4</div>
<div class="box">box5</div>
<script src="https://cdn.bootcdn.net/ajax/libs/fastdom/1.0.10/fastdom.js"></script>
<script>
let boxes = document.querySelectorAll('.box');
for (let i = 0; i < boxes.length; i++) {
const box = boxes[i];
fastdom.measure(() => {
const offsetWidth = box.offsetWidth;
fastdom.mutate(() => {
box.style.width = offsetWidth + 5 + 'px';
});
});
}
</script>
</body>
</html>
site\public\perf.js
(function (ready) {
if (document.readyState === "complete" || document.readyState === "interactive") {
ready();
} else {
document.addEventListener("readystatechange", function () {
if (document.readyState === "complete") {
ready();
}
});
}
})(function perf() {
var data = {
url: window.location.href,
FP: 0,
FCP: 0,
+ LCP: 0
};
new PerformanceObserver(function (entryList) {
var entries = entryList.getEntries() || [];
entries.forEach(function (entry) {
if (entry.name === "first-paint") {
console.log("记录FP: " + (data.FP = entry.startTime));
} else if (entry.name === "first-contentful-paint") {
console.log("记录FCP: " + (data.FCP = entry.startTime));
}
});
}).observe({ type: "paint", buffered: true });
+ new PerformanceObserver(function (entryList) {
+ var entries = entryList.getEntries() || [];
+ entries.forEach(function (entry) {
+ if (entry.startTime > data.LCP) {
+ console.log("记录LCP: " + (data.LCP = entry.startTime));
+ }
+ });
+ }).observe({ type: "largest-contentful-paint", buffered: true });
});
<link rel="preload" as="style" href="css/style.css">
site\public\perf.js
(function (ready) {
if (document.readyState === "complete" || document.readyState === "interactive") {
ready();
} else {
document.addEventListener("readystatechange", function () {
if (document.readyState === "complete") {
ready();
}
});
}
})(function perf() {
var data = {
url: window.location.href,
FP: 0,
FCP: 0,
LCP: 0,
+ FID: 0
};
new PerformanceObserver(function (entryList) {
var entries = entryList.getEntries() || [];
entries.forEach(function (entry) {
if (entry.name === "first-paint") {
console.log("记录FP: " + (data.FP = entry.startTime));
} else if (entry.name === "first-contentful-paint") {
console.log("记录FCP: " + (data.FCP = entry.startTime));
}
});
}).observe({ type: "paint", buffered: true });
new PerformanceObserver(function (entryList) {
var entries = entryList.getEntries() || [];
entries.forEach(function (entry) {
if (entry.startTime > data.LCP) {
console.log("记录LCP: " + (data.LCP = entry.startTime));
}
});
}).observe({ type: "largest-contentful-paint", buffered: true });
+ new PerformanceObserver((entryList) => {
+ for (const entry of entryList.getEntries()) {
+ const FID = entry.processingStart - entry.startTime;
+ console.log('FID:', FID, entry);
+ }
+ }).observe({ type: 'first-input', buffered: true });
});
site\public\perf.js
(function (ready) {
if (document.readyState === "complete" || document.readyState === "interactive") {
ready();
} else {
document.addEventListener("readystatechange", function () {
if (document.readyState === "complete") {
ready();
}
});
}
})(function perf() {
var data = {
url: window.location.href,
FP: 0,
FCP: 0,
LCP: 0,
FID: 0,
CLS: 0
};
new PerformanceObserver(function (entryList) {
var entries = entryList.getEntries() || [];
entries.forEach(function (entry) {
if (entry.name === "first-paint") {
console.log("记录FP: " + (data.FP = entry.startTime));
} else if (entry.name === "first-contentful-paint") {
console.log("记录FCP: " + (data.FCP = entry.startTime));
}
});
}).observe({ type: "paint", buffered: true });
new PerformanceObserver(function (entryList) {
var entries = entryList.getEntries() || [];
entries.forEach(function (entry) {
if (entry.startTime > data.LCP) {
console.log("记录LCP: " + (data.LCP = entry.startTime));
}
});
}).observe({ type: "largest-contentful-paint", buffered: true });
new PerformanceObserver((entryList) => {
for (const entry of entryList.getEntries()) {
const FID = entry.processingStart - entry.startTime;
console.log('FID:', FID, entry);
}
}).observe({ type: 'first-input', buffered: true });
+ new PerformanceObserver((entryList) => {
+ var entries = entryList.getEntries() || [];
+ entries.forEach(function (entry) {
+ console.log('entry', entry);
+ if (!entry.hadRecentInput) {
+ data.CLS += entry.value;
+ console.log("CLS: " + data.CLS);
+ }
+ });
+ }).observe({ type: 'layout-shift', buffered: true });
});
site\public\perf.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>lighthouse</title>
+ <style>
+ @keyframes grow {
+ from {
+ /**transform: scaleY(0);**/
+ height: 200px;
+ }
+ to {
+ /**transform: scaleY(2);**/
+ height: 500px;
+ }
+ }
+ #banner {
+ animation: grow 3s infinite;
+ }
+ </style>
</head>
<body>
+ <img id="banner" style="width:500px;height:300px;" src="/1.png" /><br />
+ <img src="/2.png" />
<script src="/perf.js"></script>
</body>
</html>
事件 | 含义 |
---|---|
beforeunload | 事件触发于 window、document 和它们的资源即将卸载时 |
navigationstart | 相同的浏览环境下卸载前一个文档结束之时 |
pagehide | 当浏览器在显示与会话历史记录不同的页面的过程中隐藏当前页面时, pagehide(页面隐藏)事件会被发送到一个Window |
visibilitychange | 当浏览器的某个标签页切换到后台,或从后台切换到前台时就会触发该消息 |
unload | 当文档或一个子资源正在被卸载时, 触发 unload事件 |
unloadEventEnd | 事件处理程序结束之时 |
send request | 发送请求 |
receive data | 接收响应 |
commitNavigationEnd | 提交本次导航结束 |
domLoading | 解析器开始工作时 |
事件 | 含义 |
---|---|
receive data | 接收数据 |
complete loading | 完成加载 |
parseHTML | 解析HTML |
recalculateStyle | 重新计算样式 |
layout | 布局 |
update layer tree | 更新图层树 |
paint | 绘制 |
raster | GPU光栅化 |
compositor | 复合图层 |
display | 显示 |
dominteractive | 主文档的解析器结束工作时 |
readystatechange | interactive(可交互) |
domContentLoadedEventStart | 所有的需要被运行的脚本已经被解析之时 |
DOMContentLoaded | 当初始的 HTML 文档被完全加载和解析完成之后,DOMContentLoaded 事件被触发,而无需等待样式表、图像和子框架的完全加载 |
domContentLoadedEventEnd | 这个时刻为所有需要尽早执行的脚本不管是否按顺序,都已经执行完毕 |
domComplete | 主文档的解析器结束工作 |
readystatechange | complete(完成) |
loadEventStart | load事件被现在的文档触发之时 |
load | 当整个页面及所有依赖资源如样式表和图片都已完成加载时,将触发load事件 |
loadEventEnd | load事件处理程序被终止 |
pageshow | 当一条会话历史记录被执行的时候将会触发页面显示(pageshow)事件 |
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<html>
<head>
<title>hello</title>
<style>
#posts {
width: 300px;
height: 300px;
background-color: green;
}
.post {
width: 300px;
height: 100px;
background-color: red;
}
</style>
</head>
<body>
<div id="posts"></div>
<script>
function addPost() {
const posts = document.getElementById('posts');
const element = document.createElement('div');
element.className = 'post';
element.innerHTML = 'post';
posts.appendChild(element);
}
addPost()
</script>
</body>
</html>
</head>
<body>
</body>
</html>
document.addEventListener('readystatechange', (event) => {
console.log(event, document.readyState);
});
preconnect
或 dns-prefetch
资源提示,以尽早与重要的第三方来源建立连接passive
,以提高页面的滚动性能,passive
不会对事件的默认行为说 nounload
事件不会可靠地触发,而且监听该事件可能会妨碍系统实施“往返缓存”之类的浏览器优化策略。建议您改用pagehide
或visibilitychange
事件<link rel=preload>
来优先提取当前在网页加载后期请求的资源document.write()
动态注入的外部脚本可将网页加载延迟数十秒<meta name="viewport">
不仅会针对移动设备屏幕尺寸优化您的应用,还会阻止系统在响应用户输入前出现 300 毫秒的延迟