CSS解释器和样式布局
CSS基本功能
简介
CSS全称Cascading Style Sheet,级联表,主要用来控制网页的显示风格,有两个重要特征。
-
- 将网页内容和内容展示方式分离,提高开发效率。
- 不仅能提供对页面任意元素的精准控制,同时还能提供丰富多彩的样式。
样式来源有三种类型:
-
- 网页开发者编写的样式信息
- 网页的读者设置的样式信息
- 浏览器的内在样式
以上三种类型的优先级是递减的。
样式规则
样式规则是css规范中最基本的组成。如图6-1描述了一个典型的css规则结构。
一个规则包含:
-
- 规则头
-
-
- 由一个或多个选择器组成
-
-
- 规则体
-
-
- 由一个或多个样式声明组成
- 每个样式声明由样式名和样式值组成。
-
选择器
css的选择器是一组模式,用以匹配相应的HTML元素。主要的选择器如下:
-
- 标签选择器:"div"
- class选择器:".class"
- ID选择器:"#id"
- 属性选择器:"div[id='adiv']"
- 后代选择器:"body div"
- 子女选择器:"body>div"
- 相邻同胞选择器:"p+div"
- 还有很多,伪类选择器、通用选择器、群组选择器、根选择器等。
一般而言,选择器描述得越具体,优先级越高。即,选择器指向越准确,优先级越高。如,如果用1表示标签选择器的优先级,那么class选择器是10,ID选择器是100,数值越大表示优先级越高。
标准中引入两个新的JavaScript接口QuerySelector和QuerySelectorAll。css定义的所有选择器都可以作为参数传给这两个接口,从而获取对应的DOM节点。
框模型
框模型(盒模型)是CSS标准中引入表示HTML标签元素的布局结构。
一个框模型从外到内大致包括(如图6-2):
-
- 外边距margin
- 边框border
- 内边距padding
- 内容content
在HTML网页中,每个可视元素布局都是按照框模型来设计的。(很多元素不是用来显示的,如表示语义的元素。)
框模型是布局计算的基础,渲染引擎根据该模型来理解 如何排版元素 以及 元素之间的位置关系。
包含块模型
当webkit计算元素的箱子的位置和大小是,webkit需要计算该元素和另外一个矩形区域的相对位置,这个矩形区域称为该元素的包含块。包含块具体定义如下:
-
- 根元素的包含块称为初始包含块。通常大小是可视区域的大小。
- 对于其他,设置为static、relative、sticky的元素,包含块是,最近祖先的箱子模型中的内容区域。
- 如果元素属性是fixed,包含块为初始包含块。该元素包含块脱离HTML文档,固定在可视区域的某个特定位置。
- 如果元素位置属性为absolute,那么包含块由最近的 position不为static 的祖先元素决定,
-
-
- 包含块由该祖先元素的padding box边界形成。
- 如果没有找到符合条件的祖先元素,则包含块是“初始包含块”。
-
CSS样式属性
css标准中定义了各式各样的样式属性,大致分为以下类别:
-
- 背景:设置背景颜色或图片。
- 文本:设置文本缩进、对齐、单词间隔、字母间隔、字符转换、装饰和空白字符等
- 字体:
- 列表:
- 表格:
- 定位:
CSSOM
CSSOM,css对象模型,思想是,在DOM中的节点接口中,加入获取和修改CSS属性或接口的JavaScript接口,因而JavaScript可以动态操作CSS样式。
CSSOM定义了样式的接口,CSSStyleSheet,可以在JavaScript中访问,获取样式表的各种信息(css的href、样式表类型type、规则信息cssRules)。
开发者可以通过document.stylesheets查看当前网页包含的所有CSS样式表,因为CSSOM对DOM中的Document接口进行了扩展。
W3C定义了CSSOM View规范,增加了新的属性到window、document、element、HTMLElement、MouseEvent等,这些css属性能够让JavaScript获取视图信息(如,窗口大小、网页滚动位置、元素的框位置、鼠标事件的坐标等)
CSS解释器和规则匹配
理解webkit如何解释css代码并选择相应的规则。
样式的webkit表示类
图6-5描述了webkit内部是如何表示css文档的。
一切的起源都是从DOM中的Document类开始。
先看Document类之外的左上部分:
-
- DocumentStyleSheetCollection类:包含了所有css样式表
- CSSStyleSheet:webkit内部表示类,包含了css的href、类型、内容等
- StyleSheetContents:css的内容,包含了一个样式规则。
-
-
- 样式规则被用在css解释器的工作过程中。
-
下面的部分,webkit主要是将解释后的规则组织起来,用于为DOM中的元素匹配相应的规则,从而应用规则中的属性值序列。
这一过程主要负责者是StyleSheetResolver类,属于Document类,并包含一个DocumentRuleSets类用来表示多个规则集合(RuleSet)。
每个规则集合都是将前面解释过的结果合并起来,并进行分类。如,id类规则、标签类规则等。
(为多个规则集合,是因为这些规则集合可能源于默认的规则集合,或网页自定义的规则集合等。)
样式规则是解释器的输出结构,是样式匹配的输入数据,类型如下,图6-6描述了这些类和继承关系。
-
- Style:基本类型,大多数规则属于这个类型。
- Import:webkit为了方便引入的,对应的是导入CSS文件的style元素。
- Media:对应css标准中的 @media 类型
- Fontface:css3引入的自定义字体的规则类型。
- Page:对应 @page 类型。
- KeyFrames:对应@-webkit-key-frames类型,用来定制特定帧的样式属性信息。
- Region:对css标准中的Regions的支持,方便开发者对页面分区域排版。
图6-7描述的是一个css规则和webkit的内部结构表示类。
-
- 最外层的是样式规则,通常一个样式表包括一个或多个样式规则,这里的样式规则对应StyleRule类。
- 其次是选择器部分。图的上半部分是选择器列表。
-
-
- 在webkit内部表示是CSSSelector类。
- 在规则中,CSSSelector类使用对象列表来表示。
-
-
- 最后是属性集合CSSPropertySet。
-
-
- 属性名字,CSSPropertyID
- 属性值,CSSValue
-
解释过程
CSS解释过程是指从CSS字符串经CSS解释器处理后变成渲染引擎的内部规则表示的过程。如图6-8所示。
这一过程是由CSSParser类负责。
-
- CSSParser是桥接类,实际解释工作由CSSGrammer.y.in完成。
- Bison是生成解释器的工具。
- Bison根据CSSGrammer.y.in生成CSS解释器——CSSGrammer类。
-
-
- CSSGrammer.y.in是Bison的输入文件。
-
-
- CSSGrammer需要调用CSSParser处理解释结果。
-
-
- 如,使用CSSParser创建选择器对象、属性、规则等。
-
当需要CSS内容时,webkit调用CSSParser对象来设置CSSGrammer对象解释,解释过程中需要回调CSSParser来处理,最后将结果设置到StyleSheetContents对象中。
在解释网页自定义样式前,webkit渲染引擎会为网页设置默认样式。不同的webkit移植可以设置不同的默认样式。
样式匹配规则
样式规则建立完成后,webkit将规则结果保存在DocumentRuleSets对象类中。当DOM节点建立后,webkit会为其中的可视节点选择合适的样式信息。
图6-9描述了参与样式规则匹配的webkit主要相关类。基本思路是:
-
- 使用StyleResolver类为DOM元素节点匹配样式。
-
-
- 根据元素信息(标签名、类别等),从样式规则中查找最匹配的规则。
-
-
- 然后将样式信息保存到新建的RenderStyle对象中。
- 最后,RenderStyle对象被RenderObject类所管理和使用。
规则的匹配是由ElementRuleCollector类计算并获得,它从DocumentRuleSets类获取规则集合,根据元素属性,按照ID、class、标签等选择器逐次匹配。
图6-10描述了webkit如何为HTML元素获取样式,并从规则集合中匹配的过程。
-
- 首先,当webkit为HTML元素创建RenderObject类时,
-
-
- StyleResolver类负责获取样式信息,并返回RenderStyle对象。
- RenderStyle对象包含了匹配完的样式信息结果。
-
-
- 其次,每个元素可能需要匹配不同来源的规则。
-
-
- 用户代理规则集合
- 用户规则集合
- 网页自定义规则集合
-
-
- 再次,对于自定义规则集合,
-
-
- 依次检查ID规则、class规则、标签规则等
- webkit会把匹配上的规则保存到匹配结果中。
-
-
- 最后,webkit对这些规则进行排序。
-
-
- webkit从高优先级规则中选取,并将样式属性返回。
-
实践:样式匹配
被划线的属性表示没有对元素起作用,包含两种情况:
-
- 属性设置错误
- 被更高优先级的规则属性覆盖。
JavaScript设置样式
图6-12描述了JavaScript设置样式的主要过程:
-
- JavaScript引擎调用设置属性值的公共处理函数,
- 该函数调用属性值解析函数
- webkit将解析后的信息,设置到元素的style属性的样式webkitTransform中
- 设置标记,表明该元素需要重新计算样式,并触发重新计算布局。
- 最后webkit重新绘图。
webkit布局
基础
当webkit创建RenderObject对象之后,webkit根据框模型来计算RenderObject对象的位置、大小等信息的过程称为布局计算(或排版)。
图6-13描述了这一过程中设计得主要webkit类。
-
- Frame类:表示网页的框结构。每个框都有一个FrameView类,用来表示框的视图结构。
- FrameView类:一个总入口类。主要负责视图方面的任务。如,网页视图大小、滚动、布局计算、绘图等。
-
-
- 两个跟布局计算密切相关的函数
-
-
-
-
- layout:用来布局计算
- needsLayout:用来决定是否需要布局计算。
-
-
-
-
- 实际的布局计算是在RenderObject类中。
-
布局计算可分为两类:
-
- 对整个RenderObject树进行的计算。
- 对RenderObject树中某个子树的计算,常用语文本元素或overflow:auto块的计算。
-
-
- 一般是其子树布局的改变不会影响其周围元素的布局,因此不需要重新计算更大范围内的布局。
-
布局计算
布局计算是一个递归的过程。(一个节点的大小通常要先计算子女节点的位置、大小等信息。)
图6-14描述了RenderObject节点计算布局的主要过程。主要逻辑是由RenderObject类的layout函数完成。
- 首先,该函数会判断RenderObject节点是否需要重新计算。
-
- 通常需要通过检查位数组中的相应标记位、子女是否需要计算布局等确定。
- 其次,该函数会确定网页宽度和垂直方向上的外边距。
-
- 网页通常在垂直方向上滚动,水平方向尽量不需要滚动。
- 再次,该函数会遍历每一个子女节点,依次计算它们的布局。
-
- 一般来说,页面元素的宽高是布局时计算得出的,除非网页定义了元素的宽高。
- 元素定义了自身宽高,webkit按照定义的宽高确定元素大小。
- 文本节点之类的内联元素,需要根据字号大小、文字多少来确定宽高。
- 元素宽高超过了布局容器包含块能提供的宽高,同时其overflow为auto或visible,webkit会提供滚动条来保证显示全部内容。
- 如果元素有子女,则webkit需要递归这一过程。
- 最后,节点根据子女的大小计算得出自己的高度。
- 整个过程结束。
总体来讲,只要样式发生变化,webkit都需要重新计算,实际场景中有以下情况:
- 当网页首次被打开时,浏览器设置网页可视区域,会调用布局计算的方法。
-
- 可视区域发生变化时,包含块大小也会发生变化,所以需要重新计算布局。
- 网页的动画会触发布局计算。
- JavaScript通过CSSOM等直接修改样式,也会触发布局计算。
- 用户交互也会触发布局计算。
-
- 如,滚动网页。
CSS的布局计算是以包含块和框模型为基础的,这表示元素的布局计算都依赖于块。
- 块,通常在垂直方向上展开。
- 内联元素,表现为行布局形式,即,以 行 进行显示。
对于一个块元素对应的RenderObject对象,它的子女:
- 要么都是,块元素的RenderObject对象,
- 要么都是,非内联元素对应的RenderObject对象。
布局测试
布局测试可以说是webkit最重要也最著名的测试了,用于测试网页渲染结果,包括网页加载和渲染整个过程。
渲染引擎需要处理各种各样越来越复杂的网页,需要布局测试来保证引擎的渲染结果的正确性。
基本测试工作方式如下:
- 预先准备大量用于测试的网页和期望的渲染结果,
- 然后用webkit编译出的DumpRenderTree来测试排版,
- 把得到的结果和期望的结果进行对比,以检查webkit引擎对网页计算排版布局等的正确性。