从输入URL回车到浏览器渲染页面过程
- 解析URL,判断协议
- 解析DNS,先判断本地是否有未过期DNS缓存,如果有则直接使用缓存结果,如果没有则请求DNS服务器获取解析
- TCP三次握手建立连接
- 客户端向服务端发送
- 如果是HTTPS协议,还需要经过TLS握手
- 浏览器发送请求,如果使用了HTTP1.1 以上的协议,还有TCP复用连接的过程
- HTTP协议1.0、1.1、2.0的特点区别
- 请求到HTML文档,开始解析文档
- 如果解析到CSS链接,则启用另外线程下载CSS并解析
- 如果解析到JS,则暂停解析HTML先解析JS
- 浏览器会将HTML文档解析为AST,然后生成DOM树
- 解析完成,生成DOM树、CSSOM树,合并得到渲染树
- 计算页面布局,生成布局树
- 再计算页面层次关系,生成层次树
- 然后生成绘制指令
- 提交到合成线程,进行分块合成
- 再进行光栅化
- 最后提交GPU线程渲染到显示器,得到最终结果
浏览器渲染渲染页面过程
- 页面请求到HTML,开始解析HTML,得到 DOM 树
- 解析CSS,得到CSSOM树
- 合并 DOM 树 CSSOM 树得到渲染树
- 计算页面布局(得到每个容器的几何属性)生成layout布局树
- 计算元素堆叠关系,生成layer层次树
- 生成绘制指令
- 分块,将每个图层划分为更小的绘制区域
- 光栅化,将分块的结果转换为一个一个的位图
- 最后将位图交给GPU线程处理,GPU将其绘制到屏幕上
回流重绘
-
重排
-
重绘
-
是什么?
- 回流也叫重排,本质就是重新计算 layout 树。当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout。
- 重绘,本质就是重新根据分层信息计算了绘制指令。当改动了可见样式后,就需要重新计算,会引发 重绘。
- 发生回流就一定重绘
-
发生回流的操作
- 刷新浏览器页面
- 容器几何属性变更
- 增加或删除可见DOM元素
- 浏览器窗口尺寸变更
- 动画效果
-
浏览器的优化策略
- 由于每一次回流重绘都会带来额外的性能消耗,因此大多数浏览器都会通过队列的来优化回流重绘次数,浏览器将会导致回流的操作都存入队列,直到一段时间后,或者达到一定阈值时,才一次性清空队列
- 当js中有获取元素尺寸属性的代码时,会触发清空队列,比如如下的属性
offsetWidth、offsetHeight、offsetTop、offsetLeftscrollWidth、scrollHeight、scrollTop、scrollLeftclientXXX- 以上属性会强制刷新优化队列
-
字节回流重绘代码题,触发了几次回流重绘?
-
let el = document.getElementById('app'); el.style.width = (el.offsetWidth+1) + 'px'; el. style.width = 1 + 'px" -
执行
el.offsetWidth时强制刷新优化队列,但是队列中还没有回流代码 -
el.style.width=xxx这两行代码涉及回流操作,存入队列 -
代码执行完毕,清空优化队列,所以最后只执行一次回流
-
-
如何减少回流重绘
-
将要发生回流操作的DOM先从文档流中剔除,待所有操作执行完毕后再添加回文档流
-
const container = document.querySelector(".container"); container.style.display = "none"; for (let i = 0; i < 200; i++) { const child = document.createElement("div"); child.innerHTML = i; container.appendChild(child); } container.style.display = "block"; -
使用文档碎片
-
const container = document.querySelector(".container"); const frag = document.createDocumentFragment(); for (let i = 0; i < 200; i++) { const child = document.createElement("div"); child.innerHTML = i; frag.appendChild(child); } container.appendChild(frag); -
使用克隆节点
-
const container = document.querySelector(".container"); const containerClone = container.cloneNode(true); for (let i = 0; i < 200; i++) { const child = document.createElement("div"); child.innerHTML = i; containerClone.appendChild(child); } container.parentNode.replaceChild(containerClone, container);
-
-
为什么transform效率高
- 因为
transform既不会影响布局也不会影响绘制指令,它影响的只是渲染流程的最后一个「绘制」阶段,由于 绘制 阶段在合成线程中,所以transform的变化几乎不会影响渲染主线程
- 因为
浏览器每一帧的过程
每一帧开始时
- 处理输入事件
- 执行
requestAnimationFrame - 浏览器渲染过程...
- 如果当前帧还有空闲时间则执行
requestIdeCallback