首页 > 其他分享 >浏览器如何渲染一个 html 文件?

浏览器如何渲染一个 html 文件?

时间:2023-02-26 00:56:23浏览次数:55  
标签:CSSOM 浏览器 DOM 标签 渲染 html css 加载

浏览器如何渲染一个 html 文件

14KB 规则,具体的看 MDN
的解释:https://developer.mozilla.org/zh-CN/docs/Web/Performance/How_browsers_work#tcp_慢启动_14kb_规则

大概就是 TCP 在响应浏览器的请求时,第一个数据包大概为 14KB 然后下一个为 28KB, 接着为 56KB,
后续的包是前一个包大小的两倍,直到达到预定的阈值,或者遇到阻塞。 可以这么理解一下,服务器端并不知道我们的宽带是多少,所以使用这样的方式来进行「试探」。
所以就可以针对这一点进行优化,比如:在首个 14KB 中包含尽可能多的渲染页面所需的内容。

参考:

  1. 构建 DOM 树与 CSSOM 树
  2. css 加载会造成阻塞吗?
  3. 构建渲染树
  4. 针对关键渲染帧优化性能

疑问:

在构建 DOM 树和 CSSOM 树章节中缺少使用 @import 引入 css 的分析;之前看到的所有文章都说 @import 会等到页面完全载入后才开始加载;我不太明白这个意思;但是我使用这个例子, link 与 @import 依然是并行加载的,而且发起请求的时间差不多是同时:

<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap-utilities.css" type="text/css"
      rel="stylesheet">
<link href="https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap.css" type="text/css"
      rel="stylesheet">
<style>
   @import url("https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap-grid.css");
   @import url("https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/5.2.3/css/bootstrap-reboot.css");
</style>
<body>
<div>div</div>*10000
</body>

构建 DOM 树

首先浏览器接受服务器返回到字节数据,然后通过转码将这些数据转换为字符串;为什么是一部分呢?因为这个过程是渐进的,如果 html
文件很大,只传输了一部分,那么就会从这一部分开始,而不是等到所有的 html 文件传输完成才开始解析。


然后会根据字符串生成对应的 tokens;tokens 的形式其实就是「开始标签」和「结束标签」,比如


<html>
<body></body>
</html>

这样的 html 结构,生成的 tokens 大致的形式为,也就标识了节点的层级结构

[startTag html]
  [startTag body]
  [endTag body]
[endTag html]

tokens 生成后就会直接开始创建 html 元素对象,然后构建 DOM 树;

但是在这里我有一个疑问,是 tokens 生成后就立马开始解析创建元素对象,还是 endTag 也生成后才开始解析的?

进行了一个测试,有一个很大的 html 文件例如:

<html>
<body>
<div>top div</div>
<script>debugger</script>
<!-- 非常多的内容,让该 html 文件很大 -->
<div>bottom div</div>
</body>
</html>

实际情况为:当开始 debug 时,top div 已经渲染在页面上了 bottom div 还没有渲染出来;

首先肯定可以确定的一个事情:

  1. 解析是按照顺序,从上至下执行的
  2. 在整个 DOM 树构建完成之前,会将已经解析的部分渲染出来 (将 DOMContentLoaded 事件作为 dom 树构建完成,document.addEventListener('DOMContentLoaded'))

问题:

  1. 如果需要 endTag 生成后才开始解析,那么 body 中间的内容呢?此时查看 network 发现只传输了 300Byte; 说明很多的内容都没有传输过来,但是
    endTag 都已经确认了,后续的内容传递过来,要怎么确定是 body 的子节点呢?还是直接插到 body 里面?
  2. 如果不是 endTag 生成后开始解析,那么 </body> </html> 结束标签, 是浏览器自己创建的吗?会不会创建错误呢?

推测:

我的倾向是第一个,需要 endTag 生成后才开始解析,后续的还未传递过来的内容,应该有某些方法进行定位.


html 变成 token,token 又变成节点,然后浏览器开始构建 DOM 树,大概的流程这样

build-dom-tree

在构造 DOM 的过程中,将会遇到各种资源:

  1. 遇到图片时,将会去异步加载这个图片,不会阻塞 html 解析和 DOM 的渲染
  2. 遇到外联 css 文件时,也会去异步的加载这个 css 文件,加载完成后也会对这个 css 文件进行解析,最后会构建 CSSOM;但是 CSSOM 与 DOM 有一些不同;详细见 CSSOM
  3. 遇到内联 css 样式时,会直接解析;其他的与外联 css 文件一致
  4. 遇到普通 script 标签时,将会直接 暂停 html 的解析和 DOM 树的渲染 先执行 js 代码,执行完成后才恢复;因为 js 代码可能会做出 修改节点内容、删除节点等操作;
  5. 遇到外联的 script 标签,还是与普通 script 一样,暂停解析和渲染,先加载 js 文件,并且等代码执行后才恢复
  6. 遇到 defer script 标签,将会异步加载 js 文件,并且在触发 DOMContentLoaded 事件之前执行,那么在下载时就不会再阻塞解析了;但是一定会阻塞渲染,因为会在 DOMContentLoaded 触发之前执行。
  7. 遇到 async script 标签,与 defer 类似,也会异步加载 js 文件,与 defer 的不同之处在于 async script 标签在加载 js 文件后,会立即执行 js 代码,同样执行的代码会阻塞渲染,但是不会阻塞 DOMContentLoaded 事件的触发;也就是说,如果在代码执行前触发 DOMContentLoaded 事件,那么是可以正常执行事件回调函数的。

!注意,不管是 defer 还是 async 标签,在添加了 type="module" 后,表现都发生了变化;可以日后研究

用于阻塞的代码:

let i = 0;
while (true) {
  i+=1;

  if(i>3000000000) break;
}

alert(123);

构建 CSSOM 树

DOM 中包含了页面的所有内容,那么 CSSOM 包含了页面的所有样式

DOM 的构造是增量的,也就是解析一部分 token 就构建一部分 DOM,但是 CSSOM 不是;因为 css 文件中下面的文件可能会覆盖上面的 css 规则

所以为了避免多次不必要的构建,浏览器在遇到 style 标签时,那么浏览器将会直接解析该 style 标签内所有 css,解析完成后再更新 CSSOM 树;遇到 link 外联的样式表时,将会等到该样式表 加载完成 -> 解析完成 后才更新 CSSOM 树。


CSSOM 的数据结构见参考

CSSOM 树的构建将会阻塞 DOM 渲染;所以 CSSOM 树不会阻塞 html 解析和 DOM 树的构建过程,但是会阻塞 DOM 的渲染过程

CSSOM 树与 DOM 树相似,但是 CSSOM 与 DOM 树是两棵树,是完全独立的。CSSOM 树的构建是非常快的

浏览器应用 CSS 样式,是从右向左进行匹配的,比如说 .link .button 就是先匹配到 .button 然后再匹配到 .link;可能有点反直觉,但是这样其实性能更好,可以直接找到 .button 设置样式; 所以这也是 css 优化的一个方向:避免多层级选择器,避免后代选择器,避免 * 选择器……

还有就是加载外联 css 将会阻塞 js 代码的执行,例如(下面的代码需要开启限速)

<head>
   <script>
      console.time();
      console.timeLog();
      document.addEventListener('DOMContentLoaded', function () {
         console.log('DOMContentLoaded');
         console.timeEnd();
      })
   </script>
   <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap/5.2.3/css/bootstrap-grid.min.css" /> 
</head>
<body>
<div id="btn" class="text-info">top div</div>
<!--<script>console.log('after link')</script>-->
<script>console.log(getComputedStyle(document.getElementById('btn')).color)</script>
<style>
   /* 这里覆盖 bootstrap 的 text-info 但是上面的 script 标签打印的结果并不是 red;因为上面的 script 标签获取的时候 CSSOM 还没有更新呢 */
   .text-info { color: red; }
</style>
<div>bottom div</div>
</body>

上面代码的执行结果是:直接打印时间和 DOMContentLoaded;

如果把 <script>console.log('after link')</script> 解除注释,那么将会等到 css 文件加载完成后才打印 DOMContentLoaded;

注意:如果 script 标签内没有一点内容,一个空格都没有,那么也会直接打印 DOMContentLoaded;script 标签内就算是有一个空格,那么也会等到 css 文件加载完成;

  这里很好理解,浏览器不知道我们在 script 标签内做了什么,比如说,我需要获取这个 text-info 的 字体颜色;`console.log(getComputedStyle(document.getElementById('btn')).color)` 这个时候应该获取到正确的颜色;但是如果连 css 都还没加载完,那如何获取正确的颜色?

结论:加载 css 文件是否阻塞 js 代码执行(包括 DOMContentLoaded),重点在于 js 代码的位置,如果 js 代码位于 link 标签后,将阻塞 js 代码执行;如果 js 代码放在 link 标签前,那么将不会阻塞;

注意:不要使用 webstorm 的 open in browser 因为 webstorm 将会启动一个服务器,并且开启热更新服务,将会导致测试结果发生改变;我就遇到了这样的问题,无论 script 放在那里都会阻塞;直接使用 浏览器 打开文件即可;

构建 render 树

等待 DOM 树和 CSSOM 树构建完成后,就可以开始构建 render 树了;

为了构建 render 树,浏览器大致做了以下事情:

  1. 从 DOM 树的根开始,遍历每个可见节点。
    • 一些节点本身是不可见的,例如 link, meta, script 标签等,所以直接省略它们
    • 还有一些节点是设置了 display: none; 这一些节点也属于不可见节点;但是设置了 opacity: 0visibility: hidden 属性的节点,依然属于可见节点;
  2. 对于每个可见节点,通过 CSSOM 匹配对应的规则,并应用。
  3. 生成所有可见节点,及其内容,及其计算样式的渲染树

所以如果将元素的 display 属性修改为 none 那么一定会引起回流,因为将会重新构建 render 树。

布局(回流,重排)

我们有了渲染树后,知道了所有要渲染出来的东西,那么我们就要开始计算这些东西对应的位置;

这个过程就是布局;

其实这个过程没有什么好说的,大概就是遍历整个渲染树;计算节点应在的位置,精确到像素;

所以如果进行优化要减少回流次数的话

  1. 会尽量减少引起重新构建 render 树的操作,比如删除元素,修改元素 display 属性为 none
  2. 要减少可能会影响到布局的操作,比如说元素的宽度高度的改变,字体大小的改变,改变元素位置,改变元素内容,改变窗口大小等
  3. 某些场景下可以使用 absolute, fixed 定位脱离文档流,这样即使重排,造成的影响也比较小;
  4. 如果一定会进行造成重排的操作,那么可以将这样的操作集合一起。

注意,如果 <img /> 元素一开始没有指定 width height 属性,那么元素一开始就不会占据位置,将会等到图片加载时,才拥有宽度高度,此时将挤开周围的元素也就引起了「回流」;也会影响用户的体验,属于 cls 问题。

具体的性能优化方案可以看 - 参考第四点。

绘制(重绘)

确定了元素的位置,那么就直接渲染出来了。

因为这一步是在布局的后面,所以重排必定重绘,重绘不一定重排就是这里来的;

重绘:就是浏览器重新将内容绘制一遍,比如,我改变了字体的颜色,那么浏览器就只会重新绘制这一块内容;所以重绘其实并不是很影响性能,所以想要聚焦在重绘这一块提升性能是不够明显的。

具体的性能优化方案可以看 - 参考第四点。

标签:CSSOM,浏览器,DOM,标签,渲染,html,css,加载
From: https://www.cnblogs.com/99xx/p/how-to-render-html.html

相关文章

  • 条件渲染
                   ......
  • 猫抓 浏览器插件安装教程,适用Chrome浏览器和Edge浏览器
    软件截图软件介绍猫抓是一款网页媒体嗅探工具类插件,可以在任意网页中嗅探获取视频链接等数据,可以一键获取需要的链接并自动保存。使用起来十分方便,打开需要下载文件的网......
  • HTML
    <!doctypehtml><html> <read> <title>字体排版</title> </read> <body> <div> <h1>给设计师:字体排版才是网页的最基础</h1> <imgsrc="https://nim-nosdn.neteas......
  • HTML
    HTML笔记大纲1基本介绍HTML元素1.级别容器级<div></div>内部可存放任意内容文本级<p></p>只能存放文字或类似文字的内容2.元素对空白不敏感(即可通......
  • HTML5的WebSocket使用
    index.html(客户端)1<!DOCTYPEhtml>2<htmllang="en">3<head>4<metacharset="UTF-8"/>5<metaname="viewport"content="width=device-width......
  • 检索业务:基本数据渲染和排错
     采用标签显示商品的数据<divclass="rig_tab"><divth:each="product:${result.getProducts()}"><divclass="ico"......
  • 实时渲染路径追踪概述
    大家好,本文是对Real-TimePathTracingandBeyond和它的视频:HPG2022Monday,Day1(enhanced)学习的总结,并结合我的理解进行了一些延伸得益于下面的技术进步,使得通常应......
  • 书签项目可以跳转ChatGPT,浏览器新必应展示。
    等了一个星期,终于可以访问新必应了,话不多说看截图:然后这篇博客的项目也是接入了ChatGPT,国内可以正常访问博文地址喜欢的可以去下载看看,当然只是跳转ChatGPT,git开源的......
  • 【实用技巧】【一】浏览器中的请求快速导入Postman、Apifox、Jmeter
    1 前言实用小技巧哈,调试接口的时候怎么快速把浏览器中的请求快速复制到Postman或者Apifox中呢。前提:会浏览器右键检查的吧....这个你不知道的话过分了奥2 操作展示......
  • HTML个人简介
    <html><head><title>个人简介</title></head><body> <h1>个人简历</h1> <imgsrc="https://mailh.qiye.163.com/js6/s?_host=mailh.qiye.163.com&func=mbox%3AgetMe......