简介

        本文介绍Scss的@extend的用法。

@extend的作用

        在设计网页的时候常常遇到这种情况:一个元素使用的样式与另一个元素完全相同,但又添加了额外的样式。通常会在 HTML 中给元素定义两个 class,一个通用样式,一个特殊样式。假设现在要设计一个普通错误样式与一个严重错误样式,一般会这样写:

<div class="error seriousError">
Oh no! You've been hacked!
</div>

样式如下

.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
border-width: 3px;
}

        麻烦的是,这样做必须时刻记住使用 .seriousError 时需要参考 .error 的样式,带来了很多不变:智能比如加重维护负担,导致 bug,或者给 HTML 添加无语意的样式。使用 @extend 可以避免上述情况,告诉 Sass 将一个选择器下的所有样式继承给另一个选择器。

.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
@extend .error;
border-width: 3px;
}

        上面代码的意思是将 .error 下的所有样式继承给 .seriousError,border-width: 3px; 是单独给 .seriousError 设定特殊样式,这样,使用 .seriousError 的地方可以不再使用 .error。

        其他使用到 .error 的样式也会同样继承给 .seriousError,例如,另一个样式 .error.intrusion 使用了 hacked.png 做背景,<div class="seriousError intrusion"> 也同样会使用 hacked.png 背景。

.error.intrusion {
background-image: url("/image/hacked.png");
}

原理概述

        @extend 的作用是将重复使用的样式 (.error) 延伸 (extend) 给需要包含这个样式的特殊样式(.seriousError),刚刚的例子:

Scss:

.error {
border: 1px #f00;
background-color: #fdd;
}
.error.intrusion {
background-image: url("/image/hacked.png");
}
.seriousError {
@extend .error;
border-width: 3px;
}

编译后的CSS:

.error, .seriousError {
border: 1px #f00;
background-color: #fdd;
}

.error.intrusion, .intrusion.seriousError {
background-image: url("/image/hacked.png");
}

.seriousError {
border-width: 3px;
}

        合并选择器时,@extend 会很聪明地避免无谓的重复,.seriousError.seriousError 将编译为 .seriousError,不能匹配任何元素的选择器(比如 #main#footer )也会删除。

复杂的选择器

        Class 选择器并不是唯一可以被延伸 (extend) 的,Sass 允许延伸任何定义给单个元素的选择器,比如 .special.cool,a:hover 或者 a.user[href^="http://"] 等。

简单示例

Scss

a:hover {
text-decoration: underline;
}

.hoverlink {
@extend a:hover;
}

编译为

a:hover, .hoverlink {
text-decoration: underline;
}

继承所有样式

        与上面 .error.intrusion 的例子一样,所有 a:hover 的样式将继承给 .hoverlink,包括其他使用到 a:hover 的样式。

Scss

.comment a.user:hover {
font-weight: bold;
}

.hoverlink {
@extend a:hover;
}

编译为

.comment a.user:hover, .comment .user.hoverlink {
font-weight: bold;
}

多重继承

说明

        同一个选择器可以延伸给多个选择器,它所包含的属性将继承给所有被延伸的选择器。

示例

Scss

.error {
border: 1px #f00;
background-color: #fdd;
}
.attention {
font-size: 3em;
background-color: #ff0;
}
.seriousError {
@extend .error;
@extend .attention;
border-width: 3px;
}

编译后的CSS:

.error, .seriousError {
border: 1px #f00;
background-color: #fdd;
}

.attention, .seriousError {
font-size: 3em;
background-color: #ff0;
}

.seriousError {
border-width: 3px;
}

        每个 .seriousError 将包含 .error 与 .attention 下的所有样式,这时,后定义的样式享有优先权:.seriousError 的背景颜色是 #ff0 而不是 #fdd,因为 .attention 在 .error 之后定义。

        多重延伸可以使用逗号分隔选择器名,比如 @extend .error, .attention; 与 @extend .error; @extend.attention 有相同的效果。

链式继承

说明

        当一个选择器继承给第二个后,可以继续将第二个选择器继承给第三个。

示例

SCSS

.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
@extend .error;
border-width: 3px;
}
.criticalError {
@extend .seriousError;
position: fixed;
top: 10%;
bottom: 10%;
left: 10%;
right: 10%;
}

        现在,每个 .seriousError 选择器将包含 .error 的样式,而 .criticalError 不仅包含 .seriousError 的样式也会同时包含 .error 的所有样式 

编译后的CSS:

.error, .seriousError, .criticalError {
border: 1px #f00;
background-color: #fdd;
}

.seriousError, .criticalError {
border-width: 3px;
}

.criticalError {
position: fixed;
top: 10%;
bottom: 10%;
left: 10%;
right: 10%;
}

选择器列

说明

        暂时不可以将选择器列 (Selector Sequences),比如 .foo .bar 或 .foo + .bar,延伸给其他元素,但是,却可以将其他元素延伸给选择器列。

示例

Scss

#fake-links .link {
@extend a;
}

a {
color: blue;
&:hover {
text-decoration: underline;
}
}

编译后的CSS:

a, #fake-links .link {
color: blue;
}

a:hover, #fake-links .link:hover {
text-decoration: underline;
}

合并选择器列

需要合并的场景

        有时会遇到复杂的情况,比如选择器列中的某个元素需要延伸给另一个选择器列,这种情况下,两个选择器列需要合并,比如:

#admin .tabbar a {
font-weight: bold;
}
#demo .overview .fakelink {
@extend a;
}

        技术上讲能够生成所有匹配条件的结果,但是这样生成的样式表太复杂了,上面这个简单的例子就可能有 10 种结果。所以,Sass 只会编译输出有用的选择器。

示例1:不包含相同的选择器

        当两个列 (sequence) 合并时,如果没有包含相同的选择器,将生成两个新选择器:第一列出现在第二列之前,或者第二列出现在第一列之前。

Scss

#admin .tabbar a {
font-weight: bold;
}
#demo .overview .fakelink {
@extend a;
}

编译后的CSS:

#admin .tabbar a,
#admin .tabbar #demo .overview .fakelink,
#demo .overview #admin .tabbar .fakelink {
font-weight: bold;
}

示例2:包含相同的选择器

        如果两个列 (sequence) 包含了相同的选择器,相同部分将会合并在一起,其他部分交替输出。在下面的例子里,两个列都包含 #admin,输出结果中它们合并在了一起。

SCSS:

#admin .tabbar a {
font-weight: bold;
}
#admin .overview .fakelink {
@extend a;
}

编译后的CSS:

#admin .tabbar a, 
#admin .tabbar .overview .fakelink,
#admin .overview .tabbar .fakelink {
font-weight: bold;
}

@extend-Only 选择器

说明

        有时,需要定义一套样式并不是给某个元素用,而是只通过 @extend 指令使用,尤其是在制作 Sass 样式库的时候,希望 Sass 能够忽略用不到的样式。

        如果使用普通的 CSS 规则,最后会编译出很多用不到的样式,也容易与其他样式名冲突,所以,Sass 引入了“占位符选择器” (placeholder selectors),看起来很像普通的 id 或 class 选择器,只是 # 或 . 被替换成了 %。可以像 class 或者 id 选择器那样使用,当它们单独使用时,不会被编译到 CSS 文件中。

        占位符选择器需要通过延伸指令使用,用法与 class 或者 id 选择器一样,被延伸后,占位符选择器本身不会被编译。

示例

SCSS:

// This ruleset won't be rendered on its own.
#context a%extreme {
color: blue;
font-weight: bold;
font-size: 2em;
}
.notice {
@extend %extreme;
}

编译后的CSS: 

#context a.notice {
color: blue;
font-weight: bold;
font-size: 2em;
}

!optional 声明

        如果 @extend 失败会收到错误提示。比如: a.important {@extend .notice},当没有 .notice 选择器时将会报错,只有 h1.notice 包含 .notice 时也会报错,因为 h1 与 a 冲突,会生成新的选择器。

        如果要求 @extend 不生成新选择器,可以通过 !optional 声明达到这个目的,例如:

a.important {
@extend .notice !optional;
}

在指令中继承

        在指令中使用 @extend 时(比如在 @media 中)有一些限制:Sass 不可以将 @media 层外的 CSS 规则继承给指令层内的 CSS,这样会生成大量的无用代码。也就是说,如果在 @media (或者其他 CSS 指令)中使用 @extend,必须继承给相同指令层中的选择器。

正确用法

@media print {
.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
@extend .error;
border-width: 3px;
}
}

错误用法

.error {
border: 1px #f00;
background-color: #fdd;
}

@media print {
.seriousError {
// INVALID EXTEND: .error is used outside of the "@media print" directive
@extend .error;
border-width: 3px;
}
}

希望有一天,浏览器可以原生支持 @extend 指令,这样就可以在任何指令中使用延伸功能,不再受限制了。