Webkit底层原理(5)--CSS解释器和样式布局

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:https://blog.csdn.net/caomage/article/details/102217809

从整个网页的加载和渲染过程来看,CSS解释器和规则匹配处于DOM树建立之后,RenderObject树(下一篇文章会介绍)建立之前,CSS解释器解释后的结果会保存起来,然后RenderObject树基于该结果进行规则匹配和布局计算。当网页有用户交互或者动画等动作的时候,通过CSSOM技术,JavaScript代码同样可以很方便的修改CSS样式,Webkit此时需要重新解释样式并重复以上这一过程。

一、CSSOM(CSS Object Model)

通常我们的CSS代码都是静态的,那么CSS有没有提供一些方法可以让开发者写一些脚本去操作它呢?这就是CSSOM,成为CSS对象模型。它的思想是在DOM中的一些节点接口中,加入获取和操作CSS属性或者接口的JavaScript接口。因而JavaScript可以动态操作CSS样式。

对于内部和外部样式表,CSSOM定义了样式表的接口,称为CSSStyleSheet,这是一个可以在JavaScript代码中访问的接口。借助于该接口,开发者可以在JavaScript中获取样式表的各种信息,例如CSS的href、样式表类型type、规则信息cssRules等,甚至可以获取样式表中的CSS规则列表。这个接口同DOM中的Script或者Link节点不一样,它是CSSOM定义的心接口。开发者可以通过document.stylesheets查看当前网页中包含的所有CSS样式表,这是因为CSSOM对DOM中的Document接口进行了扩展。

W3C还定义了另一个规范,那就是CSSOM View,它的基本含义是增加一些新的属性到Window、Document、Element、HTMLElement和MouseEvent等接口,用于表示跟视图相关的特征,例如窗口大小、网页滚动位置、元素的位置、鼠标事件的坐标等信息。

二、CSS解释器和规则匹配

接下来看一下Webkit如何解释CSS代码并选择相应的规则。

1. 解释过程

CSS解释器是指从CSS字符串经过CSS解释器处理后变成渲染引擎的内部规则表示的过程。这一过程并不复杂,基本的思想是由CSSParser类负责。当Webkit需要解释CSS内容的时候,调用CSSParser来负责,最后Webkit将创建好的结构设置到StyleSheetContents对象中。

在解释网页中自定义的CSS样式之前,实际上Webkit渲染引擎会为每个网页设置一个默认的样式,这决定了网页所没有设置的元素属性及其属性默认值和将要显示的效果。一般来讲,不同的Webkit移植可以设置不同的默认样式。

2. 样式规则匹配

样式规则建立完成之后,Webkit保存规则结构在DocumentRuleSets对象中。当DOM的节点建立之后,Webkit会为其中的一些节点(可视节点)选择合适的样式信息。这些工作都是由StyleResolver负责。

基本的思路是使用StyleResolver来为DOM的元素节点匹配样式。StyleResolver类根据元素的信息,例如Tag Name、Class等,从样式规则中查找最匹配的规则,然后将样式信息保存到新建的RenderStyle中。最后这些RenderStyle对象被RenderObject使用。

样式的匹配则是由ElementRuleCollector来计算并获得,它根据元素的属性等信息,从之前的DocumentRuleSets中获取规则集合,依次按照ID、Class、Tag等选择器信息逐次匹配获得元素的样式。具体的过程是:

  1. 当Webkit需要为HTML元素创建RenderObject的时候,首先StyleResolver负责获取样式信息,并返回RenderStyle对象,RenderStyle对象包含了匹配完的结果样式信息;
  2. 根据实际需求,每个元素可能需要匹配不同来源的规则,依次是浏览器规则集合、用户规则集合和HTML网页中包含的自定义规则集合。这三个规则的匹配方式是类似的,这里以自定义规则匹配为例;
  3. 对于自定义规则集合,它先查找ID规则,检测有无匹配的规则,之后依次检测类型规则、标签规则等。如果某个规则匹配上该元素,Webkit把这些规则保存到匹配结果中;
  4. 最后,Webkit对这些规则进行排序。对于该元素需要的样式属性,Webkit选择从高优先级规则中选取,并将样式属性返回。

3. JavaScript设置样式

CSSOM定义了JavaScript访问样式的能力和方式。在Webkit中,这需要JavaScript引擎和渲染引擎共同来完成。之后的文章会详细介绍JavaScript引擎。

大致的过程是,JavaScript引擎调用设置属性值的公共处理函数,然后该函数调用属性值解析函数。而后Webkit将解析后的信息设置到元素的style属性的样式webkitTransform中,然后设置标记表明该元素需要重新计算样式,并触发重新布局。最后就是Webkit的重新绘制。

三、Webkit布局

1. 基础

当Webkit创建RenderObject对象之后,每个对象是不知道自己的位置、大小等信息的,Webkit根据盒模型来计算它们的位置大小信息的过程称为布局计算(排版)。

布局计算根据其计算的范围大致可以分为两类:

  1. 对整个RenderObject树进行的计算;
  2. 对RenderObject树中某个子树的计算,常见的是文本元素或者overflow:auto;,这种情况一般是其子树布局的改变不会影响其周围元素的布局,因而不需要重新计算更大范围内的布局。

2. 布局计算

布局计算是一个递归的过程,因为一个节点的大小通常需要先计算它的子女节点的位置、大小等信息。RenderObject节点计算布局的主要逻辑都是由RenderObject的layout函数来完成,大致过程如图:

  1. layout函数会判断RenderObject节点是否需要重新计算,通常这需要通过检查数组中相应的标记位、子女是否需要计算布局来确定;
  2. layout函数会确定网页的宽度和垂直方向上的外边距,这是因为网页通常是在垂直方向滚动,水平方向尽量不需要滚动;
  3. layout函数会遍历其每一个子女节点,依次计算它们的布局。每一个元素会实现自己的layout函数,根据特定的算法来计算该类型元素的布局。如果页面元素定义了自己的宽高,Webkit按照定义的宽高来确定元素的大小,而对于文本节点这种内联元素则需要结合其字号大小以及文字数量来确定其对应的宽高。如果页面元素所确定的宽高超过了布局容器所能提供的宽高,同时overflow:visible或者overflow:visible,Webkit会提供滚动条来保证可以显示所有内容,一般来说页面元素的宽高是在布局的时候通过相关计算得出来的。如果元素由子女,则Webkit需要递归这一过程;
  4. 节点根据它的子女们的大小计算得出自己的高度,整个过程结束。

哪些情况下需要重新计算布局呢?总体来讲,只要样式发生变化,Webkit都需要重新计算,有以下一些情况:

  1. 当网页首次被打开的时候,浏览器设置网页的可视区域(viewport),并调用计算布局的方法。就是说当可视区域发生变化的时候,Webkit需要重新计算布局;
  2. 网页的动画会触发布局计算。当网页显示结束之后,动画可能改变样式属性,Webkit就需要重新计算;
  3. JavaScript通过CSSOM直接修改样式信息,也会触发Webkit重新计算布局;
  4. 用户的交互也会触发布局计算,例如滚动网页。

CSS的布局计算是以包含块和盒模型为基础的,这表示这些元素的布局计算都依赖于块。但是,CSS标准也规定了行内元素,它们和块元素显示不太一样的是它们不会独占一行,而是在行内显示。为此,Webkit的处理方式是–对于一个块元素对应的RenderObject对象,它的子女要么都是块元素的RenderObject对象,要么都是非行内元素的RenderObject,这可以通过建立匿名块(Anonymous Blok)对象来实现,下一篇文章会介绍。

此外,布局计算是比较耗时间的,更糟糕的是,一旦布局发生变化,Webkit可能需要后面的重新绘制操作。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员成长指北

爱奇艺PC Web NodeJS中间层实践

爱奇艺作为中国最大的互联网视频综合门户,一直致力于给用户提供更好的使用体验及观影品质。PC主站作为爱奇艺的门户,日均覆盖用户达千万级别。随着公司业务...

15120
来自专栏数据森麟

用Python来看《我不是药神》到底神在哪?

简介:互联网公司运维技术负责人,拥有 10 年的互联网开发和运维经验。一直致力于运维工具的开发和运维专家服务的推进,赋能开发,提高效能。最后给自己代个盐,欢迎大...

8220
来自专栏编程微刊

jQuery动态生成input填写时间值并且提交给后端

今天写的一个demo,关于jQuery动态生成input填写时间值并且提交给后端。

11630
来自专栏彭湖湾的编程世界

【JavaScript】吃饱了撑的系列之JavaScript模拟多线程并发

最近,明学是一个火热的话题,而我,却也想当那么一回明学家,那就是,把JavaScript和多线程并发这两个八竿子打不找的东西,给硬凑了起来,还写了一个并发库co...

8910
来自专栏前端自习课

【NPM】361- 10个 NPM 使用技巧

注: 如果你需要关于初学npm的参考,可以参阅我们的初学者指南。如果你对 npm 和 Yarn 之间的差异感到困扰,可以参阅我们发表的文章:Yarn vs np...

8720
来自专栏salesforce零基础学习

Salesforce LWC学习(三) import & export / api & track

我们使用vs code创建lwc 时,文件会默认生成包含 template作为头的html文件,包含了 import LightningElement的 js...

12620
来自专栏salesforce零基础学习

Salesforce LWC学习(六) @salesforce & lightning/ui*Api Reference

上一篇中我们在demo中使用了很多的 @salesforce 以及 lightning/ui*Api的方法,但是很多没有细节的展开。其实LWC中针对这些modu...

9730
来自专栏Web行业观察

能否让JS作为打开网页的入口?

意思是,让JavaScript文件作为一个合法的网页入口(而不是html文件)。话题地址在这:

13230
来自专栏老沙课堂

浅入深出Copy和mutableCopy

由Tagged Pointed 可以知道a b 为Tagged Pointer 对象 想深入了解的的可以看一下我的上一篇文章

8220
来自专栏程序猿讲故事

多War项目中静态文件的共享方案

在互联网产品中,一般会有多个项目(Jar、WAR)组成一个产品线。这些WAR项目,因为使用相同的前端架构(jQuery、easyui等),在各个项目中都会存在这...

8030

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励