如果你在日常工作中使用 CSS,你的主要目标可能会重点围绕着使事情“看起来正确”。如何实现这一点经常是远不如最终结果那么重要。这意味着比起正确的语法和视觉结果来说,我们更少关心 CSS 的工作原理。
CSS 的视觉结果通常是操作隐藏属性的间接后果,你可能还没有意识到这一点。某些 CSS 属性(比如 background-color
)与你看到的内容有直接而明显的关系。同时,其它像 display
这样的属性对于我们很多人来说仍然是模棱两可的,因为结果似乎与上下文环境有很大关系。所以...我们需要对于CSS进行再次的整理和探索
渲染过程概述
当加载一个 HTML 文档时,为了让该页面渲染,有很多事情要发生。
第一个步骤就是解析 HTML 文档。浏览器从这一步创建一个文档树。树状结构是表示像 HTML 这种具有明显层次结构信息的一种方法。树中的元素可以被描述为类似于族谱,比如祖先、父亲、孩子和兄弟姐妹。
你可能听说过术语 DOM。它代表文档对象模型(Document Object Model)。这是文档树结构的一种扩展,被用于存储和操作 Web 文档内容有关的信息。
随着 HTML 被解析,样式表和其它资源也被获取。样式声明通过一个称为层叠的过程来解释和确定。
在此过程期间,CSS 属性的最终值被确定。在计算后,这些值可能与样式表中定义的有所不同。例如,像 auto
这种关键字和相对单位被指派了真实值,并且继承值被应用了。这些计算值被存储在一个树中,类似于 DOM 中的元素,毫不奇怪,它被称为 CSS 对象模型或者 CSSOM。
现在才有可能开始渲染页面的过程。这个过程的第一步是盒模型的计算。这是一个算出元素的尺寸和间距的重要步骤,虽然不一定是元素的最终位置。
与盒模型相比,不那么被人熟知的是一个称为视觉格式化模型的过程。这个过程确定元素在页面上的布局和位置。它包含了一些你可能已经熟悉的概念,比如定位方案、格式化上下文、显示模式和堆叠上下文。
最后,页面被渲染。
上面的段落中可能有一些你还不熟悉的一些术语。如果是这样的话,最重要的是要理解层叠、盒模型以及视觉格式化模型。这些术语都是解释、处理和渲染 HTML 和 CSS 的核心步骤。我们下面要更详细地研究一下这三个步骤。
层叠
层叠可能是最被误解的 CSS 特性之一。它是指组合不同样式表以及解决 CSS 选择器之间冲突的过程。
层叠查看声明的重要性、来源、特殊性以及顺序,来确定使用哪个样式规则。
你需要知道什么:
大多数网站有多个样式表。通常,样式是用引用一个 CSS 文件的一个 link
标记,或者 HTML 主体部分的 style
标记添加的。即使最基础的页面也会有浏览器提供的默认样式。这种默认样式表有时被称为 user-agent 样式表。
在层叠期间,样式表是按如下顺序被解释的:
-
!important 声明
-
作者的样式表
-
浏览器默认的样式表
注意:这里我跳过了用户样式表,是因为它们不再常见,可能不会是读这本文的所有人要考虑的因素。
在组合这些来源后,如果多个规则应用到同一元素,就用特殊性来确定应用哪条规则。
特殊性
特殊性(Specificity)是给选择器的权重。它常被误认为是一个数字。实际上是 4 个单独的数字或者 4 种权重类别。
要计算特殊性,要统计如下选择器的数目:
-
ID 选择器
-
类选择器、属性选择器和伪类选择器
-
元素选择器和伪元素选择器
例如:#nav .selected:hover > a::before
将是 1, 2, 2
。
无论有多少个类选择器,都不会比一个 ID 选择器有更高的特殊性。当比较选择器时,我们首先比较 ID 选择器的特殊性。只有 ID 选择器相等时,才比较类选择器、属性选择器和伪类选择器的特殊性值。如果最后依然相等,才比较元素和伪元素选择器。
如果每个类别的特殊性都是相等的,那么来源中最后的声明获胜。
是的!我知道我说的是 4 个类。行内样式比 ID 选择器有更高的特殊性。不过由于在技术上行内样式在特殊性计算中是第一类,通常你不会与行内样式竞争,所以很容易记住行内样式的特殊性总是会占优。
重要的注意事项:虽然 !important 声明不会作为特殊性计算的因素,不过它们比层叠中的普通声明有更大的优先级。
继承
继承不是层叠的一部分,不过我在这里把它包含进来,是因为它经常被与层叠结合在一起讨论。
继承是应用给一个元素的值可以被传递到(或者继承)子元素上的过程。
你肯定知道这个事实,即当把字体属性应用到 body 或者其它容器元素时,该属性也会被容器内的所有元素所继承。这就是继承。
并非所有属性默认都被继承。理解继承是编写更优雅更简洁 CSS 的关键。有时候用 inherit
关键字强制继承会相当有用。
注意: 有些属性(比如 border-color
)默认值是 currentcolor
。也就是说,它们会使用在 color
属性上设置的值。默认值与继承不是一码事。不过,color
属性本身经常是被继承的,所以我倾向于认为这是一种事实上的继承。
盒模型
理解盒模型是布局和定位时防止挫败所必需的。盒模型是 CSS 中最基本的概念之一。
盒模型用于计算元素的宽度和高度。它只是计算过程中的一个步骤,确定元素的最终布局和定位并非完全依赖于它。
你需要知道什么:
HTML 中的每个元素都是一个矩形的盒子。每个盒子有用元素的外边距(margin)、边框(border)、内边距(padding)和内容区定义的四个区域。
默认情况下,当我们设置一个元素的宽度时,只是设置内容区的宽度。当给一个元素添加内边距、边框或者外边距时,是增加了除宽度以外的部分。实际上,这就是说宽度为 50% 的两个元素如果添加了内边距、外边距或者边框,就不会并排填满宽度(因为已经超过了 100% 宽度)。
当设置元素的背景时,不仅会填充内容区,还会填充内边距区和边框区。
概念上,我们把一个 HTML 元素当作是一个东西,所以很容易认为一个元素的视觉边界等于它的宽度,不过实际却并非如此。虽然一个元素的视觉边界包含了内边距和边框区,不过 width 属性是显式地被应用到内容盒。
Width Auto
另一个潜在的困惑来源是 auto
的工作机制。宽度为 auto
是大多数 HTML 元素的默认元素,对于像 div、p 这种块元素来说,auto
会计算宽度,这样外边距、边框、内边距以及内容区都组合在一起也能刚好放在可用空间内。
在这种情况下,感觉添加内边距和外边距会向内挤压内容,不过实际上,宽度会被重新计算,以确保所有东西都能刚好放下。相比之下,在设置宽度为 100%
时,光内容区就会把可用空间填满,而不管外边距、内边距和边框。
Box-sizing
box-sizing
属性会改变盒模型的工作方式。当 box-sizing
被设置为 border-box
时,内边距和边框会减少内容区的内部宽度,而不是添加元素的整体宽度。也就是说,元素的宽度现在与其视觉宽度是相同的。
视觉格式化模型
盒模型计算元素的大小,而视觉格式化模型(Visual Formatting Model)负责确定这些盒子的布局。视觉可视化模型考虑盒子的类型、定位方案、元素之间的关系以及内容施加的约束,来确定页面上每个元素的最终位置和呈现。
你需要知道什么:
视觉格式化模型遍历文档树,根据 CSS 盒模型生成一到多个渲染元素所需的盒子。CSS 的 display
属性在确定一个元素如何参与当前上下文和定位方案中发挥关键作用。这些部分综合在一起,确定元素的最终布局和定位。
这是一个复杂的步骤,也是目前为止最难尝试和总结的。如果你还没了解所有这些知识的话,没有关系。理解如何通过 CSS 属性操纵定位方案和格式化上下文是个好的开端。如果你理解了该模型的不同部分之间的相互作用的话,你就会比大多数人做得更好。起码你应该知道它们的存在。
Display 类型
我们知道,在 CSS 中设置 display
属性可以确定如何渲染元素,但是还不清楚其工作原理。事实上,它有时甚至好像是不可预测的。
这是因为,display
属性确定了元素的”盒子类型“。这个隐藏属性由一个内部显示类型和一个外部显示类型组成,二者在一起帮助确定如何渲染元素。
外部显示类型通常要么是解析为 block
,要么是解析为 inline
,并且与我们对 CSS 中这些 display
属性的期望几乎是一致的。从技术上讲,外部显示类型规定一个元素如何参与其父元素的格式化上下文。
内部显示类型决定元素会生成什么样的格式化上下文。这会影响其子元素的布局排列方式。
想想弹性盒容器的工作机制。其外部类型是 block
,内部类型是 flex
。它的子元素也可以有一个 block 外部类型,不过子元素的布局是受弹性盒容器的格式化上下文所影响的。
思考这种问题的一个方法是,display 的职责是在元素及其父元素之间共享的。
格式化上下文
格式化上下文都是与布局有关。它们是控制容器内元素的布局以及它们之间如何相互作用的规则。
有些格式化上下文可以直接在容器上设置,比如通过用 display
属性值 flex
、grid
或 table
。其它类型的格式化上下文,比如块格式化上下文和行内格式化上下文可以被浏览器根据需要创建。
注意:在过去,因为块格式化上下文与浮动交互的方式,理解如果让浏览器建立一个新的块格式化上下文是很重要的。设置为块格式化上下文的元素会包含浮动。不过现在它没有以前那么重要。事实上,它甚至都不是现代清除浮动技术的工作原理了。
定位方案
一个盒子可以根据三种定位方案之一来布局,包括常规流、浮动和绝对定位。你可能熟悉浮动和绝对定位,因为在编写 CSS 时,我们会更直接与这两个打交道。常规流只是一个在元素没有浮动或者定位时默认定位方案的名字。
常规流
常规流(Normal Flow)描述了默认定位方案,'in-flow'(流内)描述遵从该方案的元素。你可以把流内当作是元素根据其源顺序和格式化上下文布局时的自然位置。
浮动
浮动(float)是一个 CSS 属性,它会导致元素脱离常规流,尽可能远地向左或者向右移动,直到它挨着其包含盒或者另一个浮动元素的边缘。当浮动发生时,文本和行内元素会环绕着浮动元素。
正常情况下,如果没有设置浮动,一个元素的高度会调整以容纳其所有的后代元素。当元素被浮动时,就会从流中脱离出来,这意味着容器不会调整其高度以清除它们。
正是这种行为,让多行文本、标题以及其它元素环绕着浮动内容而流动。但是有时这是有问题的。清除浮动并设立新的块格式化上下文会导致容器清除其浮动子元素。这种技术允许浮动被用在布局上,已经成为 Web 开发技术的奠基石很久了。它依然是要知道的重要技术,不过正在逐渐被像 Flexbox 和 Grid 这种更新的布局技术所替代。
绝对定位
绝对定位的元素是彻底从流中删除,并且不像浮动元素,它们对周围的内容没有影响。
相对定位的容器允许我们用绝对定位来控制后代元素的偏移。
相对定位元素也可以设置偏移,不过这种偏移是相对于父元素的常规位置,而不是另一个相对定位的容器。
CSS 属性 top
、bottom
、left
和 right
被用于计算盒子的偏移。这些属性都不是二维偏移,不过都可以相对于其容器的内容盒来定位每个边。
具有重叠偏移的定位元素可以导致元素占用同一空间。堆叠上下文用于解决这个问题。
堆叠上下文
堆叠上下文决定渲染到页面上的东西的顺序。你可以把一个堆叠上下文当作一个层。堆叠底部的层先绘制,该堆叠之上更高的元素出现在上面。
在一个绝对或者相对定位元素上设置 z-index
属性是建立新的堆叠上下文的最常见手段。不过,有很多组成堆叠上下文的其它手段,包括设置透明度(opacity)、转换(transform)、滤镜(filter)或者使用 will-change
属性。
不过,使用这些其它手段的原因并不直观,并且比开发者的预期相比,这些手段对渲染性能有一定影响。只不过它们有助于理解这些层可以被浏览器单独渲染。因此,有时因为性能的原因,有意创建新的堆叠上下文是很有用的。
除非建立了堆叠上下文,否则设置 z-index
是没有效果的。z-index
越高,层在堆叠中放在的位置越高。
有关堆叠的最让人感到困惑的部分之一是,一个新的堆叠上下文可以建立在一个已有的堆叠上下文内。也就是说可以有层中层。
在这种情况下,并非总是最高的 z-index
会放在最上面。
写在最后
太久没有过好好写博客了...感觉好多东西仿佛你理解和写出来完全是两个不同的概念...庆幸自己憋着气还是把想写的内容写出来了。其实关于CSS,我一直都不认为是一个简单的样式表而已。深入去研究,会发现其难度并不比JS的各种理论知识轻松。后面我会尝试去更深入的去分析CSS中的各种属性,以及认真研究研究grid和flex布局的优势。
完结撒花~
PS:参考资料:《CSS权威指南(第3版)》