浏览器渲染页面的主体流程
当我们在浏览器输入网址后;浏览器都会做哪些事情才会让我们看见页面呢?
- 首先在客户端浏览器输入网址:www.baidu.com
- 客户端浏览器向服务器发送请求 HTTP REQUEST(省略很多细节步骤)
- 服务器端存储着百度官网项目的原代码
服务器收到请求后:服务器端把指定文中的代码返回给客户端 HTTP RESPONSE
浏览器渲染页面的步骤
根据HTML,生成DOM树
- 根据CSS,生成CSSOM树
- DOM树+CSSOM树=》RENDER-TREE(渲染树)
- 按照RENDER-TREE在设备的视口中进行结构和位置的相关计算:布局或重排/回流
- 根据渲染树以及回流得到的几何信息,得到节点的绝对像素:绘制或栅格化
【DOM树】Bytes=>字节流 Tokens=>令牌
【CSSOM树】
【Render-Tree渲染树】
总结步骤:
- 处理 HTML 标记,构建 DOM 树
- 处理 CSS 标记,构建 CSSOM 树
- 将 DOM 树和 CSSOM 树融合成渲染树
- 根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流 => 布局(Layout)或 重排(reflow)
- 根据渲染树以及回流得到的几何信息,得到节点的绝对像素 => 绘制(painting)
优化方案:
- 标签语义化和避免深层次嵌套
- CSS选择器渲染是从右到左
- 尽早尽快地把CSS下载到客户端(充分利用HTTP多请求并发机制)
style
link
@import
- 放到顶部
避免阻塞的JS加载
async
defer
- 放到底部
Webkit浏览器预测解析:chrome的预加载扫描器html-preload-scanner通过扫描节点中的 “src” , “link”等属性,找到外部连接资源后进行预加载,避免了资源加载的等待时间,同样实现了提前加载以及加载和执行分离。
-
重绘
元素样式的改变,但宽高、大小、位置不变,如字体颜色,背景颜色等,这种不影响元素在页面上的位置和大小的样式,会触发页面的重绘
回流【重排】
元素的宽高、大小、位置等影响页面布局改变的样式改变,会触发页面的回流。
回流比重绘更消耗性能。 触发回流一定会触发重绘,触发重绘不一定能触发回流
如何减少回流和重绘
在老版本的浏览器中,我们分别改变了三次样式(都涉及了位置或者大小的改变),会触发三次回流和重绘。
现代浏览器中默认增加了“渲染队列的机制”,以此来减少DOM的回流和重绘。遇到一行修改样式的代码,先放到渲染队列中,继续看下面一行代码是否还为修改样式的,如果是继续增加到渲染队列中…直到下面的代码不再是修改样式的,而是获取样式的代码!此时不再向渲染队列中增加,把之前渲染队列中要修改的样式一次性渲染到页面中,引发一次DOM的回流和重绘。
// 老版本三次回流重绘
// 新版本一次回流重绘
box.style.width = '200px';
box.style.height = '200px';
box.style.margin = '20px';
console.log(box.style.width);
console.log(box.offsetHeight);
1.读写分离 尽量把所有改变样式的代码都放一块
box.style.width = '200px';
console.log(box.style.width); //=>中断渲染队列,立即渲染一次,引发一次DOM回流和重绘 200px
box.style.height = '200px';
console.log(box.offsetHeight);
box.style.margin = '20px';
// 上面会触发三次
box.style.width = '200px';
box.style.height = '200px';
box.style.margin = '20px';
console.log(box.style.width);
console.log(box.offsetHeight);
2.读写分离-集中改变样式的两种方式
box.className = 'active';
box.style.cssText = 'width:200px;height:200px;'
3.在动态操作DOM结构中的优化(例如:数据绑定)
- 减少循环中对元素和样式的操作,利用文档碎片统一在循环结束后改变一次元素结构
/* for (let i = 1; i <= 5; i++) {
let liBox = document.createElement('li');
liBox.innerText = `我是第${i}个LI`;
item.appendChild(liBox);
//=>每一次向页面中增加,都会触发一次DOM的回流和重绘(5次)
} */
// 文档碎片:临时创建的一个存放文档的容器,我们可以把新创建的LI,存放到容器中,当所有的LI都存储完,我们统一把容器中的内容增加到页面中(只触发一次回流)
let frag = document.createDocumentFragment();
for (let i = 1; i <= 5; i++) {
let liBox = document.createElement('li');
liBox.innerText = `我是第${i}个LI`;
frag.appendChild(liBox);
}
item.appendChild(frag);
// 真实项目中,有一个文档碎片类似的方式,也是把要创建的LI事先存储好,最后统一放到页面中渲染(字符串拼接)
let str = ``;
for (let i = 1; i <= 5; i++) {
str += `<li>我是第${i}个LI</li>`;
}
item.innerHTML = str;