Sass基础教程-2 At-rule 流程控制

2021-05-05
css
约 8357 字 预计阅读 17 分钟

Sass 是一种 CSS 的预编译语言,对css进行了扩展,提供了一系列内置的机制,方便以编程的方式编写css,大大提升了css的复用性和灵活性.

本章介绍At-rules,流程控制,操作符

1. At-Rules

sass里基本的At-rules如下

  • @use loads mixins, functions, and variables from other Sass stylesheets, and combines CSS from multiple stylesheets together.
  • @forward loads a Sass stylesheet and makes its mixins, functions, and variables available when your stylesheet is loaded with the @use rule.
  • @import extends the CSS at-rule to load styles, mixins, functions, and variables from other stylesheets.
  • @mixin and @include makes it easy to re-use chunks of styles.
  • @function defines custom functions that can be used in SassScript expressions.
  • @extend allows selectors to inherit styles from one another.
  • @at-root puts styles within it at the root of the CSS document.
  • @error causes compilation to fail with an error message.
  • @warn prints a warning without stopping compilation entirely.
  • @debug prints a message for debugging purposes.

1.1. @use

1.1.1. @use和@import的区别

重要,提到模块化(module)就是指@use

@import并不是真正意义的模块化

参考:

  • css - What’s the difference between @import and @use SCSS rules? - Stack Overflow

  • Sass: @use (sass-lang.com)

  • Sass: @import (sass-lang.com)

  • Introducing Sass Modules | CSS-Tricks

  • @use有命名空间,不怕变量被污染,当然使用的时候也稍微麻烦点,要带上scope,@import的变量如果和本地文件的变量重名会被覆盖,所以使用import的时候一般会推荐使用长一点的变量名

  • @use不会污染其它文件,一个文件里的use相对于整个项目里的其它文件是不可见的,比如在一个文件里@use button,这时候其它文件并不能使用这个button里的成员,而@import A一次之后,其它被import的文件也可以使用A里的变量

  • 所有-, _开头的variables, mixins, functions被认为是私有成员,不能用@use引入

  • Similarly, @extends will only apply up the chain; extending selectors in imported files, but not extending files that import this one.(这个稍后翻译)

1.1.2. 读取成员Loading Members

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// src/_corners.scss
$radius: 3px;

@mixin rounded {
  border-radius: $radius;
}
// style.scss
@use "src/corners";

.button {
  @include corners.rounded;
  padding: 5px + corners.$radius;
}
1.1.2.1. 名称空间namespace

指定名称空间namespace, 在use的时候加上as

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// src/_corners.scss
$radius: 3px;

@mixin rounded {
  border-radius: $radius;
}
// style.scss
@use "src/corners" as c;

.button {
  @include c.rounded;
  padding: 5px + c.$radius;
}

也可以用*来导入到本地空间里

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// src/_corners.scss
$radius: 3px;

@mixin rounded {
  border-radius: $radius;
}
// style.scss
@use "src/corners" as *;

.button {
  @include rounded;
  padding: 5px + $radius;
}
1.1.2.2. 私有成员
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// src/_corners.scss
$-radius: 3px;

@mixin rounded {
  border-radius: $-radius;
}
// style.scss
@use "src/corners";

.button {
  @include corners.rounded;

  // This is an error! $-radius isn't visible outside of `_corners.scss`.
  padding: 5px + corners.$-radius;
}

1.1.3. 重写默认值以及with的用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// _library.scss
$black: #000 !default;
$border-radius: 0.25rem !default;
$box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default;

code {
  border-radius: $border-radius;
  box-shadow: $box-shadow;
}
// style.scss
@use 'library' with (
  $black: #222,
  $border-radius: 0.1rem
);

输出

1
2
3
4
code {
  border-radius: 0.1rem;
  box-shadow: 0 0.5rem 1rem rgba(34, 34, 34, 0.15);
}

1.1.4. 和mixins一起使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// _library.scss
$-black: #000;
$-border-radius: 0.25rem;
$-box-shadow: null;

/// If the user has configured `$-box-shadow`, returns their configured value.
/// Otherwise returns a value derived from `$-black`.
@function -box-shadow() {
  @return $-box-shadow or (0 0.5rem 1rem rgba($-black, 0.15));
}

@mixin configure($black: null, $border-radius: null, $box-shadow: null) {
  @if $black {
    $-black: $black !global;
  }
  @if $border-radius {
    $-border-radius: $border-radius !global;
  }
  @if $box-shadow {
    $-box-shadow: $box-shadow !global;
  }
}

@mixin styles {
  code {
    border-radius: $-border-radius;
    box-shadow: -box-shadow();
  }
}


// style.scss
@use 'library';

@include library.configure(
  $black: #222,
  $border-radius: 0.1rem
);

@include library.styles;

1.1.5. 模块搜索

模块的加载总是在当前文件加载之前

1.1.5.1. 搜索路径

如果要@use moduleA, 那么搜索路径为node_modules/moduleA/sass/moduleA.scss

在sass里相对路径总是可用的,不需要额外的写./

1.1.5.2. 模块的拆分

sass里只编一个文件,其它文件都是模块, 为了标识模块,都以_开头,但是在引入的时候不需要加入_

比如有下列模块解构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
│  main.scss
│  postcss.config.js
│  tailwind.config.js
│  tailwind.scss
│
├─common
│      _global.scss
│      _variables.scss
│
├─components
│      _code.scss
│      _markdown.scss
│
├─pages
│      _index.scss
│
├─partial
│      _header.scss
│
└─vendor
        _animation.scss
        _normalize.scss
        _utils.scss

引入的时候可以这样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@import "vendor/utils";
@import "vendor/animation";

@import "common/variables";
@import "common/global";

@import "components/code";
@import "components/markdown";

@import "partial/header";

@import "pages/index"

也可以建立一个_index来索引整个子目录,示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// foundation/_code.scss
code {
  padding: .25em;
  line-height: 0;
}

// foundation/_lists.scss
ul, ol {
  text-align: left;

  & & {
    padding: {
      bottom: 0;
      left: 0;
    }
  }
}

// foundation/_index.scss
@use 'code';
@use 'lists';

// style.scss
@use 'foundation';

可以看到,这样在外层引用的时候,省略了路径和子文件,更加简单,缺点就是不方便插拔子文件

1.1.6. 读取css

和scss一样

1
2
3
4
5
6
7
// code.css
code {
  padding: .25em;
  line-height: 0;
}
// style.scss
@use 'code';

1.2. @forward

我的理解是, @use不能跨文件, 所以每个文件如果复用很麻烦, 但是用@import会污染scope,于是折中的方案就是,利用@forward来进行组织.

所以@forward@import的一个附带作用域的版本

另外要注意的是,@forward和@use同时出现,@forward要放在前面

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// src/_list.scss
@mixin list-reset {
  margin: 0;
  padding: 0;
  list-style: none;
}


// bootstrap.scss
@forward "src/list";


// styles.scss
@use "bootstrap";

li {
  @include bootstrap.list-reset;
}

1.2.1. 同样可以使用as来定义前缀

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// src/_list.scss
@mixin reset {
  margin: 0;
  padding: 0;
  list-style: none;
}
// bootstrap.scss
@forward "src/list" as list-*;
// styles.scss
@use "bootstrap";

li {
  @include bootstrap.list-reset;
}

1.2.2. 可见性

利用hide,可以对部分进行屏蔽

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/_list.scss
$horizontal-list-gap: 2em;

@mixin list-reset {
  margin: 0;
  padding: 0;
  list-style: none;
}

@mixin list-horizontal {
  @include reset;

  li {
    display: inline-block;
    margin: {
      left: -2px;
      right: $horizontal-list-gap;
    }
  }
}

// bootstrap.scss
@forward "src/list" hide list-reset, $horizontal-list-gap;

1.2.3. 重写默认值

类似@use,@forward可以重写默认值, 所以也可以当做一个配置的方法.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// _library.scss
$black: #000 !default;
$border-radius: 0.25rem !default;
$box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default;

code {
  border-radius: $border-radius;
  box-shadow: $box-shadow;
}
// _opinionated.scss
@forward 'library' with (
  $black: #222 !default,
  $border-radius: 0.1rem !default
);

// style.scss
@use 'opinionated' with ($black: #333);

1.2.4. 注意事项

非常重要的一点,@forward不能用在含有样式代码的文件中

意思就是说@forward只用在整合和暴露内部成员

也就是说如果像下面这样:

1
2
3
4
@forward "colors" as colors-*;
// ------ Fonts ------ //
$global-font-size: 16px !default;
$global-font-color: $colors-gray-800;

这样是拿不到变量的

@forward一般就用来整合文件,暴露出去,当然也有特殊用法,就是上一节的重写默认值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16

// _library.scss
$black: #000 !default;
$border-radius: 0.25rem !default;
$box-shadow: 0 0.5rem 1rem rgba($black, 0.15) !default;

code {
  border-radius: $border-radius;
  box-shadow: $box-shadow;
}
// _opinionated.scss
@forward 'library' with (
  $black: #222 !default,
  $border-radius: 0.1rem !default
);

1.3. @import

@import应该是最常用的一个内置At-rules了, 没有import无法模块化

首先CSS本身就有@import, 参考: @import - CSS(层叠样式表) | MDN (mozilla.org)

然而sass里的import和css里的import最大的不同就是sass是编译器执行的

@import在将来会被移除,所以从现在开始尽量少用或者不用, 原因很简单

  • 语义上容易和前面的use等等产生分歧
  • 并且css里也@import,重名了.
  • 最重要的是import会对作用域进行污染,容易产生覆盖问题
  • 定位一个变量的时候比较麻烦,因为都是全局的
  • 每import一个文件,都会编译一次,会增加编译的时间
  • 没有办法定义私有变量

1.3.1. 模块搜索路径

1.3.1.1. 如何搜索

如果是@import variables, 那么会搜索./variables.scss, ./variables.sass, ./variables.css

一般@import有以下几种形式

  • 网络的文件
  • 相对路径的文件
  • npm包的文件
1.3.1.2. import相对路径

假设有如下文件树

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
│  main.scss
│  postcss.config.js
│  tailwind.config.js
│  tailwind.scss
│
├─common
│      _global.scss
│      _variables.scss
│
├─components
│      _code.scss
│      _markdown.scss
│
├─pages
│      _index.scss
│
├─partial
│      _header.scss
│
└─vendor
        _animation.scss
        _normalize.scss
        _utils.scss
1
2
// main.scss
@import 'common/variables'
1.3.1.3. import网络文件
1
@import url(https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/scss/bootstrap.scss);
1.3.1.4. import npm包的文件

@use一样,如果是npm包的,如果要@import moduleA, 那么搜索路径为node_modules/moduleA/sass/moduleA.scss

bootstrap举例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Custom.scss
// Option A: Include all of Bootstrap

// Include any default variable overrides here (though functions won't be available)
@import "../node_modules/bootstrap/scss/bootstrap";

// Custom.scss
// Option B: Include parts of Bootstrap

// 1. Include functions first (so you can manipulate colors, SVGs, calc, etc)
@import "../node_modules/bootstrap/scss/functions";

// 2. Include any default variable overrides here

// 3. Include remainder of required Bootstrap stylesheets
@import "../node_modules/bootstrap/scss/variables";
@import "../node_modules/bootstrap/scss/mixins";

// 4. Include any optional Bootstrap components as you like
@import "../node_modules/bootstrap/scss/root";
@import "../node_modules/bootstrap/scss/reboot";
@import "../node_modules/bootstrap/scss/type";
@import "../node_modules/bootstrap/scss/images";
@import "../node_modules/bootstrap/scss/containers";
@import "../node_modules/bootstrap/scss/grid";
1.3.1.5. 注意事项

在任何操作系统上都用forward slashes - /不要用back slashes - \

1.3.1.6. Partials

当以_开头的文件被引入时, 意味着这些文件只是组合,不要去单独编译他们

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// foundation/_code.scss
code {
  padding: .25em;
  line-height: 0;
}

// foundation/_lists.scss
ul, ol {
  text-align: left;

  & & {
    padding: {
      bottom: 0;
      left: 0;
    }
  }
}

// foundation/_index.scss
@import 'code', 'lists';

// style.scss
@import 'foundation';

1.3.2. 嵌套 -Nesting

1
2
3
4
5
6
7
8
9
// _theme.scss
pre, code {
  font-family: 'Source Code Pro', Helvetica, Arial;
  border-radius: 4px;
}
// style.scss
.theme-sample {
  @import "theme";
}

官方推荐在使用第三方的样式的时候,为了限制作用域,可以结合mixin来引入第三方的样式

总之,import会很容易产生问题,即使是自己写的模块,也会容易出现作用域的问题,名称的问题等等,尽量少用import

替代方案如下

  • 可以使用@use@forward来替代
  • 可以将样式放在@mixin里,再进行import

1.3.3. import css

css文件也可以引入,只是文件中不要含有sass的语法

1.3.4. CSS的import

由于sass是要编译的,那么原始css文件如何保留不被编译,sass提供了下面几种机制

  • .css结尾的import
  • http://或者https://开头的import
  • 调用了url方法的import
  • 带有media参数的import

例如:

1
2
3
4
@import "theme.css";
@import "http://fonts.googleapis.com/css?family=Droid+Sans";
@import url(theme);
@import "landscape" screen and (orientation: landscape);

1.3.5. 插值

import也可以使用差值

1
2
3
4
5
@mixin google-font($family) {
  @import url("http://fonts.googleapis.com/css?family=#{$family}");
}

@include google-font("Droid Sans");

1.3.6. import和模块系统

如果@import引入的文件包含了使用@use导入的模块,那么模块里的成员是不能被访问到的(即便定义了namespace也不行),

@forward可以传递作用域,使之变为可能

1.3.7. Import-Only文件

仅导入文件,@import可用,@use不可用,这样的文件有一定的兼容性,切无需增肌额外的namespace.

为什么这么做?

有些模块是为模块化系统设计的,所以名称很短,如果在用不了@use的系统里就会导致一些覆盖,冲突等意想不到的结果

这时候定义一个文件名为xxx.import.scss,为老的@import引入方式提供支持

示例:

一个为模块化设计的短名称的模块,里面的名称在@use的模块化系统里没有任何问题,因为可以@include reset.list()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// _reset.scss

// Module system users write `@include reset.list()`.
@mixin list() {
  ul {
    margin: 0;
    padding: 0;
    list-style: none;
  }
}

但是如果被@import了,那就容易乱套,这时候建立一个文件如下

1
2
3
4
// _reset.import.scss

// Legacy import users can keep writing `@include reset-list()`.
@forward "reset" as reset-*;

这时候,再去@import reset, 就可以用@include reset-list(), 这样就不会出现冲突和覆盖了

1.4. 关于@use,@forward和@import的使用总结

  • 按照官方推荐,尽量的使用@use@forward
  • 层次清晰一点,同类型的模块放在一个文件夹下,可以设置_index.scss进行合并
  • 由于@use无法传递成员,所以variables,mixins, functions这类一定使用@forward进行组合,并配合as保留命名空间,不污染scope
  • 项目里文件夹层次不宜过多,一般一层子目录就够了
  • 文件夹的_index.scss里,含有variables,mixins, functions的子模块要用@forward,否则外层的sass访问不到
  • 不是所有sass的实现都支持@use,目前只有DartSass,所以使用前先确认编译环境
  • 使用@import的时候尽量使用长名称,以免和业务发生冲突
  • @use@import都有可能会使得文件编译后变得臃肿,所以使用前,一定规划好层次解构.避免不必要的重复导入

1.5. @mixin 和 @include

@mixin用来混合属性或者混合计算属性, 和variables一样,@mixin的重要作用是用来复用, 也可以理解为更细粒度的模块化

定义mixin的方式有2种,带参数和不带参数

  • @mixin <name> { ... }
  • @mixin name(<arguments...>) { ... }

mixin里可以包含普通语句和顶级语句(@use @import @mixin @function), 基本上和在外层写一个样式没有区别

如果要混合@mixin,请使用@include

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22

@mixin reset-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

@mixin horizontal-list {
  @include reset-list;

  li {
    display: inline-block;
    margin: {
      left: -2px;
      right: 2em;
    }
  }
}

nav ul {
  @include horizontal-list;
}

1.5.1. @mixin参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@mixin rtl($property, $ltr-value, $rtl-value) {
  #{$property}: $ltr-value;

  [dir=rtl] & {
    #{$property}: $rtl-value;
  }
}

.sidebar {
  @include rtl(float, left, right);
}

1.5.2. @mixin默认参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
@mixin replace-text($image, $x: 50%, $y: 50%) {
  text-indent: -99999em;
  overflow: hidden;
  text-align: left;

  background: {
    image: $image;
    repeat: no-repeat;
    position: $x $y;
  }
}

.mail-icon {
  @include replace-text(url("/images/mail.svg"), 0);
}

1.5.3. 按名称传递参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@mixin square($size, $radius: 0) {
  width: $size;
  height: $size;

  @if $radius != 0 {
    border-radius: $radius;
  }
}

.avatar {
  @include square(100px, $radius: 4px);
}

1.5.4. 可变数量参数

类似python的*args

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@mixin order($height, $selectors...) {
  @for $i from 0 to length($selectors) {
    #{nth($selectors, $i + 1)} {
      position: absolute;
      height: $height;
      margin-top: $i * $height;
    }
  }
}

@include order(150px, "input.name", "input.address", "input.zip");

结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
input.name {
  position: absolute;
  height: 150px;
  margin-top: 0px;
}

input.address {
  position: absolute;
  height: 150px;
  margin-top: 150px;
}

input.zip {
  position: absolute;
  height: 150px;
  margin-top: 300px;
}

1.5.5. 字典型可变参数

类似python的**kwargs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
@use "sass:meta";

@mixin syntax-colors($args...) {
  @debug meta.keywords($args);
  // (string: #080, comment: #800, variable: #60b)

  @each $name, $color in meta.keywords($args) {
    pre span.stx-#{$name} {
      color: $color;
    }
  }
}

@include syntax-colors(
  $string: #080,
  $comment: #800,
  $variable: #60b,
)

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
pre span.stx-string {
  color: #080;
}

pre span.stx-comment {
  color: #800;
}

pre span.stx-variable {
  color: #60b;
}

注意:必须用meta.keywords进行转换

1.5.6. 混用可变参数

1
2
3
$form-selectors: "input.name", "input.address", "input.zip" !default;

@include order(150px, $form-selectors...);

1.5.7. @content代码块

@mixin还可以结合@content来指定一段样式代码传递的位置,其要点如下:

  • 类似参数,但是无需写形参
  • 实参必须是样式代码
  • @content只能配合@mixin使用,并且是在@mixin里使用@content
  • @content实际上就是占位符
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@mixin hover {
  &:not([disabled]):hover {
    @content;
  }
}

.button {
  border: 1px solid black;
  @include hover {
    border-width: 2px;
  }
}

输出

1
2
3
4
5
6
.button {
  border: 1px solid black;
}
.button:not([disabled]):hover {
  border-width: 2px;
}

另外,如果要传递多个代码块, @content必须在@each里使用

1.5.8. 更复杂的用法,给@content传递参数

这个写法实在晦涩难懂, 在函数里绕来绕去, 看示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
@mixin media($types...) {
  @each $type in $types {
    @media #{$type} {
      @content($type);
    }
  }
}

@include media(screen, print) using ($type) {
  h1 {
    font-size: 40px;
    @if $type == print {
      font-family: Calluna;
    }
  }
}

输出

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@media screen {
  h1 {
    font-size: 40px;
  }
}
@media print {
  h1 {
    font-size: 40px;
    font-family: Calluna;
  }
}

觉得这种不如直接写成这样, 简单直接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$medias: 'screen', 'print';

@each $sc in $medias {
    @media #{$sc} {
        h1 {
            font-size: 40px;
            @if $sc == print {
              font-family: Calluna;
            }
        }
    }
}

1.6. @function

@function@mixin的区别在于

  • @function主要用来计算,有返回值, @mixin没有返回值,主要用来包含和混合样式
  • @mixin需要用@include来使用, @function不需要

1.6.1. 使用原则

用来计算值,不要用来写样式

1.6.2. 参数

基本和@mixin的使用方式相同,可以用可变参数,字典型参数,可变列表型参数和可变字典型参数,参数默认值这些特性都支持

可变列表型参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
@function sum($numbers...) {
  $sum: 0;
  @each $number in $numbers {
    $sum: $sum + $number;
  }
  @return $sum;
}

.micro {
  width: sum(50px, 30px, 100px);
}

1.6.3. @return

返回值使用@return

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@function pow($base, $exponent) {
  $result: 1;
  @for $_ from 1 through $exponent {
    $result: $result * $base;
  }
  @return $result;
}

.sidebar {
  float: left;
  margin-left: pow(4, 3) * 1px;
}

1.6.4. 注意事项

在sass里,很多地方-_等同, 尤其是一些命名, @function的名称 larger-fontlarger_font相同

1.6.5. 参考

1.7. @extend

@extend类似于@mixin都是用来混合样式的,但是用法有诸多不同, 稍微难理解一点

1.7.1. 原则和工作机制

1.7.1.1. @extend是会生成继承的组合代码的

他会使用类似,进行样式选择器组合

1
2
3
4
5
6
7
8
.warning {
    color: red;
}

p {
    background-color: gray;
    @extend .warning;
}

会生成

1
2
3
4
5
6
7
.warning, p {
  color: red;
}

p {
  background-color: gray;
}

同样的如果使用@mixin

1
2
3
4
5
6
7
8
@mixin warning {
    color: red;
}

p {
    background-color: gray;
    @include warning;
}

输出如下

1
2
3
4
p {
  background-color: gray;
  color: red;
}

所以,@extend@mixin的第一个区别是前者生成的是新的选择器以及相应的样式, 后者直接包含样式,不生成选择器

1.7.1.2. @extend生成的选择器是交错的

可以理解为:为达目的 不择手段, 看下面的示例:

1
2
3
4
5
6
7
8
.header .circle {
    border-radius: 100%;
    border-style: none;
}

.user-info .medium-avatar {
    @extend .circle
}

想要继承.circle,但是它不是单独存在,这个时候又无法确定顺序,所以生成的代码就比较臃肿了,输出如下:

1
2
3
4
.header .circle, .header .small-avatar .medium-avatar, .small-avatar .header .medium-avatar {
  border-radius: 100%;
  border-style: none;
}

1.7.2. 单纯的可继承样式

有些样式,我甚至都不想公开使用,只是内部继承, 可以理解为私有父类, 这种时候可以使用%

示例:

1
2
3
4
5
6
7
.alert:hover, %strong-alert {
  font-weight: bold;
}

%strong-alert:hover {
  color: red;
}

编译之后

1
2
3
.alert:hover {
  font-weight: bold;
}

同样可以利用-或者_将可继承变得私有化

1.7.3. 范围

注意@use,@forward@import的区别,为了避免作用域的混乱,尽量用@use@forward来限制继承的范围

1.7.4. 可选的继承

结尾加上!optional, 因为在没有找到可用的选择器的时候,sass会发出警告,如果设置可选,那么就会在找不到的时候忽略.

这条规则在编写发布公共的sass组件时会比较有用.

1.7.5. 如何选择@extend和@mixin

参考以下区别和特性进行选择

  • @mixin生成的代码比较简单,就是一个包含关系,@extend涉及到一些选择器的计算和组合

  • 选择器是可被服务器和编译器进行压缩优化的, @mixin不会,所以@mixin会比@extend的代码要大(在某些情况下)

  • @extend适合处理关系紧密,且语义明了的选择器

  • @mixin是和处理需要参数传递,并进行定制的地方

  • @extend即使是在编译以后,解构也很清晰,更接近原生的css

1.7.6. 限制

单独的选择器才能extend

当样式选择器错综复杂的时候, @extend基本上用处很小,因为它会假设选择器之间不会有任何交错(所以这种情况下,没有必要使用@extend了,及其不准确)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
header .warning li {
  font-weight: bold;
}

aside .notice dd {
  // Sass doesn't generate CSS to match the <dd> in
  //
  // <header>
  //   <aside>
  //     <div class="warning">
  //       <div class="notice">
  //         <dd>...</dd>
  //       </div>
  //     </div>
  //   </aside>
  // </header>
  //
  // because matching all elements like that would require us to generate nine
  // new selectors instead of just two.
  @extend li;
}

输出

1
2
3
header .warning li, header .warning aside .notice dd, aside .notice header .warning dd {
  font-weight: bold;
}

@extend可以在其它At-Rules里使用,但是必须不能再被其它包裹

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@media screen and (max-width: 600px) {
  .error--serious {
    @extend .error;
    //      ^^^^^^
    // Error: ".error" was extended in @media, but used outside it.
  }
}

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

1.8. @error

报错

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@mixin reflexive-position($property, $value) {
  @if $property != left and $property != right {
    @error "Property #{$property} must be either left or right.";
  }

  $left-value: if($property == right, initial, $value);
  $right-value: if($property == right, $value, initial);

  left: $left-value;
  right: $right-value;
  [dir=rtl] & {
    left: $right-value;
    right: $left-value;
  }
}

.sidebar {
  @include reflexive-position(top, 12px);
  //       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  // Error: Property top must be either left or right.
}

1.9. @warn

警告

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
$known-prefixes: webkit, moz, ms, o;

@mixin prefix($property, $value, $prefixes) {
  @each $prefix in $prefixes {
    @if not index($known-prefixes, $prefix) {
      @warn "Unknown prefix #{$prefix}.";
    }

    -#{$prefix}-#{$property}: $value;
  }
  #{$property}: $value;
}

.tilt {
  // Oops, we typo'd "webkit" as "wekbit"!
  @include prefix(transform, rotate(15deg), wekbit ms);
}

1.10. @debug

输出调试信息

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
@mixin inset-divider-offset($offset, $padding) {
  $divider-offset: (2 * $padding) + $offset;
  @debug "divider offset: #{$divider-offset}";

  margin-left: $divider-offset;
  width: calc(100% - #{$divider-offset});
}

// output
// test.scss:3 Debug: divider offset: 132px

1.11. @at-root

将嵌套的规则独立到外层

示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
article .outside {
    font-size: medium;
    background-color: blue;
    @at-root .inside {
        font-size: small;
        border: 0.2rem;
    }
    .inside2 {
        font-size: x-small;
        font-weight: 400;
    }
}

很单纯的进行了分离没有任何覆盖和计算,输出如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
article .outside {
  font-size: medium;
  background-color: blue;
}

.inside {
  font-size: small;
  border: 0.2rem;
}

article .outside .inside2 {
  font-size: x-small;
  font-weight: 400;
}

如果不是想要单独定义新样式,而是将规则进行分离,可以这样

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
article .outside {
  font-size: medium;
  background-color: blue;
  font-size: small;
  border: 0.2rem;
  font-size: x-small;
  font-weight: 400;
}

article .outside .dark-bg {
  background-color: black;
}

CSS的At-Rules

@media

@support

@keyframes

2. 流程控制

2.1. @if and @else

这个没啥好说的, 条件为truefalse, 还有个分支是@else if

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@use "sass:math";

@mixin triangle($size, $color, $direction) {
  height: 0;
  width: 0;

  border-color: transparent;
  border-style: solid;
  border-width: math.div($size, 2);

  @if $direction == up {
    border-bottom-color: $color;
  } @else if $direction == right {
    border-left-color: $color;
  } @else if $direction == down {
    border-top-color: $color;
  } @else if $direction == left {
    border-right-color: $color;
  } @else {
    @error "Unknown direction #{$direction}.";
  }
}

.next {
  @include triangle(5px, black, right);
}

2.2. @for

以下两种方式, to不包含末端, through包含末端

1
2
@for <variable> from <expression> to <expression> { ... }
@for <variable> from <expression> through <expression> { ... }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@for $i from 1 through 6 {
    h#{$i} {
        font-weight: 400;
        font-family: $global-serif-font-family;

        .anchor {
        float: left;
        line-height: 1;
        margin-left: -20px;
        padding-right: 4px;

        &:hover {
            border-bottom: initial;
        }

        .icon-link {
            visibility: hidden;
            font-size: 16px;
            display: contents;

            &:before {
            vertical-align: middle;
            }
        }
        }

        &:hover {
        .icon-link {
            visibility: visible;
        }
        }
    }
}

结果

1
2
3
4
5
6
7
8
.next {
  height: 0;
  width: 0;
  border-color: transparent;
  border-style: solid;
  border-width: 2.5px;
  border-left-color: black;
}

注意:

false的可能性

  • false
  • null

除此之外,全部都是true, 包括0, 空字符串, 空list

2.3. @each

2.3.1. 普通列表类型

1
@each <variable> in <expression> { ... }
1
2
3
4
5
6
7
8
9
$sizes: 40px, 50px, 80px;

@each $size in $sizes {
  .icon-#{$size} {
    font-size: $size;
    height: $size;
    width: $size;
  }
}

2.3.2. 键值对类型

1
@each <variable>, <variable> in <expression> { ... }.
1
2
3
4
5
6
7
8
9
$icons: ("eye": "\f112", "start": "\f12e", "stop": "\f12f");

@each $name, $glyph in $icons {
  .icon-#{$name}:before {
    display: inline-block;
    font-family: "Icon Font";
    content: $glyph;
  }
}

2.3.3. 解构

在已知列表包含了列表,且已知嵌套列表的长度的时候,我们可以简单的利用解构(Destructuring)来进行操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
$icons:
  "eye" "\f112" 12px,
  "start" "\f12e" 16px,
  "stop" "\f12f" 10px;

@each $name, $glyph, $size in $icons {
  .icon-#{$name}:before {
    display: inline-block;
    font-family: "Icon Font";
    content: $glyph;
    font-size: $size;
  }
}

得到

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
@charset "UTF-8";
.icon-eye:before {
  display: inline-block;
  font-family: "Icon Font";
  content: "";
  font-size: 12px;
}

.icon-start:before {
  display: inline-block;
  font-family: "Icon Font";
  content: "";
  font-size: 16px;
}

.icon-stop:before {
  display: inline-block;
  font-family: "Icon Font";
  content: "";
  font-size: 10px;
}

2.4. @while

一种更自由的迭代方式, 只要条件为true就继续执行,表达式为

1
 @while <expression> { ... }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
@use "sass:math";

/// Divides `$value` by `$ratio` until it's below `$base`.
@function scale-below($value, $base, $ratio: 1.618) {
  @while $value > $base {
    $value: math.div($value, $ratio);
  }
  @return $value;
}

$normal-font-size: 16px;
sup {
  font-size: scale-below(20px, 16px);
}

结果

1
2
3
sup {
  font-size: 12.36094px;
}

3. References

TAG: sass scss css web
文章作者 : Cocding