摘要: 理解浏览器原理。css
一旦 CSS 被浏览器下载,CSS 解析器就会被打开来处理它遇到的任何 CSS。这能够是单个文档内的 CSS、<style>
标记内的 CSS,也能够是 DOM 元素的style
属性内嵌的 CSS。所 有 CSS 都根据语法规范进行解析和标记。解析完成后,就会生成有一个包含全部选择器、属性和属性各自值的数据结构。html
例如,考虑如下 CSS:前端
.fancy-button { background: green; border: 3px solid red; font-size: 1em; }
以上 CSS 片断将生成以下数据结构,以便在后续的过程当中方便使用:web
值得注意的一件事是,浏览器将 background
和 border
的简写还原成普通写法,也就是一个一个属性的声明,由于简单写主要方便开发人员的编写,但从这里开始,浏览器只处理普通写法。完解析成以后,浏览器引擎继续构建 DOM 树。算法
既然咱们已经解析了现有内容中的全部样式,接着就是对它们进行样式计算了。咱们尝试尽可能对全部值减小到一个标准化的计算值。当离开计算阶段时,任何维度值都被缩减为三个可能的输出之一:auto
、百分比或像素值。为了清晰起见,让咱们看几个例子,看 web 开发人员写了什么,以及计算后的结果:小程序
如今咱们已经计算了数据存储中的全部值,是时候处理级联了。segmentfault
因为 CSS 来源有多种,因此浏览器须要一种方法来肯定哪些样式应该应用于给定的元素。为此,浏览器使用一个名为 特殊性(specificity) 的公式,它计算选择器中使用的标记、类、id 和属性选择器的数值,以及 !important
声明的数值。微信小程序
经过内联 style
属性在元素上定义的样式被赋予一个等级,该等级优先于 <style>
块或外部样式表中的任何样式。若是 Web 开发人员使用 !important
某个值,则该值将赛过任何 CSS,不管其位置如何,除非还有 !important
内联。浏览器
同一级别的个数,数量多的优先级高,假设一样即比较下一级别的个数。至于各级别的优先级例如如下:微信
!important > 内联 > ID > 类 > 标签 | 伪类 | 属性选择 > 伪对象 > 通配符 > 继承
选择器的特殊性由选择器自己的组件肯定,特殊性值表述为 5 个部分,如:
0,0,1,0,1
(1)、对于选择器中给定的各个 !important
属性值,加 1,0,0,0,0 。
(2)、对于选择器中给定的各个 ID 属性值,加 0,0,1,0,0 。
(3)、对于选择器中给定的各个类属性值、属性选择器或伪类,加 0,0,0,1,0 。
(4)、对于选择器中给定的各个元素和伪元素,加 0,0,0,0,1 。伪元素是否具备特殊性?在这方面 CSS2 有些自相矛盾,不过 CSS2.1 很清楚的指出,伪元素具备特殊性,并且特殊性为 0,0,0,0,1,同元素特殊性相同。
(4)、结合符(+ > [] ^= $= 等等特殊符号)和通配符(*)对特殊性没有任何贡献,此外通配符的特殊性为 0,0,0,0,0。全是 0 有什么意义呢?固然有意义!子元素继承祖先元素的样式根本没有特殊性,所以当出现这种状况后,通配符选择器定义的样式声明也要优先于子元素继承来的样式声明。由于就算特殊性是 0,也比没有特殊性可言要强。
为了说明这一点,让咱们说明一些选择器及其计算后的权重数值:
而当优先级与多个 CSS 声明中任意一个声明的优先级相等的时候,CSS 中最后的那个声明将会被应用到元素上。
在下面的示例中,div
将具备蓝色背景。
div { background: red; } div { background: blue; }
如今 CSS 将生成如下数据结构,在本文中,咱们将继续在此基础上进行构建。
CSS 也有来源,但它们的用途不一样:
CSS 信息能够从各类来源提供,这些来源能够是 用户(user) 和 做者(author) 及 用户代理/浏览器(user agent),优先级以下:
用户样式
浏览器还容许用户设置网页的样式,例如,咱们用 IE 浏览网站的时候,均可以经过浏览器查看菜单下的样式或者文字大小子菜单来设置网页实际的显示效果。
做者样式
网页建立者创建的样式表,通常会 css 文件出现或者是在页面头部里定义的 style,也就是网站源代码的一部分。例如,你们看百度和谷歌的页面就不同,这就是做者样式不同的结果。
用户代理/浏览器样式
也就是浏览器自身设置用来显示网站的样式,不一样的浏览器可能有不一样的样式表,例如 IE 和 Firefox 的就不同,因此你们分别使用这两种浏览器访问同一个网站的时候,看到实际效果可能就不一样。
一般状况下,做者样式具备最高的重要性,其次是用户样式,最后才是浏览器样式,可是若是出现了 !important
标记的话,那么规则会被改变,经过 !important
能够提升某种样式的重要性,让它的优先级高于其余没有加该声明的全部样式。
让咱们进一步扩展咱们的数据集,看看当用户将浏览器的字体大小设置为最小 2em
时会发生什么:
当浏览器拥有一个完整的数据结构,包含来自全部源的全部声明时,它将按照规范对它们进行排序。首先,它将按来源排序,而后按特性(specificity)排序,最后按文档顺序排序。
从上图可知,类名为 .fancy-button
优先级最高(表中越上面优先级越高)。例如,从上表中,人会注意到用户的浏览器首选项设置优先 于 Web 开发人员的设置样式。如今,浏览器找到与选择器匹配的全部 DOM 元素,并将获得的计算样式挂载到匹配的元素,在本例中 div
为类名为 .fancy-button
:
若是您但愿了解更多关于级联的工做原理,请查看官方规范。
虽然到目前为止咱们已经作了不少,但尚未完成。如今咱们须要更新 CSS 对象模型(CSSOM)。 CSSOM 位于document.stylesheets
中,咱们须要对其进行更新,以便让它知道咱们目前为止已经解析和计算的全部内容。
Web 开发人员可能在没有意识到的状况下使用这些信息。例如,当调用 getComputedStyle() 时,若是须要,运行上面指出的相同过程
如今咱们已经应用了一个具备样式的 DOM 树,而后开始构建一个用于可视化目的的树了。这棵树出如今全部现代引擎中,被称为盒子树(box tree)。为了构造这棵树,咱们遍历 DOM 树并建立零个或多个 CSS 盒子,每一个盒子都有一个 margin
、border
、padding
和 content
。
在本节中,咱们将讨论如下 CSS 布局概念:
display
元素的值来调用。一些最多见的格式化上下文是块(块格式化上下文或BFC),flex,grid,table-cells 和 inline。其余一些 CSS 也能够强制使用新的格式化上下文,例如 position: absolute
,float
或使用 multi-colum
。请记住,在计算阶段,维度值能够是三个值之一:auto、百分数或像素。布局的目的是在Box Tree中调整全部盒子的大小和位置,使它们为绘制作好准备。
下面示例能够更容易地理解Box Tree是如何构建的。为了便于理解,这里不显示单独的 CSS 框,只显示主盒(principal box)。让咱们看看一个基本的 “Hello world” 布局使用如下代码:
<body> <p>Hello world</p> <style> body { width: 50px; } </style> </body>
浏览器从 body 元素开始,生成它的主盒(principal box),它的宽度为50px
,默认高度为auto
。
如今移动到 p
标签并生成其主盒(principal box),而且因为 p
标签默认有边距(margin),这将影响正文的高度,以下所示:
如今浏览器移动到 “Hello world” 文本,这是 DOM 中的文本节点。所以,咱们在布局中生成一个 行内盒(line box) 。请注意,文本溢出了正文,咱们将在下一步处理这个问题。
由于加上“world”长度后实际长度比较设置大而且咱们没有设置 overflow
属性,因此引擎会向其父级报告它在布局文本时中止的位置。
因为父级已收到其子级没法完成全部内容布局的指令,所以它会克隆包含全部样式的 行内盒(line box),并传递该框的信息以完成布局。
布局完成后,浏览器会返回 box tree
,解析还没有解决的全部基于 auto
或基于百分比的值。 在图中,能够看到正文和段落如今包含全部 “Hello world”,由于它的 height 设置为 auto
。
代码部署后可能存在的 BUG 无法实时知道,过后为了解决这些 BUG,花了大量的时间进行 log 调试,这边顺便给你们推荐一个好用的 BUG 监控工具 Fundebug。
如今让布局变得更复杂一点。咱们将使用一个普通布局,其中有一个按钮,内容为 “Share It”,并将其浮动到一段文本的左侧。浮动自己被认为是**“shrink-to-fit”** 上下文。之因此将其称为“shrink-to-fit”,是由于若是尺寸是自动的,则该框将围绕其内容进行收缩。
浮动盒子是与这种布局类型匹配的盒子的一种类型,可是还有许多其余的盒子,例如绝对定位盒子(包括 position: fixed
)和基于自动调整大小的表格单元格,以下代码:
<article> <button>SHARE IT</button> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pellentesq </p> </article> <style> article { min-width: 400px; max-width: 800px; background: rgb(191, 191, 191); padding: 5px; } button { float: left; background: rgb(210, 32, 79); padding: 3px 10px; border: 2px solid black; margin: 5px; } p { margin: 0; } </style>
该过程开始时遵循与“Hello world”示例相同的模式,所以我将跳到咱们开始处理浮动按钮的位置。
因为浮动建立了一个新的块格式化上下文(BFC),而且是一个 shrink-to-fit
上下文,所以浏览器执行一种称为内容度量的特定布局类型。
在这种模式下,它看起来与其余布局相同,但有一个重要的区别,即它是在无限空间中完成的。在此阶段,浏览器所作的就是以 BFC 的最大和最小宽度布局 BFC 树。
在本例中,它使用文本布局一个按钮,所以其最窄的大小(包括全部其余 CSS 框)将是最长单词的大小。在最宽的地方,它将是一行的全部文本,加上 CSS Box。注意:这里按钮的颜色不是文字的颜色。这只是为了说明问题。
如今咱们知道最小宽度是 86px,最大宽度是 115px,咱们将此信息传递回父类的 box,让它决定宽度并适当地放置按钮。在这个场景中,有足够的空间来适应浮动的最大大小,这就是按钮的布局方式。
为了确保浏览器遵循标准,而且内容围绕浮动,浏览器更改了 article 的 BFC 的几何形状。这个几何图形被传递给段落,以便在段落布局期间使用。
从这里开始,浏览器遵循与第一个示例相同的布局过程——可是它确保任何内联内容的内联和块的起始位置都位于浮动所占用的约束空间以外。
当浏览器继续沿着树向下移动并克隆节点时,它将越过约束空间的块位置。这容许最后一行文本(以及它以前的一行)之内联方向开始于 content box 的开头。而后浏览器返回到树中,根据须要解析 auto 和百分数。
关于布局如何工做的最后一个方面是碎片化。 若是你曾经打印过网页或使用过 CSS 多列,那么你已经利用了碎片。 碎片化是将内容分开以使其适合不一样几何形状的逻辑。 让咱们来看看同一个例子,利用 CSS 多列状况:
<body> <div> <p> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras nibh orci, tincidunt eget enim et, pellentesque condimentum risus. Aenean sollicitudin risus velit, quis tempor leo malesuada vel. Donec consequat aliquet mauris. Vestibulum ante ipsum primis in faucibus </p> </div> <style> body { columns: 2; column-fill: auto; height: 300px; } </style> </body>
一旦浏览器到达 multicol 格式化上下文盒子,它就会看到它有一组设定的列。
它遵循之前相似的克隆模型,并建立了一个具备正确维度的碎片处理程序,以知足做者对其列的要求。
而后浏览器按照与以前相同的模式尽量多地布局行,而后浏览器建立另外一个碎片管理器,并继续完成布局。
来回顾一下咱们如今的状况,咱们取出全部的 CSS 内容,对其进行解析,将其级联到 DOM 树中,并完成布局。可是咱们尚未对布图应用颜色、边框、阴影和相似的设计处理——处理这些过程被称为绘画。
绘画基本上是由 CSS 标准化的,简单地说,你能够按照如下顺序绘画:
更多绘画的顺序可查看 CSS 2.2 Appendix E。
所以,若是咱们从前面的“SHARE IT”按钮开始,并遵循这个过程,它绘制过程大体以下:
完成后,它将转换为位图,最终每一个布局元素(甚至文本)都成为引擎中的图像。
如今,咱们大多数的网站都不是由单一的元素组成的。此外,咱们常常但愿某些元素出如今其余元素之上。为了实现这一点,咱们能够利用 z-index
的特性将一个元素叠加到另外一个元素上。
这可能感受就像咱们在设计软件中使用图层同样,可是惟一存在的图层是在浏览器的合成器中。看起来好像咱们在使用 z-index
建立新层,但实际上并非这样,那么究竟是怎么样呢?
咱们要作的是建立一个新的堆栈上下文。建立一个新的堆叠上下文能够有效地改变你绘制元素的顺序。让咱们来看一个例子:
<body> <div id="one"> Item 1 </div> <div id="two"> Item 2 </div> <style> body { background: lightgray; } div { width: 300px; height: 300px; position: absolute; background: white; z-index: 2; } #two { background: green; z-index: 1; } </style> </body>
若是没有使用 z-index
,上面的文档将按照文档顺序绘制,这将把 “Item 2” 置于 “Item 1” 之上。但因为 z-index
的影响,绘画顺序发生了变化。让咱们逐步完成每一个阶段,相似于咱们以前完成布局的方式。
浏览器以根框开头,咱们在后台画画。
而后浏览器按照文档顺序遍历较低层次的堆栈上下文(在本例中是“Item 2”),并开始按照上面的规则绘制该元素。
而后它遍历到下一个最高的堆栈上下文(在本例中是“Item 1”),并按照 CSS 2.2 中定义的顺序绘制它。
z-index
不影响颜色,只影响哪些元素对用户可见,所以也不影响哪些文本和颜色可见。
在这个阶段,咱们至少有一个位图从绘画传递到合成。合成程序的工做是建立一个或多个层,并将位图呈现到屏幕上供最终用户查看。
此时一个合理的问题是,“为何任何站点都须要不止一个位图或合成层?”,根据咱们目前看到的例子,咱们真的不会这么作。咱们来看一个稍微复杂一点的例子。假设在一个假设的世界中,Office 团队想让 Clippy 从新上线,他们想经过 CS S 转换让 Clippy 跳动来吸引人们对他的注意。
动画 Clippy 的代码能够是这样的:
<div class="clippy"></div> <style> .clippy { width: 100px; height: 100px; animation: pulse 1s infinite; background: url(clippy.svg); } @keyframes pulse { from { transform: scale(1, 1); } to { transform: scale(2, 2); } } </style>
当浏览器读取 web 开发人员但愿在无限循环中为 Clippy 添加动画时,它有两个选项:
在大多数状况下,浏览器将选择选项 2 并生成如下内容(我有意简化了 Word Online 为此示例生成的图层数量):
而后,它将从新组合剪辑位图在正确的位置,并处理脉动动画。这对于性能来讲是一个很好的优点,由于在许多引擎中,合成程序是在它本身的线程上的,这样就能够解除主线程的阻塞。若是浏览器选择上面的选项 1,它将不得不阻塞每一帧以完成相同的结果,这将对最终用户的性能和响应能力产生负面影响。
正如咱们刚刚了解到的,咱们使用了全部的样式和 DOM,并生成了一个呈现给最终用户的图像。那么浏览器如何建立交互性的假象呢?嗯,我相信你如今已经学过了,因此让咱们看一个例子,用咱们的 “SHARE IT” 按钮做为类比:
button { float: left; background: rgb(210, 32, 79); padding: 3px 10px; border: 2px solid black; } button:hover { background: teal; color: black; }
咱们在这里添加的是一个伪类,它告诉浏览器在用户悬停在按钮上时更改按钮的背景和文本颜色。这就引出了一个问题,浏览器如何处理这个问题?
浏览器不断跟踪各类输入,当这些输入正在移动时,它会经历称为命中测试的过程。 对于此示例,该过程以下所示:
:hover
在声明块内部有一个仅使用绘制样式调整的伪类。但愿这部分对你关于 css 解析过程多多少少有点帮助,共进步!
Fundebug专一于JavaScript、微信小程序、微信小游戏、支付宝小程序、React Native、Node.js和Java线上应用实时BUG监控。 自从2016年双十一正式上线,Fundebug累计处理了10亿+错误事件,付费客户有Google、360、金山软件、百姓网等众多品牌企业。欢迎你们免费试用!