聊聊浏览器的渲染机制

作者: 若邪Y | 来源:发表于2016-12-11 22:29 被阅读1237次

本文中浏览器特指Chrome浏览器

开始之前说说几个概念,以及在准备写这篇文章之前对浏览器的渲染机制的了解:

DOM:Document Object Model,浏览器将HTML解析成树形的数据结构,简称DOM。
CSSOM:CSS Object Model,浏览器将CSS代码解析成树形的数据结构
Render Tree:DOM 和 CSSOM 合并后生成 Render Tree(Render Tree 和DOM一样,以多叉树的形式保存了每个节点的css属性、节点本身属性、以及节点的孩子节点,display:none 的节点不会被加入 Render Tree,而 visibility: hidden 则会,所以,如果某个节点最开始是不显示的,设为 display:none 是更优的。)

查阅了一些关于浏览器渲染机制的文章后,得到以下比较重要或者有争议性的观点:

1.Create/Update DOM And request css/image/js:浏览器请求到HTML代码后,在生成DOM的最开始阶段(应该是 Bytes → characters 后),并行发起css、图片、js的请求,无论他们是否在HEAD里。注意:发起 js 文件的下载 request 并不需要 DOM 处理到那个 script 节点,比如:简单的正则匹配就能做到这一点,虽然实际上并不一定是通过正则:)。这是很多人在理解渲染机制的时候存在的误区。
2.Create/Update Render CSSOM:CSS文件下载完成,开始构建CSSOM
3.Create/Update Render Tree:所有CSS文件下载完成,CSSOM构建结束后,和 DOM 一起生成 Render Tree。
4.Layout:有了Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的CSS定义以及他们的从属关系。下一步操作称之为Layout,顾名思义就是计算出每个节点在屏幕中的位置。
5.Painting:Layout后,浏览器已经知道了哪些节点要显示(which nodes are visible)、每个节点的CSS属性是什么(their computed styles)、每个节点在屏幕中的位置是哪里(geometry)。就进入了最后一步:Painting,按照算出来的规则,通过显卡,把内容画到屏幕上。

出处

浏览器的主要组件为 (1.1):
1.用户界面 - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口显示的您请求的页面外,其他显示的各个部分都属于用户界面。
2.浏览器引擎 - 在用户界面和呈现引擎之间传送指令。
3.呈现引擎 - 负责显示请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
4.网络 - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
5.用户界面后端 - 用于绘制基本的窗口小部件,比如组合框和窗口。其公开了与平台无关的通用接口,而在底层使用操作系统的用户界面方法。
6.JavaScript 解释器。用于解析和执行 JavaScript 代码。
7.数据存储。这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。
值得注意的是,和大多数浏览器不同,Chrome 浏览器的每个标签页都分别对应一个呈现引擎实例。每个标签页都是一个独立的进程。

主流程
呈现引擎一开始会从网络层获取请求文档的内容,内容的大小一般限制在 8000 个块以内。
然后进行如下所示的基本流程:

可以看到,第一次解析html的时候,外部资源好像是一起请求的,最后一次Finish Loading是a.js的,因为服务端延迟的10秒钟。文章二中说资源是预解析加载的,就是说style.css和b.js是a.js造成阻塞的时候才发起的请求,图中也是可以解释得通,因为第一次Parse HTML的时候就遇到阻塞,然后预解析就去发起请求,所以看起来是一起请求的。
将index.html内容增加足够多,并且在最后面才加入script:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
    <link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<p>重复</p>
<p>重复</p>
....
....重复5000行
<script src='http://127.0.0.1:8080/b.js'></script>
<script src='http://127.0.0.1:8080/a.js'></script>
<p>3333333</p>
</body>
</html>

多刷新几次,查看TimeLine


可以发现,当html内容太多的时候,浏览器需要分段接收,解析的时候也要分段解析。还可以看到,请求资源的时机是无法确定的,但肯定不是同时请求的,也不是解析到指定标签的时候才去请求,浏览器会自行判断,如果当前操作比较耗时,就会去加载后面的资源。

HTML 是否解析一部分就显示一部分

修改 index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
    <link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<p>222222</p>
<script src='http://127.0.0.1:8080/b.js'></script>
<script src='http://127.0.0.1:8080/a.js'></script>
<p>3333333</p>
</body>
</html>

因为a.js的延迟,解析到a.js所在的script标签的时候,a.js还没有下载完成,阻塞并停止解析,之前解析的已经绘制显示出来了。当a.js下载完成并执行完之后继续后面的解析。当然,浏览器不是解析一个标签就绘制显示一次,当遇到阻塞或者比较耗时的操作的时候才会先绘制一部分解析好的。

<script>标签的位置对HTML解析有什么影响

修改index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
    <link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
    <script src='http://127.0.0.1:8080/b.js'></script>
    <script src='http://127.0.0.1:8080/a.js'></script>
</head>
<body>
<p id='hh'>1111111</p>
<p>222222</p>
<p>3333333</p>
</body>
</html>

还是因为a.js的阻塞使得解析停止,a.js下载完成之前,页面无法显示任何东西。



整个处理过程中,Parse HTML 3次,计算元素样式1次,页面布局计算1次,绘制一次。

修改index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
    <link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<p>222222</p>
<p>3333333</p>
<script src='http://127.0.0.1:8080/b.js'></script>
<script src='http://127.0.0.1:8080/a.js'></script>
</body>
</html>

解析到a.js部分的时候,页面要显示的东西已经解析完了,a.js不会影响页面的呈现速度。



整个处理过程中,Parse HTML 3次,计算元素样式2次,页面布局计算1次,绘制一次。

修改index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
    <link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<script src='http://127.0.0.1:8080/b.js'></script>
<script src='http://127.0.0.1:8080/a.js'></script>
<p>222222</p>
<p>3333333</p>
</body>
</html>

阻塞后面的解析,导致不能很快的显示。

整个处理过程中,Parse HTML 3次,计算元素样式2次,页面布局计算2次,绘制2次。
可以发现浏览器优化得非常好,当阻塞在a.js的时候,现将已经解析的部分显示(计算元素样式,布局排版,绘制),当a.js下载好后接着解析和显示后面的(因为a.js后面还有要显示到页面上的元素,所以还需要进行1次计算元素样式,布局排版,绘制)

修改index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
    <link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<p>222222</p>
<script src='http://127.0.0.1:8080/a.js'></script>
<p>3333333</p>
<script>
    document.getElementById("hh").style.height="200px";
</script>
</body>
</html>

a.js阻塞的时候,排版,绘制1次;a.js下载完后重排,重绘一次;修改DOM,引起重排,重绘一次。是不是这样呢?看下图


事实是修改DOM并没有引起重排,重绘。因为浏览器将a.js下载完成并执行后的一次重排和重绘与修改DOM本应该导致的重排和重绘积攒一批,然后做一次重排,重绘

浏览器是聪明的,它不会你每改一次样式,它就reflow或repaint一次。一般来说,浏览器会把这样的操作积攒一批,然后做一次reflow,这又叫异步reflow或增量异步reflow。但是有些情况浏览器是不会这么做的,比如:resize窗口,改变了页面默认的字体,等。对于这些操作,浏览器会马上进行reflow。

css文件的影响

服务端将style.css的相应也设置延迟。
修改index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
    <link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
</head>
<body>
<p id='hh'>1111111</p>
<p>222222</p>
<p>3333333</p>
<script src='http://127.0.0.1:8080/a.js'></script>
</body>
</html>

可以看出来,css文件不会阻塞HTML解析,但是会阻塞渲染,导致css文件未下载完成之前已经解析好html也无法先显示出来。

接着修改index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
</head>
<body>
<p id='hh'>1111111</p>
<p>222222</p>
<p>3333333</p>
<link rel="stylesheet" href="http://127.0.0.1:8080/style.css">
<script src='http://127.0.0.1:8080/a.js'></script>
</body>
</html>

同样阻塞渲染

修改index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
    <link rel="stylesheet" href="http://127.0.0.1:8080/style.css" media="print">
</head>
<body>
<p id='hh'>1111111</p>
<p>222222</p>
<p>3333333</p>
<script src='http://127.0.0.1:8080/a.js'></script>
</body>
</html>

注意media="print"



因为指定了media="print",样式不起作用,不会阻塞渲染。

<link href="style.css" rel="stylesheet">
<link href="style.css" rel="stylesheet" media="all">
<link href="portrait.css" rel="stylesheet media="orientation:portrait">
<link href="print.css" rel="stylesheet" media="print">
第一条声明阻塞渲染,匹配所有情况。
第二条声明一样阻塞渲染:"all" 是默认类型,如果你未指定任何类型,则默认为 "all"。因此,第一条声明和第二条声明实际上是一样的。
第三条声明有一条动态媒体查询,在页面加载时判断。根据页面加载时设备的方向,portrait.css 可能阻塞渲染,也可能不阻塞。
最后一条声明只适用打印,因此,页面在浏览器中首次加载时,不会阻塞渲染。

但是。。。看一下火狐的表现


图片资源的影响

修改index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="cache-control" content="no-cache,no-store, must-revalidate"/>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>浏览器渲染</title>
    <link rel="stylesheet" href="http://127.0.0.1:8080/style.css" media="print">
</head>
<body>
<p id='hh'>1111111</p>
<p>222222</p>
[站外图片上传中……(2)]
<p>3333333</p>
</body>
</html>

图片比较大,2M多,但服务端还是要延迟10秒响应。

图片既不阻塞解析,也不阻塞渲染。



图片未请求回来之前,先进行一次layout和paint,paint的范围就是页面初始的可视区域。当返回一部分图片信息后(估计是得到了图片的尺寸),再进行一次layout和paint,paint的范围受到图片尺寸的影响。当图片信息全部返回时,最后进行一次paint。
如果固定img的宽高,当返回一部分图片信息后,不会再layout,但仍会paint一次。
补充:图片用作背景(不是写在CSS文件内)是在Recalculate Style的时候才发起的请求,layout、paint次数和固定宽高的img一样。背景图属性写在CSS文件里,则CSS文件下载并执行Recalculate Style的时候才会请求图片。

参考

浏览器的渲染原理简介
浏览器的工作原理:新式网络浏览器幕后揭秘
JS 一定要放在 Body 的最底部么?聊聊浏览器的渲染机制
https://blog.chromium.org/2015/03/new-javascript-techniques-for-rapid.html
https://developers.google.cn/web/fundamentals/performance/critical-rendering-path/render-blocking-css

相关文章

  • 聊聊浏览器的渲染机制

    本文中浏览器特指Chrome浏览器 开始之前说说几个概念,以及在准备写这篇文章之前对浏览器的渲染机制的了解: DO...

  • 简述网页的渲染机制

    这次简单聊聊我对浏览器的渲染机制的理解。首先需要提到几个基本概念:DOM:Document Object Mode...

  • 略知一二之浏览器渲染原理

    浏览器渲染原理 推荐相关阅读文章 浏览器页面渲染机制,你真的弄懂了吗? 关键渲染路径 深入浅出浏览器渲染原理 [译...

  • 前端渲染机制及重绘/回流

    渲染机制 渲染步骤 浏览器的渲染机制一般分为以下几个步骤: 1. 处理 HTML 并构建 DOM 树。2. 处...

  • 浏览器原理相关视频课程

    大话浏览器渲染原理浏览器渲染原理和打开网页机制HTTPS 网络协议原理解析从零开始,彻底掌握浏览器渲染原理深入V8...

  • 网页的渲染机制

    网页的渲染机制 参考文章:ScriptJava——了解HTML页面的渲染过程浏览器加载,解析,渲染的过程HTML渲...

  • 网页的渲染机制

    网页的渲染机制 参考文章:ScriptJava——了解HTML页面的渲染过程浏览器加载,解析,渲染的过程HTML渲...

  • 重绘(redraw或repaint),重排(reflow)

    浏览器运行机制图: 浏览器的运行机制:layout:布局; 1、构建DOM树(parse):渲染引擎解析HTML文...

  • 2020-07-23 18课 CSS动画+浏览器渲染原理

    浏览器渲染原理 渲染机制 浏览器加载HTML代码后解析HTML代码来构建HTML树(DOM) 加载CSS代码后解析...

  • 网页渲染机制

    网页通过浏览器展示在我们面前,说网页的渲染机制就是说浏览器怎么渲染页面的。市面上浏览器主流浏览器chrome,fi...

网友评论

    本文标题:聊聊浏览器的渲染机制

    本文链接:https://www.haomeiwen.com/subject/yphumttx.html