CSS解释器和样式布局

80 阅读12分钟

CSS解释器和样式布局

CSS基本功能

简介

CSS全称Cascading Style Sheet,级联表,主要用来控制网页的显示风格,有两个重要特征。

    • 将网页内容和内容展示方式分离,提高开发效率。
    • 不仅能提供对页面任意元素的精准控制,同时还能提供丰富多彩的样式。

样式来源有三种类型:

    • 网页开发者编写的样式信息
    • 网页的读者设置的样式信息
    • 浏览器的内在样式

以上三种类型的优先级是递减的。

样式规则

样式规则是css规范中最基本的组成。如图6-1描述了一个典型的css规则结构。

一个规则包含:

    1. 规则头
      1. 由一个或多个选择器组成
    1. 规则体
      1. 由一个或多个样式声明组成
      2. 每个样式声明由样式名和样式值组成。

选择器

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需要计算该元素和另外一个矩形区域的相对位置,这个矩形区域称为该元素的包含块。包含块具体定义如下:

    1. 根元素的包含块称为初始包含块。通常大小是可视区域的大小。
    2. 对于其他,设置为static、relative、sticky的元素,包含块是,最近祖先的箱子模型中的内容区域。
    3. 如果元素属性是fixed,包含块为初始包含块。该元素包含块脱离HTML文档,固定在可视区域的某个特定位置。
    4. 如果元素位置属性为absolute,那么包含块由最近的 position不为static 的祖先元素决定,
      1. 包含块由该祖先元素的padding box边界形成。
      2. 如果没有找到符合条件的祖先元素,则包含块是“初始包含块”。

下图为MDN上对包含块的解释供参考:

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引擎对网页计算排版布局等的正确性。