参考

https://segmentfault.com/a/1190000022205291
https://github.com/fouber/blog
https://segmentfault.com/a/1190000022696744

补充

字体图标代替图片图标

卡顿

动画或页面的帧率比设备屏幕刷新率(常见 60Hz)低时,会有卡顿的感觉

https://zhuanlan.zhihu.com/p/25166666
https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame

渲染流程

(js ->) style -> layout -> paint -> composite -> screen

Chrome devtool 中查看卡顿的帧

采集卡顿程度

https://fed.taobao.org/blog/taofed/do71ct/web-performance/?spm=taofed.blogs.blog-list.8.75e65ac8r0IX6q

1
2
3
4
5
var t = new Date();
setInterval(function () {
console.log(new Date() - t);
t = new Date();
}, 100);

解决卡顿

  • 对于 60Hz 来说,1 帧的时长约 (1000/60)ms,除去系统上下文切换开销,每一帧中只留给我们 10ms 左右的脚本处理时间
  • js 脚本修改 DOM 造成 layout repaint,若脚本执行耗时长,这一帧耗时便长,屏幕刷新时这一帧还没更新就卡顿了,如何解决:
    • 拆分该脚本,分在几帧里执行。帧耗时缩短,帧数变多,帧频提升
    • 简化 DOM 结构

DOMContentLoaded 和 load 事件

DOMContentLoaded 事件 页面 DOM 加载完成即触发,而无需等待样式表、图像和子框架的完全加载
load 事件 全加载完毕

页面加载耗时

首屏耗时

在 window.onload 事件里执行 new Date() - performance.timing.navigationStart 即可获取首屏时间

适用于 SPA 的路由切换

Vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/** 加法 */
function accAdd(arg1, arg2) {
var r1, r2, m;
try {
r1 = arg1.toString().split(".")[1].length;
} catch (e) {
r1 = 0;
}
try {
r2 = arg2.toString().split(".")[1].length;
} catch (e) {
r2 = 0;
}
m = Math.pow(10, Math.max(r1, r2));
return (arg1 * m + arg2 * m) / m;
}

router.afterEach((to, from) => {
/** 累计所加载的资源,不包含当前页面 */
let accumulatedRs = window.performance
.getEntriesByType("resource")
.map((item) => {
let newItem = {
name: item.name,
duration: item.duration,
};
return newItem;
});

/** 存储中的 accumulatedRs */
let storageRs = localStorage.accumulatedRs;
let storageRsLength = 0;
if (storageRs && storageRs.startsWith("[")) {
// 刷新页面之前, accumulatedRs 会一直累加;刷新后, accumulatedRs 会被重置,所以会出现 storageRs 比 accumulatedRs 要多的情况
if (JSON.parse(storageRs).length >= accumulatedRs.length) {
storageRs = localStorage.accumulatedRs = "";
storageRsLength = 0;
} else {
storageRsLength = JSON.parse(storageRs).length;
}
} else {
storageRs = localStorage.accumulatedRs = "";
storageRsLength = 0;
}

let loadTime = 0;
let len = accumulatedRs.length - storageRsLength;
for (let i = 0; i < len; i++) {
const r = accumulatedRs.slice(-len)[i];
loadTime = accAdd(loadTime, r.duration);
}
localStorage.accumulatedRs = JSON.stringify(accumulatedRs);
let lastPage = router.resolve(from).href;
console.log(`上个页面 = ${lastPage} --- 加载耗时 = ${loadTime.toFixed(2)}ms`);
});

浏览器的渲染是单线程的

浏览器的渲染是单线程的,在某一个时刻要么在进行 JS 运算,要么在进行 UI 渲染。不会同时进行