浏览器渲染页面的主体流程

当我们在浏览器输入网址后;浏览器都会做哪些事情才会让我们看见页面呢?

  • 首先在客户端浏览器输入网址:www.baidu.com
  • 客户端浏览器向服务器发送请求 HTTP REQUEST(省略很多细节步骤)
  • 服务器端存储着百度官网项目的原代码
  • 服务器收到请求后:服务器端把指定文中的代码返回给客户端 HTTP RESPONSEday30浏览器的渲染机制 - 图1day30浏览器的渲染机制 - 图2

    浏览器渲染页面的步骤

  • 根据HTML,生成DOM树

  • 根据CSS,生成CSSOM树
  • DOM树+CSSOM树=》RENDER-TREE(渲染树)
  • 按照RENDER-TREE在设备的视口中进行结构和位置的相关计算:布局或重排/回流
  • 根据渲染树以及回流得到的几何信息,得到节点的绝对像素:绘制或栅格化

【DOM树】Bytes=>字节流 Tokens=>令牌
image.png
【CSSOM树】
image.png
【Render-Tree渲染树】
image.png
总结步骤:

  • 处理 HTML 标记,构建 DOM 树
  • 处理 CSS 标记,构建 CSSOM 树
  • 将 DOM 树和 CSSOM 树融合成渲染树
  • 根据生成的渲染树,计算它们在设备视口(viewport)内的确切位置和大小,这个计算的阶段就是回流 => 布局(Layout)或 重排(reflow)
  • 根据渲染树以及回流得到的几何信息,得到节点的绝对像素 => 绘制(painting)

image.png
优化方案:

  • 标签语义化和避免深层次嵌套
  • CSS选择器渲染是从右到左
  • 尽早尽快地把CSS下载到客户端(充分利用HTTP多请求并发机制)
    • style
    • link
    • @import
    • 放到顶部
  • 避免阻塞的JS加载

    • async
    • defer
    • 放到底部
      image.png

      Webkit浏览器预测解析:chrome的预加载扫描器html-preload-scanner通过扫描节点中的 “src” , “link”等属性,找到外部连接资源后进行预加载,避免了资源加载的等待时间,同样实现了提前加载以及加载和执行分离。

  • 减少DOM的回流和重绘
    image.png
    image.png
    image.png
    image.png

    重绘

    元素样式的改变,但宽高、大小、位置不变,如字体颜色,背景颜色等,这种不影响元素在页面上的位置和大小的样式,会触发页面的重绘

    回流【重排】

    元素的宽高、大小、位置等影响页面布局改变的样式改变,会触发页面的回流。

    回流比重绘更消耗性能。 触发回流一定会触发重绘,触发重绘不一定能触发回流

如何减少回流和重绘

在老版本的浏览器中,我们分别改变了三次样式(都涉及了位置或者大小的改变),会触发三次回流和重绘。
现代浏览器中默认增加了“渲染队列的机制”,以此来减少DOM的回流和重绘。遇到一行修改样式的代码,先放到渲染队列中,继续看下面一行代码是否还为修改样式的,如果是继续增加到渲染队列中…直到下面的代码不再是修改样式的,而是获取样式的代码!此时不再向渲染队列中增加,把之前渲染队列中要修改的样式一次性渲染到页面中,引发一次DOM的回流和重绘。

  1. // 老版本三次回流重绘
  2. // 新版本一次回流重绘
  3. box.style.width = '200px';
  4. box.style.height = '200px';
  5. box.style.margin = '20px';
  6. console.log(box.style.width);
  7. console.log(box.offsetHeight);

1.读写分离 尽量把所有改变样式的代码都放一块

  1. box.style.width = '200px';
  2. console.log(box.style.width); //=>中断渲染队列,立即渲染一次,引发一次DOM回流和重绘 200px
  3. box.style.height = '200px';
  4. console.log(box.offsetHeight);
  5. box.style.margin = '20px';
  6. // 上面会触发三次
  7. box.style.width = '200px';
  8. box.style.height = '200px';
  9. box.style.margin = '20px';
  10. console.log(box.style.width);
  11. console.log(box.offsetHeight);

2.读写分离-集中改变样式的两种方式

  1. box.className = 'active';
  2. box.style.cssText = 'width:200px;height:200px;'

3.在动态操作DOM结构中的优化(例如:数据绑定)

  • 减少循环中对元素和样式的操作,利用文档碎片统一在循环结束后改变一次元素结构
    1. /* for (let i = 1; i <= 5; i++) {
    2. let liBox = document.createElement('li');
    3. liBox.innerText = `我是第${i}个LI`;
    4. item.appendChild(liBox);
    5. //=>每一次向页面中增加,都会触发一次DOM的回流和重绘(5次)
    6. } */
    7. // 文档碎片:临时创建的一个存放文档的容器,我们可以把新创建的LI,存放到容器中,当所有的LI都存储完,我们统一把容器中的内容增加到页面中(只触发一次回流)
    8. let frag = document.createDocumentFragment();
    9. for (let i = 1; i <= 5; i++) {
    10. let liBox = document.createElement('li');
    11. liBox.innerText = `我是第${i}个LI`;
    12. frag.appendChild(liBox);
    13. }
    14. item.appendChild(frag);
    15. // 真实项目中,有一个文档碎片类似的方式,也是把要创建的LI事先存储好,最后统一放到页面中渲染(字符串拼接)
    16. let str = ``;
    17. for (let i = 1; i <= 5; i++) {
    18. str += `<li>我是第${i}个LI</li>`;
    19. }
    20. item.innerHTML = str;