渲染是指什么?
渲染 (render),是指将HTML
代码转换为像素信息的过程。
当用户在浏览器上输入url之后,访问的服务器返回html文件,本质上是html代码,是字符串。渲染这个过程的任务就是:识别这段字符串,并且转换为像素信息。
渲染时间点
用户打开网页的过程可以简单概括为:
-
网络:拿HTML。
这里概括为拿HTML,是因为在HTML文件中可以通过
<style>
标签和<script>
标签引入 CSS 和 JS 文件。事实上网络的过程也很复杂, 但是不是这篇笔记的重点讨论内容。
-
渲染:解析HTML代码并最终转换为像素信息。
浏览器有很多进程,其中有网络进程,而网络进程又包含网络线程。
网络线程完成网络请求任务之后,拿到了一个html
文件,但是它没有解析的能力,于是将html
文件包装成一个任务,通过消息队列,转交给渲染主线程。
渲染主线程拿到渲染任务之后,就开始了渲染流程,就是本篇笔记的重点内容。
渲染流程
解析HTML - Parse HTML
DOM树(Document Object Model):页面中的元素和文本,以树形结构相关联。
在 JS 代码中,通过document
对象可以访问和修改DOM树。而上图中的DOM树指的是浏览器底层由C++
生成的DOM树。
这一个转换步骤是为后续步骤做准备的,因为字符串难处理,而对象结构容易处理。
CSS也会被解析成CSSOM(CSS Object Model),也是树形结构,根节点(StyleSheetList)是网页中所有的样式表,二级子节点可能包含内部样式表、外部样式表、内联样式表和浏览器默认样式表(取决于代码中是否有这些内容),如果有两个<link>
,则会出现两个外部样式表节点。
可以在github上的chromium源码找到浏览器默认样式表的内容。
除了浏览器默认样式表,内部样式表、外部样式表、内联样式表都可以通过 JS 访问到。
- 内部样式表和外部样式表:使用
document.styleSheets
可以访问到一个数组,元素是样式表对象。
- 使用
document.styleSheets[0].addRule("div", "border: 1px solid red important")
可以让页面上的所有div标签的边框变成红色,这种做法与传统的“获取所有div标签,再设置其style”的做法不同。- 内联样式表:使用
dom.style
访问
HTML解析过程遇到CSS怎么办?
为了提高解析效率,浏览器会启动一个预解析器率先下载和解析CSS。
渲染主线程在解析HTML的时候,会关注每一个标签;而预解析线程只关注外部样式表的标签<link>
,尽快地完成CSS的下载与解析。
这样做的目的是防止CSS的解析阻塞了HTML的解析。
HTML解析过程遇到JS怎么办?
渲染主线程遇到 JS 的script标签时必须暂停一切行为,等待下载 JS 文件,并且启用V8引擎解析执行 JS 代码,然后才能继续解析 HTML。
原因:JS 代码可能修改 DOM 树。
预解析线程可以分担一点下载 JS 的任务。
样式计算 - Recalculate Style
样式计算过程计算每一个DOM节点的最终样式(Computed Style)。
计算样式如何查看:在浏览器上打开开发者工具,查看“计算样式”,并选择“全部显示”。
通过上一过程,得到的 DOM 树和 CSSOM 树。通过遍历 DOM 树,为每一个 DOM 节点,计算它的所有 CSS 属性。
属性值的计算过程,分为如下4个步骤:
- 确定声明值;
- 层叠冲突;
- 使用继承;
- 使用默认值。
确定声明值
如果先不考虑冲突的话,那么通过 页面作者书写的CSS样式 和 用户代理样式表(浏览器内置的样式表) 的声明值相加得到全部的声明值,并且将部分值进行转换。
例如,将color: red;
转换为color: rgb(255, 0, 0);
,将font-size: 2em;
转换为font-size: 14px;
。
层叠冲突
在确定声明值时,可能出现一种情况,那就是声明的样式规则发生了冲突。
此时会进入解决层叠冲突的流程。而这一步又可以细分为下面这三个步骤:
- 比较源的重要性
- 比较优先级
- 比较次序
比较源的重要性
样式有三种来源:
- 浏览器会有一个基本的样式表来给任何网页设置默认样式。这些样式统称用户代理样式。
- 网页的作者可以定义文档的样式,这是最常见的样式表,称之为页面作者样式。
- 浏览器的用户,可以使用自定义样式表定制使用体验,称之为用户样式。
对应的重要性顺序依次为:页面作者样式 > 用户样式 > 用户代理样式。
可以在 MDN 中找到更详细的说明:CSS 层叠 - CSS:层叠样式表 | MDN (mozilla.org)
比较优先级
如果在同一源中出现了样式声明冲突,则比较其优先级。
简单来说就是:ID选择器 > 类名选择器 > 标签选择器。
更详细的说明可以查阅 MDN 的文章:优先级 - CSS:层叠样式表 | MDN (mozilla.org)
比较次序
如果出现同源同权重的情况,则比较样式的声明次序。
后声明的样式会覆盖先声明的样式。
p{
/* 会被覆盖 */
color: red;
}
p{
/* 生效 */
color: green;
}
显然,不存在次序相同的情况。至此,样式声明中存在冲突的所有情况都解决了。
使用继承
上文提到了,对于每一个 DOM 节点,都会去计算它的所有 CSS 属性。
层叠冲突这一步骤完成之后,声明值已全部确定。
而对于未声明的属性,并不是直接使用默认值,而是使用继承值。
例如:
<div>
<p>hello world</p>
</div>
div{
color: red;
}
这里<p>
标签会继承来自<div>
的color: red
样式。
继承原则:
-
继承谁的?答:就近原则,谁近就继承谁的,与权重无关。
-
哪些属性能够继承?答:大部分字体相关的属性都是可继承的,可以在MDN上查找属性是否可继承。
使用默认值
如果经过上述过程仍不能确定属性值,则使用默认值。
布局 - Layout
根据 DOM 树里每个节点的样式,计算出每个节点的尺寸和位置。
有一些数值,例如:百分比,或者
auto
,在上一步骤无法算出来,在布局这个过程才能算出来。
对于一个元素来说,它的尺寸和位置经常与它的包含块(containing block)有关。
这里简单地记录包含块的知识,更详细的说明可以查阅 MDN 文档:
版权声明:除特别声明外,本站所有文章皆是本站原创,转载请以超链接形式注明出处!