如果你在日常工作中使用CSS,那么你的主要目标很可能集中在使事情看起来是正确的。最终得到的正确结果远比如何实现更重要。这意味着相比正确的语法和视觉效果我们更少关注CSS的实现原理。
你可能还没有意识到,但CSS的视觉效果通常是操纵隐藏属性的间接结果。一些CSS属性(如 background-color)和你看到的视觉效果就有显然的直接关系。而其他的(比如display)对我们许多人来说仍然含糊不清,因为结果似乎高度依赖于上下文。
我怀疑很多开发者都不能简单的描述当设置了display: block之后实际上做了什么。最多你可能只是直观地了解这个属性是如何工作的。没关系,你可以在不了解基本原理的情况下,对CSS有很好的争议。虽然,你知道解决问题的方案,但是你却不一定真的了解问题。
你过你正式上面说的那样,没关系。刚开始我也是只知道怎样使用CSS,不久前才了解CSS的工作原理的。我想这并不能让你感觉好一点,但至少你并不孤单!
虽然CSS的基本特征是复杂且有意提取的,但是我们不能因此一点不去了解它。对于许多人来说,诸如盒子模型、级联和特殊性等概念是我们所熟知的。虽然他们经常被曲解,但是知道这些工作原理有助于我们编写更好的CSS。
CSS其他的隐藏黑科技也是如此。学习理解这些黑科技的问题就是学习的壁垒很高。它常常感觉好像什么都不能单独解释。在理解工作原理中最小的部分之前,您都需要了解所有的内容。
正因为如此,我想试着揭示CSS的这些隐藏黑科技部分,只介绍涉及你需要知道的部分,并希望以逻辑顺序解释该过程,以便你更好的理解CSS真正的工作原理。
当你加载一个HTML文档的时候,页面的渲染过程中会按照顺序发生了很多事情。
第一步就是解析HTML文档。从这一步开始浏览器会生成一个“文档树”。树的结构是一种用HTML代表具有明显层次结构信息的方式。树中的元素可以用类似于家谱的方式描述,比如:后代节点、父节点、子孙节点和兄弟节点。
你可能听说过“DOM”这个术语。它代表“文档对象模型(Document Object Model)”。它是文档树结构的扩展,它被用来存储web文档内容和操作信息。
随着HTML被解析,样式文件和其他资源文件会被下载。样式声明通过一个称为级联的过程来解释和决定。
在此过程中,将解析CSS属性的最终值。经过计算,这些值可能与我们样式表中所写的值不同。例如:像auto 这样的相对单位的关键字被赋予了真正的值,并会应用继承的值。这些计算好的值会像存储在DOM树中的元素一样被存储在一个树中,毫无疑问会被称为CSS对象模型(CSS Object Model )或CSSOM。
现在就可以开始渲染页面的过程了。这个过程中的第一步是计算 盒模型。这一步对于计算出元素的大小和间距是很重要的,尽管可能并不是最终的位置。
和 盒模型相比并不是那么被熟知的过程叫做 视觉格式模型。此过程会确定页面上元素的布局和位置。它包含您可能已经熟悉的一些概念,例如:定位方案(positioning schemes), 格式化上下文(formatting contexts), 显示模式(display modes), 和 堆叠上下文(stacking contexts)。
最终页面被渲染出来。
以上段落中可能有几个术语,您还不熟悉。如果是这样的话,最重要的是理解 级联, 盒模型, 和 视觉格式模型,理解这些术语是解释、处理和渲染HTML和CSS至关重要的一步。在描述上面每个渲染过程的细节时,我跳过了很多的细节,所以接下来我们更加仔细地看这三个步骤。
级联
级联可能是CSS中最容易被弄错的属性之一。它指的是合并不同样式表并解决CSS选择器之间冲突的过程。
级联查看声明的优先级、来源、特性和顺序,以确定使用哪种样式规则。
你需要知道什么:
大多数网站都有多种样式表。通常的样式是在页面中添加了一个引用css文件的link 标签,或者在HTML主体中使用 style 标签。即使最基本的页面也有由浏览器提供的默认样式。此默认样式表有时称为用户代理样式表(user-agent stylesheet)。
在级联过程中样式表按以下顺序解释:
!important 声明
开发者编写的样式表
浏览器默认的样式表
注意事项: 我跳过了用户样式表,因为它已不是常见的了,可能不会考虑任何阅读这篇文章的人。
合并这些来源的样式之后,如果很多规则被用在了同一个元素上,则用权重确定应用的规则。
特殊性
特殊性是指选择器的权重。仅仅把它看作一个单独的数字是一个常见的错误。实际上它是4个独立的数字或4种类别的权重。
计算特殊性,把下面的权重相加:
ID,
class, 属性 和 伪类,
元素 和 为元素
例如: #nav .selected:hover > a::before 的权重分别是 1, 2, 2.
无论多少个class的权重,都没有ID的权重高。当比较选择器时,首先应该比较ID的权重大小。仅当ID的权重一样的时候,再比较class、属性和伪类的权重,最后,如果权重依然一样的话就比较元素和伪元素的权重。
如果每个类别的权重都相等的话,则采用就近优先的原则(即应用来源中最后声明的规则)。
是的!我知道我说了4类的权重大小。但是行内样式的权重比ID的更高。尽管它们是技术上权重计算中的第一类,但是通常是无法和行内样式竞争的,所以,很容易记住行内样式总是高于其他类别的权重。
重要注意事项: !important 声明没有考虑权重计算,但是它们比级联中的正常声明具有更高的优先级。
继承
继承不是级联中的一部分,但是这里我把它包含进来主要是因为它经常和级联一起被讨论。
继承是应用于元素的值可以由其子元素传递(或继承)的过程。
您可能很熟悉字体属性(当应用于body或另一个容器元素时)也由该容器内的每个元素继承的事实。这就是继承。
并非所有属性都默认继承。 了解继承是编写更加优雅和简洁CSS的关键。 使用inherit关键字强制继承是非常有用的。
注意事项: 某些属性如border-color 具有默认值currentcolor。 这意味着他们将使用color属性上设置的值。 这个默认值与继承不一样。 虽然颜色属性本身通常是继承的,所以我倾向于认为这是一种事实上的继承。
盒模型
了解盒子模型对于限制使用布局和定位时的问题是必不可少的。 它是CSS中最基本的概念之一。
盒模型用于计算元素的宽和高。这是一个计算步骤,并不完全被依赖于确定元素的最终布局和定位。
你需要知道什么:
HTML的每一个元素都是一个矩形的盒子。每个盒子都有4个区域,用于定义元素的边距(margin)、边框(border)、填充(padding)和内容区域。
默认情况下,你给一个元素设置的宽度,只是设置了内容区域的宽度。当你给元素添加padding、border和margin时,这是增加了除了宽度之外的部分。实际上,这意味着宽度为50%的两个元素如果添加了padding,margin或border,则会超过100%的宽度,进而导致不能并排排版了。
就是这样!这是相当简单的对吧?为什么这常常是困惑的来源呢?好吧,你可能遇到过一些情况,事情似乎有些不同的表现…
填充区域
当你给一个元素设置背景的时候,填充的不仅仅是内容区域,而且还包括内部padding区域和边框区域。
从概念上来说,我们认为HTML元素是单一的东西。因此很容易认为元素的视觉边界等于其宽度,但情况并非如此。尽管元素的视觉边界包括填充(padding)和边框(border)区域,width 属性明确地应用于内容区域上。
注意事项: 改变 box-sizing 属性可以改变这种变现行为。
动态宽度
另一个潜在的困惑的来源是width: auto 是怎样工作的。一个自动的宽度对于大部分的HTML元素都是一个默认值,比如:div和p标签,auto 的宽度计算以便于margin、border、padding和内容区域合并之后能够适应可用的空间。
在这种情况下,它似乎可以感觉到在内容上田间的填充和边距,但实际上,是重新计算宽度以确保一切都适合。 通过比较,设置宽度为“100%”时,不管边距、填充和边框的大小是多少,内容区域都将填充可用空间。
Box-sizing
box-sizing属性能够改变盒模型的工作方式。当 box-sizing设置为border-box 时,padding和border将减少内容区域的内部宽度,而不是增加元素的整体宽度。这意味着一个元素的宽度现在与它的可视宽度相同。
很多人喜欢这个属性,如果你正在建立一个网格系统,或任何其他需要水平对齐类型的布局项目,这可以是更直观的工作方式。
边距重叠
当边距意外重叠当时候,真的令人很困惑,因为你不知道发生了什么。当两个或多个相邻的垂直边距接触时,边距有时会发生重叠,并且不会用填充或边框分隔。如果子元素的边缘扩展到父元素的边缘,并且不会被填充分隔开,那么就会出现边距重叠的现象。
如果元素采用的是绝对定位、浮动定位或者有一个不一样的 格式化上下文时,边距不会发生重叠现象,以及在其他一些不太可能的情况下。
如果你感到困惑,没关系。边距不会发生重叠的规则是复杂的。 您需要知道的主要事情是当元素没有填充或边框时,垂直边距可能会重叠。
如果你想了解的更详细, CSS Tricks 有一篇很好的 边距重叠释义的文章推荐你看一下。
视觉格式化模型
当盒模型计算元素的尺寸时,它是负责确定这些盒布局的视觉格式模型。 视觉格式模型考虑了盒的类型、定位方案、元素之间的关系和由内容强加的约束,以确定页面上每个元素的最终位置和呈现。
你需要知道什么:
视觉格式模型遍历文档树,并按CSS盒模型生成一个或多个渲染元素所需的盒子。CSSdisplay属性在决定元素如何参与当前的格式化上下文和定位方案中起着关键的作用。这些部分将决定元素的最终布局和位置。
这是一个复杂的步骤,是迄今为止最难尝试和总结的。如果你还不了解所有的关于这个部分的话,没关系。理解我们如何通过CSS属性操作 定位方案和格式化上下文是一个很好的开始。如果你能掌握这一模式的不同部分之间的相互作用,你就会比大多数人做得更好。至少你应该知道它们是存在的。
显示类型
我们知道在CSS中设置display 属性可以决定一个元素怎样被渲染,但是目前还不清楚它的工作原理是什么样的。事实上,有时甚至是不可预测的。
这是因为display 属性决定了元素的“盒类型”。该隐藏属性由内部显示类型和外部显示类型组成,这些类型一起帮助确定元素的呈现方式。
外部显示类型通常解析为“block”或“inline”,并且几乎与CSS中的“display”属性的期望一致。 从技术上讲,外部显示类型决定了元素如何参与其父元素的格式化上下文。
内部显示类型确定该元素将生成什么样的格式化上下文。 这将影响其子元素的布局。
想象一下Flexbox容器的工作原理。 它的外部类型是block,其内部类型是flex。 它的子元素外部类型也可以是block,但它们的布局受到Flexbox容器的格式上下文的影响。
思考这个问题的一种方式是,显示的职责在元素和它的父元素之间共享。
格式化上下文
格式化上下文是关于布局的。 它们是管理容器内元素布局的规则,以及它们如何相互交互。
一些格式化上下文可以直接在容器上建立,例如通过使用display 的值为:flex、grid或table。 其他类型(如块和内联格式化上下文)按照浏览器的要求创建。
注意事项: 一度,因为它和浮动元素的交互方式,理解如何让浏览器建立一个新的块格式化上下文非常重要。一个块格式化上下文的元素会包含浮动的元素。今天的情况不像以前那么重要了。事实上,这甚至不是现代 清除浮动技术 的工作方式。
定位方案
一个盒元素可以根据3种定位方案中的一种来布局。这三种方案分别是:正常文档流布局, 浮动布局 和 绝对定位布局。您可能熟悉浮动和绝对定位布局的方式,因为我们在编写CSS时更直接与这些交互进行交互。 当一个元素未浮动或绝对定位布局时,正常文档流布局只是默认定位方案的名称。
正常文档流布局
正常文档流描述了默认的定位方案,“in-flow”描述符合此要求的元素。 在文档流中您可以认为是根据其源的顺序和格式化上下文布局的元素的自然位置。
浮动布局
Float(浮动)是一个CSS属性,它使一个元素从正常流中跳出来,并尽可能地向左或向右偏移,直到它接触到其上一级的盒元素或另一个浮动元素的边缘。 当这种情况发生时,文本和内联元素将包围浮动元素。
通常如果不设置,元素的高度将适应其所有后代元素。 当元素浮动时,它们从正常文档流跳出来,这意味着容器不会调整其高度以将其清除。
正是这种行为允许多种文本、标题和其他元素对浮动内容进行流式包裹。但有时这是有问题的。清除浮动和建立一个新的块格式化上下文将使容器清除其浮动的子元素。这种技术允许使用浮动来进行布局,很久之前这就已经成为web开发技术之一了。这种技术仍然很重要,但它也正逐渐被新的布局技术所取代,比如Flexbox和Grid。
绝对定位布局
绝对定位的元素完全从文档流中去除,不同于浮动元素,它们对周围的内容没有影响。
具有相对定位的容器允许您使用绝对定位来控制后代元素的偏移量。
相对定位的元素也可以被给定一个偏移量,但是这个偏移量是与元素的正常位置相对的,而不是另一个相对的容器。
CSS的top, bottom, left 和 right 属性用来计算“盒容器的偏移量”。这些属性不是二维偏移,而是每个边缘相对于其容器的内容盒子进行定位。
具有重叠偏移的定位元素可以导致元素占用相同空间而发生重叠问题。堆叠上下文可以解决这个问题。
层叠上下文
堆叠上下文决定事物呈现到页面的顺序。 你可以想象一个堆叠上下文,如图层。 堆叠底部的图层首先绘制,堆叠上方的元素出现在顶部(相对于底部来说是在上层)。
在一个绝对或相对定位的元素上设置z-index 是建立新的堆叠上下文的最常见方式。 但是还有其他一些方法可以形成堆叠上下文,包括设置不透明度(opacity),转换(transforms),过滤(filters)或使用will-change属性。
其中的一些原因并不直观,与开发人员的预期相比,更多的是与渲染的性能有关。这有助于理解这些层可以由浏览器单独渲染。因此,出于性能考虑,故意创建一个新的堆栈上下文有时会很有用。
除非建立了堆叠上下文,不然设置z-index没有效果。 z-index的值设置的越高,层叠放置的堆叠越高(越靠近被最终显示的上层)。
关于堆叠最令人困惑的部分之一是可以在现有堆叠环境中建立新的堆叠上下文。 这意味着您可以拥有多层图层。
在这种情况下,并不总是拥有最高的z-index值显示在堆叠越高的位置。
就是这样!
差不多将近3000字,我只是简短地介绍一些CSS的重要的不为人熟知的工作原理部分。 如果您已经全部读完有所收获的话,恭喜你,请一定告诉我,因为你值得被奖励!
如果你只是读了其中的一部分也没关系。我希望我的这篇文章澄清了一些事情,或者对所涉及的过程有了一个大致的说明。在不牺牲精确性的情况下,用简单的术语解释这些东西是一个真正的挑战。我希望这是对的。