层叠
层叠这个概念源自 CSS 的全称 --- 层叠样式表(Cascading Style Sheets),是 CSS 中最基础的概念之一。
通常页面元素的样式受多个因素的影响,作者样式表(开发者编写的 CSS 文件)、浏览器默认样式表、元素标签的属性都可以决定一个元素在页面的样式。
那么同一种样式,就可能多次应用到同一个元素上,从而发生声明冲突。而浏览器自动处理声明冲突,决定元素的某一个样式具体用哪一个声明的过程就是层叠。
层叠解决声明冲突的手段是权重计算。
权重计算从前到后主要涉及 3 个参考维度:重要性、特殊性、源次序。
发生声明冲突时,首先比较重要性,重要性指的是各冲突声明的来源,它们的重要性依次是:
- 作者样式表中的
!important
样式 - 作者样式表中的普通样式
- 浏览器默认样式表
除非万不得已,否则不推荐使用
!important
,因为其权重太高,如果未来要覆盖它,只能用另外一个!important
。长此以往,样式表中就会充满!important
,增加代码维护难度。
最终,元素会应用重要性最高的样式声明。
如果重要性一致,则继续比较各冲突声明的特殊性。
特殊性主要比较的是声明冲突样式的选择器选中的范围,选择器选择的范围越窄,则越特殊。
具体规则:通过选择器,计算出一个 4 位数(x-x-x-x
)特殊值,哪个选择器的特殊值大,哪个就越特殊,它御下的样式代码越可能在冲突中胜出。
⭐4 位数的计算
- 千位,如果是内联样式,记 1,否则记 0
- 百位,等于所有选择器中 id 选择器的数量
- 十位,等于选择器中所有类选择器、属性选择器、伪类选择器的数量
- 个位,等于选择器中所有元素选择器、伪元素选择器的数量
- 另外通配符选择器、子元素选择器、兄弟元素选择器等也具有特殊性,不过它们的特殊性极低,以至于比 4 位数的个位权重还小,但仍比没有特殊性的继承属性值权重高
特殊值的位数之间只要相差 1,差距就会很大,因为它们是 256 的进制,100 个属性选择器的权重也没 1 个 id 选择器的高。
当前面两个参考维度都相同时,则比较源次序,代码书写靠后的样式胜出。
利用层叠,我们经常做的操作就是重置浏览器默认样式,因为不同浏览器默认样式不同,可能导致网页在不同浏览器展示效果不同,而作者样式表的样式总会覆盖浏览器样式表。
常用的重置样式表:normalize.css、reset.css、meyer.css
另外一个常见的应用就是 a
标签的 “爱恨法则”,利用层叠让 a
标签的 4 种伪类选择器设置的样式都正确显示。
详情可参考博文:CSS “爱恨原则”
继承
CSS 具有继承机制,某些样式不仅会应用到指定元素,还会应用到它的后代元素。
这个机制是严格单向的,也就是说某个元素的某个 CSS 属性值绝不会向上传播。
具体某个 CSS 属性能不能被继承,需要查阅 MDN 文档。
不过有一些经验性总结,可以帮助记忆,通常:
- 大多数盒模型属性都不能继承,如:
margin
、padding
、border
、background
、...- 原因是可以想象的,如果这些属性能继承,那文档将会变得很混乱
- 大多数与文字内容相关的属性都能继承,如:
color
、font-size
、font-family
、...
如果某个 CSS 属性是可继承的,且在某一元素上声明了该值,那么该值就会沿着元素树一直向下传递,直到没有更多的后代元素来继承这个值。
基于此,且由于大多数情况下同一个页面字体和文字风格相同,开发者会在 html
或 body
标签统一声明页面文字相关的 CSS 属性。
在谷歌浏览器调试工具中可以清楚的知道哪些样式是继承的,还有继承于谁。
带有 Inherited from xxx 标题的模块描述的便是继承属性。
亮色的属性代表的是继承值应用成功的属性,暗色代表的是没有继承成功的祖先元素声明的其他属性。
另外重要的一点是,继承的值没有特殊性,不参与权重计算。
所以当期望的继承效果没有展现时,可以从以下几个方面排查:
- CSS 属性本身是否具备可继承性
- 是否针对元素该属性已经有了作者样式表声明,继而导致层叠机制掩盖了继承机制
- 是否针对元素该属性已经有了浏览器默认样式表声明,继而导致层叠机制掩盖了继承机制
既然层叠会从多方面影响继承,那么继承岂不是很不好管理?
于是,CSS 为控制继承提供了五个特殊的通用属性值(所有 CSS 属性都可以接收的值)。
inherit
:开启继承,设置该值会使子元素属性值和父元素相同【不管该属性是否具有可继承性】initial
:设置属性值为默认值revert
、revert-layer
、unset
平时基本很少用,而且用处也不大
至于为什么层叠会掩盖继承,请参见接下来的 CSS 属性值计算篇章。
CSS 属性值计算
页面渲染是按照 html 文档的树形结构顺序,一个元素一个元素依次渲染的,而渲染每个元素的前提条件就是:该元素的所有 CSS 属性都必须有值。
从常识出发都可以感觉到,如果不是所有样式都有确切的值,那浏览器如何对其具体绘制。
一个元素从所有 CSS 属性都没值,到所有属性都有值的过程就是 CSS 属性值计算过程。
计算过程一共有 4 步,层叠和继承都是其中的步骤之一,单靠某一个机制并不能最终决定 CSS 属性的具体值。
- ① 确定声明值,这一步会把样式表(包括:作者样式表、浏览器默认样式表)中没有冲突的声明作为 CSS 属性值。
这一步后,但凡声明过且没有声明冲突的 CSS 属性都有了具体值,不会再受后面步骤影响。
- ② 层叠冲突,对样式表有冲突的声明使用层叠规则,确定其 css 属性值。
这一步后,有声明冲突的 CSS 属性也有了具体值,至此,样式表上书写过的 CSS 属性已确定完成。
- ③ 继承,经过前面两步,对仍然没有值的属性,若可以继承,则继承父元素的值。
这一步针对的是一个元素所有的,没有在样式表中声明的,且有可继承性的 CSS 属性。
因为这一步在层叠之后,所以层叠才能掩盖继承。
- ④ 经过前三步,对仍然没有值的属性,使用默认值。
了解了这个过程后,我们知道 CSS 样式不是单纯地靠解决声明冲突来确定的,许多现象也都能理解了,比如经典的:a
元素无法继承父元素颜色的问题。
普通情况下,a
元素无法继承父级元素的颜色值。
这是由于 a
元素在浏览器的默认样式表里设置有颜色值。根据属性值计算过程,第三步继承,是在前两步属性没有获得属性值的情况下才生效。
而 a
元素在第一步确定声明值的时候,color
属性就已经从浏览器默认样式表获得 color
属性值了,自然不会走第三步。
解决思路:浏览器默认样式表不能改变,a
元素 color
属性的确定只能是在第 ① 步,于是只能在作者样式表显示声明这个属性。
解决:用 a { color: inherit; }
强制其继承父元素的值。
写在最后
One day you'll leave this world behind.So live a life you will remember! --- Avicii
我是暮星,一枚有志于在前端领域证道的攻城狮。
优质前端内容持续输出中......,欢迎点赞 + 关注 + 收藏。