管理CSS层叠

了解CSS的同学都应该知道,CSS是由Cascading Style Sheets三个单词首字母组成,简单的讲就是层叠样式表。事实上,在CSS中,层叠(也有同学称之为级联)也是一个非常基础,但也是一个非常重要的概念。只有理解了层叠这个概念才能更好的理解和使用CSS中的每一个属性。话又说回来,既然是一个基础概念,懂CSS的同学都知道,那还有必要来说这个吗?那我就得问一下了,你真的懂CSS中的层叠,能使用好CSS的层叠吗?如果你没有这方面的自信,那不仿花点时间来阅读这篇文章。

管理CSS层叠的困惑

有些时候CSS层叠成为很多开发者(特别是不太熟悉CSS的开发人员)的眼中钉。为什么这么讲呢?在CSS中有些属性是有继承关系的(之前在当元素元素的祖先元素设置了样式),当我们想要恢复属性的默认值的时候,就必须记住该属性的默认值是什么?可想而之,如果不熟悉其默认值,又想覆盖继承过来的样式,那将是多么一件痛苦的事情。

所幸的是,CSS提供了一些机制来处理继承。

理论上讲,任何CSS属性都具有除none之外的四个值:initialinhertunsetrevert。这些值就是CSS用来处理继承的机制。

虽然这些值有些还没有普遍得到浏览器的支持,为了更好的管理CSS层叠,或者说处理好CSS的继承,我们还是很有必要的对他们进行了解。

回顾CSS的语法

CSS的语法其实非常的简单,在CSS样式表中,我们都像下面这样使用CSS:

比如:

html {
    font-size: small;
}

使用规则是非常的简单,问题还是前面说的,有些属性是会被继承的,比如上面代码中的font-size,它就是一个会被继承的属性,如果你的代码中在html元素设置了font-size:small;属性,那么html元素的所有后代元素都将被继承这个属性,比如下图中蓝框中显示的一样:

上图蓝框中告诉我们body元素继承了html元素中的font-size:small;。开发者工具中会提示我们“Inherited from html”。那么问题来了,在CSS中哪些属性是会被继承的呢?其实在W3C规范中各个属性的描述已清很清楚的告诉我们了。比如说border属性,在描述其语法时,在列表中有一个Inherited描述项的值为no。这也就告诉我们border属性是不能被继承的。反之,再来看一个font-size属性,语法描述的列表中同样有一个Inherited描述项,只不过它的值不是no,而是yes,也就是说font-size属性是会被继承的。这决定了当你没有为元素的属性指定值时该如何计算值。

如果你平时阅读规范仔细的话,你不难发现,在介绍每个属性的语法参数的时候,都会有一个Initial参数,该参数主要来指定每个属性的初始值。CSS属性已经给出的初始值针对不同的继承和非继承属性有关不同的含义:

  • 对于继承属性,初始值只能被用于没有指定值的根元素上
  • 对于非继承属性,初始值可以被用于任意没有指定值的元素上

到目前为止,我们引出两个概念:初始值继承值,除了这两个概念之外,在CSS属性中还有一个计算值(Computed),该值由指定的值计算而来:

  • 处理特殊的值inheritinitial
  • 根据属性的摘要中关于计算值描述的方法计算出值

计算属性的计算值通常包括将相对值转换成绝对值,比如em%这样的单位。比如,有一个元素的属性值:

font-size: 16px;
padding-top: 2em;

其中padding-top:2em;就是一个计算值,其计算出来的值将根据font-size做为基数计算(在此示例当中),在此计算出来的值是32px

然而,有些属性的百分比值会转换成百分比的计算值(这些元素的百分比相对于需要布局后才能知道的值,比如widthmargin-righttext-indenttop等)。另外,line-height属性值如果没有单位的数字,则该值就是计算值。

对于CSS的计算值,在不同的浏览器中其计算出来的值有时候会稍有偏差。

如果你感兴趣的话,可以打开浏览器的开发者工具,查看对应的计算值(比如,Chrome开发者工具,有一个Computed选项,该选项展示的就是对应的CSS计算值),如下图所示:

其中计算值的最主要用处是继承,包括inherit关键词。

最后总结两点:

  • 当元素的一个继承属性没有指定值时,则取父元素的同属性的计算值,只有文档根元素取该属性的概术中给定的初始值
  • 当元素的一个非继承属性没有指定值时,则取属性的初始值

看到这里,或许你知道了什么叫继承和非继承,以及他们取值方式。但你可能还在纠结,在CSS中到底哪些属性是继承属性,哪些不是继承属性?其实这个问题我也没办法准确的回答您,因为我也没有做过这方面的统计。不过我可以告诉大家两个小经验:

  • 在CSS中一些关于字体、文本和颜色等属性都是可继承属性
  • 在CSS中一些跟布局和盒子模型的属性都是非继承属性

如果你想准确的知道答案,可以通过这里整理的属性表格进行统计。只要Inherited选项是Yes的都表示是继承属性,否则都是非继承属性。

处理CSS继承的机制

在文章开头也提到过,到今天为止,在CSS中提供了处理CSS继承的机制,简单的讲就是CSS提供了几个新属性,可以用来处理属性的继承。这几个属性就是initialinheritunsetrevert。其实初了这四个属性之外,还有一个all属性。虽然这几个属性主要是用来帮助大家处理CSS属性继承的,但他们之间的使用,还是有一定的差异化的。接下来我们一起来看看这几个属性的实际使用以及对应的差异化:

initial

在CSS中,每个属性都具有一个初始值,其实也就是CSS属性的默认值。在CSS规范中,都对每个属性的初始值做出了相关的定义。比如text-align的初始值是leftdisplay的初始值是inline

而这里,我们要说的是CSS的关键词initial

If the cascaded value is the initial keyword, the property’s initial value becomes its specified value.

如果你在元素样式的设置中显示的设置某个属性的值为initial时,其实就表示设置了该属性的默认值。这一点可能理解起来有点蛋疼,我们来看一个小示例。

假设我们有一个<p>元素,从所周知,<p>元素是一个块元素,为了好看,咱们添加一点修饰的样式代码:

p {
    background: #f36;
    padding: 2rem;
    font-size: 2rem;
    color: #fff;
}

看到的效果将是这样:

如果我们希望p元素变成行内元素时,按照我们以前的处理方式,需要手动处理浏览器默认样式(User Agent Stylesheet),也就是显示的重置:

p{
    dispaly: inline;
}

前面提到过inlinedisplay的初始值(也就是默认值),而在规范中也提到过:你在元素样式的设置中显示的设置某个属性的值为initial时,其实就表示设置了该属性的默认值。言外之意,我是不是可以直接使用initial呢?尝试一下:

p {
    display: initial;
}

这个时候得到的效果其实和使用display:inline是一样的:

接下来,我们再来看一个继承属性color。比如:

p {
    background: #ded;
    padding: 2rem;
    font-size: 2rem;
    color: #F36;
    margin-bottom: 3rem;
}

因为color是一个继承属性,所以结构中的strong也将继承p元素中设置的color:#f36;的颜色。如果你在strong中设置color: initial时,那么strong的颜色将重置为默认值。由于我们没有设置默认的color颜色,那么这个时候,浏览器将会把一个计算值赋予成color的初值始:

看到的效果就如下:

initial的兼容性还是不错的,得到了大多数主流浏览器的支持:

Inherit

CSS还添加了一个inherit关键词属性值,用来强制继承父元素的某个属性的值。前面也说过,CSS中有些属性自动就是可继承属性,比如font-sizecolor之类,但也有很多属性又是非继承属性,比如borderborder-radius之类的。在这里,如果在非常继承的属性上显示的设置了inherit关键词,表示该元素将继承父元素指定的属性值或者计算值。

为了同样的能更好理解inherit,来看一个示例,在这个示你中,我们用border来做例子:

<div class="wrapper" style="border:5px solid blue;">
    <div>...</div>
</div>

众所周知,border是一个非继承属性,如果我们希望.wrapper中的div元素继承其父元素.wrapper中的border样式,以前的做法是,显式在div中设置一个与.wrapper一样的border样式:

<div class="wrapper" style="border:5px solid blue;">
    <div style="border: 5px solid blue;">...</div>
</div>

其实有了inherit关键词之后,一切变得是那么的简单:

<div class="wrapper" style="border:5px solid blue;">
    <div style="border: inherit;">...</div>
</div>

得到的效果将是一样的:

上面的示例是父元素设置了border样式,所以其继承了父元素的border样式。那如果将上面的示例稍做修改,在元素外套一个div,而这个div不做任何样式的设置。将又会变成一个什么样呢?直接上示例吧:

<div class="wrapper" style="border:5px solid blue;">
    <div>
        <div style="border: inherit;" class="ele">...</div>
    </div>
</div>

猜猜效果,是不是和你想的一样:

可以看到div.ele仅继承了其父元素divborder属性的计算值,并未继承其祖先元素.wrapperborder属性的设置值,通过浏览器开发者工具,可以看得一目了然:

这个示例说明:仅管元素自身显式的设置了inherit关键词,但是,如果其父元素没有明确指定样式,那么其最终效果将和revert的效果一致。即继承的是其父元素的计算值,也就是浏览器默认样式(User Agent Stylesheet)。

Revert

revert值早前被称之为default。表示没有使用任何属性值。

我们都知道,如果没有使用作者样式表(也就是Web开发人员自己写的样式表),那么浏览器将会按这样的过程去检测,元素调用的样式:

  • 浏览器首先会检测元素是否有使用作者样式表(User Defined Stylesheet)的属性
  • 如果没有找到,将会检测客户端默认样式(User Agent Stylesheet)
  • 如果没有找到客户端样式,相当于元素应用了unset

还是拿示例来说吧。比如我们一个div元素,我们并没有显示的在自己的样式表中设置其display属性的值。对于最后的渲染结果,浏览器将会使用User Agent Stylesheets的样式display:block

根据前面介绍的,就算是我们在div中显式的设置display:revert,该元素也将使用User Agent Stylesheet中的display:block样式。同样,我们在另一个div元素中使用display:initial,根据前面介绍的,那在这个div将会采用display的初始值inline。比如下面的效果:

我们再来看一个继承属性的运用场景。因为在div元素上设置了color:#fff;元素,这是用户写的样式,而且color是一个继承属性,只要是div的后代元素都将会继承color的属性值。根据前面所说revert的检测机制是,先检测用户自己写的样式,然后再检测用户代理样式,如果两都没有,才会设置unset的样式。所以最终我们看到的效果如下:(第二个设置了color:initial;)

到目前为止,该属性值仅Safari浏览器支持:

Unset

unsetinitialinherit的组合。当属性设置为unset时,如果它是一个继承属性,那么它相当于是inherit;如果它不是,则相当于initial

有一些属性,如果没有明确指定,将默认是inherit。比如,我们给元素设置一个color,那它将适用于所有默认的子元素。而其它属性,如border则默认是非继承属性。

<div class="wrapper" style="border: 5px solid blue;color: #fff;">
    <div class="ele">...</div>
</div>

此时效果如下:

示例中color属性被继承了,但border属性没有被继承。

将上面的示例代码稍作调整:

<div class="wrapper" style="border: 5px solid blue;color: #fff;">
    <div class="ele" style="border: unset; color:unset;">...</div>
</div>

div.ele元素的bordercolor都设置了unset值。也就是说它将运用initial或者inherit的值。具体取决于哪个值,这得根据属性的默认行为是什么来决定。如果默认属性是inherit,那将运用的是inherit;如果默认属性是initial,那将运用的是initial

上面的示例中,border属性采用的是initialcolor属性采用的是inherit

unset关键词得到众多浏览器的兼容:

All

在CSS中,all是一个简写属性,其重设除了unicode-bididirection之外的所有属性至它们的初始值或继承值。all有三个值:

  • initial:该关键字代表改变该元素或其父元素的所有属性至初始值。
  • inherit:该关键字代表改变该元素或其父元素的所有属性的值至他们的父元素属性的值。
  • unset:该关键字代表如果该元素的属性的值是可继承的,则改变该元素或该元素的父元素的所有属性的值为他们父元素的属性值,反之则改变为初始值。

来看示例。比如我们一个这样的一个HTML结构:

<div>...<strong>...</strong> ...</div>

给他们设置一些样式:

body {
    padding: 2vw;
}

div {
    background: #f36;
    padding: 2rem;
    font-size: 2rem;
    color: #fff;
    margin-bottom: 3rem;
}

strong {
    font-size: 3rem;
}

看到的效果如下:

这效果正是我们想要的。div显式的指定了backgroundpaddingfont-sizecolormargin-bottom属性值,而其中backgroundpaddingmargin-bottom是非继承属性,而colorfont-size是继承属性。除此之外,div还有一个客户端代理样式display:block,这个属性也是一个非继承属性。另外strong元素设置了一继承属性font-size,这个元素默认情况下继承了共父元素的color属性,同时还继承了html元素的font-familyline-height属性。当然,strong元素也有一个客户端代理样式font-weight:bold

上面看到的效果是我们平时使用的时候效果。如果这个时候,我们在divstrong同时设置all:inherit;时,得到的效果和前面的效果完全不一样:

这个时候divstrong重置了当初自己设置的属性,并且继承了各自父元素的一些属性:

  • div元素继承了body元素的paddingfont-familyline-height,同时也继承了body代理客户端的样式colorbackgrounddisplay
  • strong元素继承了div元素的样式

所以最后你看到的效果就像上图那样子。我们再把all的值设置为initial

这个时候divstrong样式都重置到了对应的初始样式,也就是对应的属性的默认样式,包括代理客户端样式也重置为对应属性的初始值。

最后来看all的值设置为unset的效果,下面的示例,我只在strong元素上设置all:unset,其效果就足以说明一切:

效果中的第一个是没设置all:unset,第二个设置了all:unset。这个时候strong元素的font-sizefont-weight继承了其父元素的font-sizefont-weight

all在CSS中有时候是一个属性,比如这里说的就是属性,但有的时候它还是CSS中某些属性的值。比如我们常在transition中用到的all,那这个时候就是属性值。到目前为止,CSS中的all属性也得到了众多浏览器的支持。

总结

这篇文章主要介绍了CSS层叠管理。到目前为止,CSS层叠管理可以通过inheritinitialunsetrevert四个值进行管理。简而言之:这些值就是CSS用来处理继承的机制。可以更好的管理CSS层叠,或者说处理好CSS的继承。

大漠

常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《图解CSS3:核心技术与案例实战》。

如需转载,烦请注明出处:http://www.w3cplus.com/css3/managing-the-css-cascade.html

返回顶部