页面性能包含了服务器请求和响应、加载、执行脚本、渲染、布局和绘制每个像素到屏幕上。这里我们只讨论[渲染、布局和绘制]这个过程。
假设我们通过请求得到了页面加载所需的资源js、css、图片等等,页面开始渲染,主要有以下几步:
- 浏览器解析 HTML,将接收到的数据 转化为 DOM 树,解析过程中如果发现引用了外部资源则暂停解析,加载外部资源,加载完成后解析剩余HTML
- 解析Css,构造 CSS 模型,等到 DOM 和 CSSOM 完成之后,浏览器构造渲染树。
- 计算所有可见内容的样式,一旦渲染树完成布局开始,定义所有渲染树元素的位置和大小。完成之后,页面被渲染到屏幕上。这一步也称为回流重绘
一句话总结: 页面渲染主要分五个步骤:构建DOM -> 构建CSSOM -> 构建渲染树 -> 布局 -> 绘制。
所以当我们更改html中DOM结构或者CSS样式时,都会导致页面从头再渲染一次,并触发回流、重绘。目前市面上的前端框架基虚拟DOM的思路主要就是从这一角度出发,通过虚拟DOM的对比,最小颗粒度的更新真实DOM,减少页面在回流、重绘上耗费的成本。
关于回流&重绘的解释:
- 回流:在页面中,当我们对元素的宽高大小进行改变时,会触发该元素以及相应的其他元素重新计算布局,这个过程就发生了回流。
- 重绘:重绘意味着元素只是进行了外观的变化,比如背景色、前景色,但是位置大小不变,这时候元素会重新绘制,相应的也不会对周边的元素产生影响。
所以我们说回流一定会发生重绘,重绘不一定导致回流,重绘的性能优于回流。
既然清楚了浏览器渲染页面的大致过程,那么我们也可以做一些对应的优化
减少频繁获取dom实例,可以使用变量临时保存。
因为在浏览器中,DOM的渲染跟JS的执行,分别发生在渲染引擎跟js引擎中,当我们通过JS操作DOM的时候,其实就是在两个线程之间通信,如果操作DOM较为频繁,一来一回对于性能的消耗比较高,并且在DOM的尺寸/外观变化时,还会发生回流、重绘,对于性能的影响就更严重了。
所以现代化的前端框架都是用了虚拟DOM的概念,通过虚拟dom的对比,使真实DOM的更新更“具体”,COPY1
2
3
4
5
6
7
8// 优化前
<script type="text/javascript">
function test () {
for(var count = 0; count < 15000; count++){
document.getElementById('name').innerHTML += 'a';
}
}
</script>COPY1
2
3
4
5
6
7
8
9
10// 优化后
<script type="text/javascript">
funtcion test(){
let content = '';
for(let i = 0; i < 100; i++>){
content += 'a';
}
document.getElementById('name').innerHTML += content;
}
</script>元素的批量修改,设置
display:none
大量元素的插入,使用
DocumentFragment
对于样式频繁变化的DOM,可以使用决定定位或者相对定位脱离文档流,也可以使用
will-change
创建图层,借用GPU
进行渲染,在兼容性较差的浏览上可以使用3D变形transform: translateZ(0)
强制创建一个图层。使用classList来实现对元素样式的修改,而非style,减少触发回流重绘的频次
COPY1
2
3
4
5
6// 优化前
<script type="text/javascript">
const box = document.getElementById('box');
box.style.height = '100px';
box.style.width = '200px';
</script>COPY1
2
3
4
5// 优化后
<script type="text/javascript">
const box = document.getElementById('box');
box.classList.add('size');
</script>避免强制更新布局
一般的,在每次事件循环的末尾会进行一次DOM更新,这一周期大概为16ms,假如我们有以下操作
1 |
|
在为box新增class样式后,我们马上又读取了元素的宽度,而浏览器此时还没有完成渲染,那么浏览器为了计算宽度值,就需要重新发生回流、重绘。
那么正确的做法是先获取宽度,再添加样式:
1 | const width = box.offsetWidth; |
- 对于需要提前加载的资源,可以使用
preload
COPY1
<link rel="preload" href="/css/mystyles.css" as="style">
- 对于需要解析的非本站域名/跨域域名,可以使用
dns-prefetch
- 需要注意的是:
dns-prefetch
仅对跨域域上的 DNS 查找有效,因此请避免使用它来指向您的站点或域(避免多此一举)- COPY
1
<link rel="dns-prefetch" href="https://fonts.googleapis.com/">
- 需要注意的是:
- preconnect