前端高手必看:浏览器帧渲染全流程深度剖析

92 阅读4分钟

从输入URL回车到浏览器渲染页面过程

  1. 解析URL,判断协议
  2. 解析DNS,先判断本地是否有未过期DNS缓存,如果有则直接使用缓存结果,如果没有则请求DNS服务器获取解析
  3. TCP三次握手建立连接
    1. 客户端向服务端发送
  4. 如果是HTTPS协议,还需要经过TLS握手
  5. 浏览器发送请求,如果使用了HTTP1.1 以上的协议,还有TCP复用连接的过程
    1. HTTP协议1.0、1.1、2.0的特点区别
  6. 请求到HTML文档,开始解析文档
    1. 如果解析到CSS链接,则启用另外线程下载CSS并解析
    2. 如果解析到JS,则暂停解析HTML先解析JS
    3. 浏览器会将HTML文档解析为AST,然后生成DOM树
  7. 解析完成,生成DOM树、CSSOM树,合并得到渲染树
  8. 计算页面布局,生成布局树
  9. 再计算页面层次关系,生成层次树
  10. 然后生成绘制指令
  11. 提交到合成线程,进行分块合成
  12. 再进行光栅化
  13. 最后提交GPU线程渲染到显示器,得到最终结果

浏览器渲染渲染页面过程

image-20250826131030828

  1. 页面请求到HTML,开始解析HTML,得到 DOM 树
  2. 解析CSS,得到CSSOM树
  3. 合并 DOM 树 CSSOM 树得到渲染树
  4. 计算页面布局(得到每个容器的几何属性)生成layout布局树
  5. 计算元素堆叠关系,生成layer层次树
  6. 生成绘制指令
  7. 分块,将每个图层划分为更小的绘制区域
  8. 光栅化,将分块的结果转换为一个一个的位图
  9. 最后将位图交给GPU线程处理,GPU将其绘制到屏幕上

回流重绘

  • 重排

    • img
  • 重绘

    • img
  • 是什么?

    • 回流也叫重排,本质就是重新计算 layout 树。当进行了会影响布局树的操作后,需要重新计算布局树,会引发 layout。
    • 重绘,本质就是重新根据分层信息计算了绘制指令。当改动了可见样式后,就需要重新计算,会引发 重绘。
    • 发生回流就一定重绘
  • 发生回流的操作

    • 刷新浏览器页面
    • 容器几何属性变更
    • 增加或删除可见DOM元素
    • 浏览器窗口尺寸变更
    • 动画效果
  • 浏览器的优化策略

    • 由于每一次回流重绘都会带来额外的性能消耗,因此大多数浏览器都会通过队列的来优化回流重绘次数,浏览器将会导致回流的操作都存入队列,直到一段时间后,或者达到一定阈值时,才一次性清空队列
    • 当js中有获取元素尺寸属性的代码时,会触发清空队列,比如如下的属性
      • offsetWidthoffsetHeightoffsetTopoffsetLeft
      • scrollWidthscrollHeightscrollTopscrollLeft
      • clientXXX
      • 以上属性会强制刷新优化队列
  • 字节回流重绘代码题,触发了几次回流重绘?

    • 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 的变化几乎不会影响渲染主线程
    • img

浏览器每一帧的过程

img

每一帧开始时

  • 处理输入事件
  • 执行requestAnimationFrame
  • 浏览器渲染过程...
  • 如果当前帧还有空闲时间则执行requestIdeCallback