重铸 CSS preprocessor 荣光 SCSS 义不容辞

这是我参与新手入门的第 2 篇文章

虽然看起来是一份样式指南,其实包含了大部分 Sass/SCSS 的使用

对于样式规范使用自动格式化的工具是非常有用的

本没有说过这句话,但是说的人多的也就说过了这句话。

CSS with superpowers

Sass/SCSS is the most mature, stable, and powerful professional grade CSS extension language in the world.


使用指引

变量

在 Sass 中,允许定义变量并且允许在运行中计算这些值。

创建新变量的准则:

  • 该值至少重复出现了两次
  • 该值至少可能会被更新一次
  • 该值 非巧合情况下 所有的表现都与变量有关

作用域

通俗来讲就是局部声明的变量会覆盖全局变量,称之为 全局变量影子

// Initialize a global variable at root level.
// 在根级别初始化一个全局变量
$variable: 'initial value';

// Create a mixin that overrides that global variable.
// 创建一个重写全局变量的 @mixin
@mixin global-variable-overriding {
  $variable: 'mixin value' !global;
}

.local-scope::before {
  // Create a local variable that shadows the global one.
  // 创建一个全局变量影子变量
  $variable: 'local value';

  // Include the mixin: it overrides the global variable.
  // 使用 @include @xxx(mixin):重写全局变量
  @include global-variable-overriding;

  // Print the variable’s value.
  // It is the **local** one, since it shadows the global one.
  // 打印变量的值
  // 这是本地的那个,因为它是全局影子变量
  content: $variable;
}

// Print the variable in another selector that does no shadowing.
// It is the **global** one, as expected.
// 在另一个没有做(没有声明全局影子变量)选择器内打印变量
// 如预期的,这是全局的那个变量
.other-local-scope::before {
  content: $variable;
}
复制代码

!default 标识符

使用 !default 标志来定义变量,方便其他开发者重写变量。

$baseline: 1em !default;
复制代码

开发者才能在引入你的库之前定义自用的 $baseline,引入后又不必担心自己的值被重定义了。

// Developer’s own variable
// 开发者自己的变量
$baseline: 2em;

// Your library declaring `$baseline`
// 库里声明了 `$baseline`
@import 'your-library';

// $baseline == 2em;
复制代码

!global 标识符

!global 标志应该只在局部范围的全局变量被覆盖时使用。定义根级别的变量时,!global 标志应该省略。

// ?
$baseline: 2em;

// ?
$baseline: 2em !global;
复制代码

多变量与 Maps

优势:

  • 能够遍历
  • 使用 map-get() 函数,提供友好 API 功能
/// Z-indexes map, gathering all Z layers of the application
/// Z-indexed map, 收集应用中所有 Z 层
/// @access private 私有的
/// @type Map Map类型
/// @prop {String} key - Layer’s name 层的名字?变量的标识
/// @prop {Number} value - Z value mapped to the key 标识对应的值
$z-indexes: (
  'modal': 5000,
  'dropdown': 4000,
  'default': 1,
  'below': -1,
);

/// Get a z-index value from a layer name
/// 用标识获取对应的变量值
/// @access public 公共的权限,大家都能获取
/// @param {String} $layer - Layer’s name
/// @return {Number}
/// @require $z-indexes
@function z($layer) {
  @return map-get($z-indexes, $layer);
}
复制代码

扩展

// input scss
/* This CSS will print because %message-shared is extended. */
%message-shared {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

// This CSS won't print because %equal-heights is never extended.
// 并没有被使用(继承)到
%equal-heights {
  display: flex;
  flex-wrap: wrap;
}

.message {
  @extend %message-shared;
}

.success {
  @extend %message-shared;
  border-color: green;
}

.error {
  @extend %message-shared;
  border-color: red;
}

.warning {
  @extend %message-shared;
  border-color: yellow;
}
复制代码
// output css
/* This CSS will print because %message-shared is extended. */
.message, .success, .error, .warning {
  border: 1px solid #ccc;
  padding: 10px;
  color: #333;
}

.success {
  border-color: green;
}

.error {
  border-color: red;
}

.warning {
  border-color: yellow;
}
复制代码

有关更多,本人菜才疏学浅,不能理解其意,无法做出归纳总结。

混合宏

@mixin

重用和减少重复组件的关键,使用 @mixin 的关键是简洁,当然也不要过度逻辑化你的代码,尽量保持一切简洁。如果一个 @mixin 最后超过了 20 行,那么它应该被分离成更小的块甚至是重写。

如果你发现有一组 CSS 属性经常不是因为巧合一起出现,那么你就可以使用 @mixin 来代替:

/// 清除浮动
@mixin clearfix {
  &::after {
    content: '';
    display: table;
    clear: both;
  }
}

/// 设定宽高
/// @param {Length} $width
/// @param {Length} $height
@mixin size($width, $height: $width) {
  width: $width;
  height: $height;
}
复制代码

@include

有时候使用宏单纯为了避免重复申明相同样式,这时我们使用 @include 混入宏可以省略参数,同时应该删除括号以保持简洁。

// ?
.foo {
  @include center;
}

// ?
.foo {
  @include center();
}
复制代码

参数列表

大概就等于 js 扩展运算符 ...

@mixin dummy($a, $b, $c) {
  // …
}

// ?
@include dummy(true, 42, 'kittens');

// ? but ?
$params: (true, 42, 'kittens');
$value: dummy(nth($params, 1), nth($params, 2), nth($params, 3));

// ?
$params: (true, 42, 'kittens');
@include dummy($params...);

// ?
$params: (
  'c': 'kittens',
  'a': true,
  'b': 42,
);
@include dummy($params...);
复制代码

混合宏和浏览器前缀

我推荐使用 Autoprefixer Bourbon Compass

只有当你遇到以上情况解决不了的情况再考虑如下方法,切记不要无脑使用前缀:

// ?
@mixin transform($value) {
  -webkit-transform: $value;
  -moz-transform: $value;
  transform: $value;
}

/// Mixin helper to output vendor prefixes
/// 输出浏览器供应商的前缀的 Mixin 助手
/// @access public 公共的获取权限
/// @author KittyGiraudel 作者 KittyGiraudel
/// @param {String} $property - Unprefixed CSS property 没有 CSS 前缀的属性
/// @param {*} $value - Raw CSS value 原始 CSS 值
/// @param {List} $prefixes - List of prefixes to output 输出的前缀列表
@mixin prefix($property, $value, $prefixes: ()) {
  @each $prefix in $prefixes {
    -#{$prefix}-#{$property}: $value;
  }

  #{$property}: $value;
}

// how to use 如何使用
.foo {
  @include prefix(transform, rotate(90deg), ('webkit', 'ms'));
}
复制代码

条件语句

Sass 通过 @if@else 指令提供了条件语句。除非你的代码确实如此复杂,否则没必要在日常开发中使用条件语句,其主要适用于框架或库。如果使用了条件语句,请遵守以下规则:

  • 除非必要,不然不需要括号
// ?
@if $support-legacy {
  // …
} @else {
  // …
}

// ?
@if ($support-legacy == true) {
  // …
}
@else {
  // …
}
复制代码

测试一个错误值时,通常使用 not 关键字 而不是比较与 false 或 null 等值

// ?
@if not index($list, $item) {
  // …
}

// ?
@if index($list, $item) == null {
  // …
}
复制代码

当使用条件语句并在一些条件下有内联函数返回不同结果时,始终要确保最外层函数有一个 @return 语句,这和函数设置了返回值就必须所有分支必须要有返回值不一样。

// ?
@function dummy($condition) {
  @if $condition {
    @return true;
  }

  @return false;
}

// ?
@function dummy($condition) {
  @if $condition {
    @return true;
  } @else {
    @return false;
  }
}
复制代码

循环

@each

@each 是最实用的,有两种使用方式,但是第二种更加实用。

@each $theme in $themes {
  .section-#{$theme} {
    background-color: map-get($colors, $theme);
  }
}

@each $key, $value in $map {
  .section-#{$key} {
    background-color: $value;
  }
}
复制代码

@for

当需要聚合伪类 :nth-* 的时候,使用 @for 循环很有用。除了这些使用场景,如果必须迭代最好还是使用 @each 循环。

@for $i from 1 through 10 {
  .foo:nth-of-type(#{$i}) {
    border-color: hsl($i * 36, 50%, 50%);
  }
}
复制代码

不要使用 @while

警告和错误 & 工具

当你需要用到的时候再去寻找吧,那样更加得高效


风格指南

  • 使用 2 个制表符大小的 tab 键
  • 理想上,每行保持为80个字符宽度
  • 正确书写多行CSS规则
  • 有意义的使用空格
// ?
.foo {
  display: block;
  overflow: hidden;
  padding: 0 1em;
}

// ?
.foo {
    display: block; overflow: hidden;

    padding: 0 1em;
}
复制代码

字符串

编码

为了避免潜在的字符编码问题,强力建议在入口文件中通过 @charset 指令使用 UTF-8 编码格式。

请确保该指令是文件的第一条语句,并排除其他字符编码声明。

@charset 'utf-8';
复制代码

引用

Sass 中字符串应该始终被单引号 ' 所包裹,理由如下:

  • 如果颜色名不被引号包裹,将会被解析为颜色值,显然这会导致严重问题
  • 大多数的语法高亮机制将会因未被引号包裹的字符串而罢工
  • 提高可读性
  • 没有正当理由不去用引号包裹字符串
// ?
$direction: 'left';

// ?
$direction: left;
复制代码

@charset 根据 CSS 应该用双引号包裹才是有效的,但是 SCSS 修正了相关信息,无论如何都能生成正确的代码

作为 CSS 的值

CSS 中类似 initialsans-serif 的标识符无须引用起来。事实上,font-family: 'sans-serif' 该声明是错误的,因为 CSS 希望获得的是一个标识符,而不是一个字符串。因此,我们无须引用这些值。

// ?
$font-type: sans-serif;

// ?
$font-type: 'sans-serif';

// Okay I guess 也许还不错
$font-type: unquote('sans-serif');
复制代码

就像上例这样,我们就可以区别用于 CSS 值的字符串(CSS 标识符)和 Sass 的字符串类型了(比如 map 的值)。

我们没有引用前者,但却使用单引号包裹了它。

包含引号的字符串

如果字符串内包含了一个或多个单引号,一种解决方案就是使用双引号包裹整个字符串,从而避免使用转义字符。
URL 最好也用引号包裹起来,原因和上面所描述一样:

// Okay 还行
@warn 'You can\'t do that.';

// Okay 还行
@warn "You can't do that.";

// ?
.foo {
  background-image: url('/images/kittens.jpg');
}

// ?
.foo {
  background-image: url(/images/kittens.jpg);
}
复制代码

数字

零值

当数字小于 1 时,应该在小数点前写出 0. 永远不要显示小数尾部的 0。

// ?
.foo {
  padding: 2em;
  opacity: 0.5;
}

// ?
.foo {
  padding: 2.0em;
  opacity: .5;
}
复制代码

单位

当定义 长度 时,0 后面不需要加单位。

// ?
$length: 0;

// ?
$length: 0em;
复制代码

计算

高级运算应该始终被包裹在括号中。这么做不仅是为了提高可读性,也是为了防止一些 Sass 强制要求对括号内内容计算的极端情况。

// ?
.foo {
  width: (100% / 3);
}

// ?
.foo {
  width: 100% / 3;
}
复制代码

当你对任何代码无法找到一个合理解释时,你应当提交一段描述清晰的注释,包括你是 怎样遇见这个问题 以及你认为 它应该怎样工作

承认自己不清楚一些机制的解析方式,远比让以后的开发者从零开始弄清它们更有帮助。

/**
 * 1. Magic number. This value is the lowest I could find to align the top of
 * `.foo` with its parent. Ideally, we should fix it properly.
 * 一段帮助后续开发理解的注释
 */
.foo {
  top: 0.327em; /* 1 */
}
复制代码

颜色

推荐使用顺序:

  • HSL 值;
  • RGB 值;
  • 十六进制(使用小写并尽可能简写)

不建议使用 CSS 颜色关键字。

颜色关键字的语义化并不准确,比如 greydarkgrey 的颜色更深一些;greygray 之间的差别也会造成一致性的问题。别忘记在 , 后添加空格:

// ?
.foo {
  color: hsl(0, 100%, 50%);
}

// Also ?
.foo {
  color: rgb(255, 0, 0);
}

// Meh
.foo {
  color: #f00;
}

// ?
.foo {
  color: #FF0000;
}

// ?
.foo {
  color: red;
}
复制代码

不过真的有人记得住这么多的颜色值吗?还是说大家都是网上找的颜色或者设计师给的?

颜色和变量

当一个颜色被多次调用时,最好其实必须用一个有意义的变量名来保存它。

$sass-pink: hsl(330, 50%, 60%);
复制代码

不过,如果你是在一个主题中使用,不建议固定的使用这个变量。你应该再使用另一个标识方式的变量来保存它。

$main-theme-color: $sass-pink;
复制代码

防止主题变化而出现此类结果 $sass-pink: blue

列表

遵守以下规范:

  • 除非适用于 CSS,否则应该始终使用逗号作为分隔符
  • 列表太长不能写在 80 字符宽度的单行中时使用多行形式,否则应该始终单行显示
  • 始终使用括号包裹

并且使用提供的 API 进行添加

// ?
$font-stack: ('Helvetica', 'Arial', sans-serif);

// ?
$font-stack: (
  'Helvetica',
  'Arial',
  sans-serif,
);

// ?
$font-stack: 'Helvetica' 'Arial' sans-serif;

// ?
$font-stack: 'Helvetica', 'Arial', sans-serif;

// ?
$font-stack: ('Helvetica', 'Arial', sans-serif,);

$shadows: (0 42px 13.37px hotpink);

// ?
$shadows: append($shadows, $shadow, comma);

// ?
$shadows: $shadows, $shadow;
复制代码

Map

  • 冒号 : 之后添加空格
  • 如果键是字符串(99% 都是字符串),则使用括号包裹起来
  • 每一个键值对单独一行
  • 为最后一个键值对添加尾部逗号 ,,方便添加新键值对、删除和重排已有键值对
// ?
$breakpoints: (
  'small': 767px,
  'medium': 992px,
  'large': 1200px,
);

// ?
$breakpoints: ( small: 767px, medium: 992px, large: 1200px );
复制代码

CSS 规则集

  • 相关联的选择器写在同一行,不相关联选择器分行书写
  • 最后一个选择器和左开大括号 { 中间书写一个空格
  • 每个声明单独一行
  • 冒号 : 后添加空格
  • 所有声明的尾部都添加一个分号 ;
// ?
.foo, .foo-bar,
.baz {
  display: block;
  overflow: hidden;
  margin: 0 auto;
}

// ?
.foo,
.foo-bar, .baz {
    display: block;
    overflow: hidden;
    margin: 0 auto }
复制代码

声明顺序

随你

确实如此,并没有规定如何排序,大家使用的规则也各不相同,但是我相信,如果正确的对 CSS 进行拆分并且添加注释,阅读起来不是什么难事。

选择器嵌套

如其名

// input scss
.foo {
  .bar {
    &:hover {
      color: red;
    }
  }
}

// output css
.foo .bar:hover {
  color: red;
}
复制代码

自动 SCSS 3.3 开始,可以在同一行中使用最近选择器引用 & 来实现高级选择器:

// input scss
.foo {
  &-bar {
    color: red;
  }
}

// output css
.foo-bar {
  color: red;
}
复制代码
  • 建议嵌套不要超过三层
  • 或者避免使用嵌套

其实现在的选择器也是挺好用的

当然也是有例外的

允许且推荐 在最外层选择器中嵌套伪类和伪元素,类似 .is-active 类名来控制当前选择器状态,也可以这样使用选择器嵌套:

.foo {
  color: red;

  &:hover {
    color: green;
  }

  &::before {
    content: 'pseudo-element';
  }
}

.foo {
  // …

  &.is-active {
    font-weight: bold;
  }
}
复制代码

命名约定

除了常量使用全大写字符外,其余使用 CSS-y 的风格:小写连字符分隔,有意义的命名,命名空间同样食用。

$vertical-rhythm-baseline: 1.5rem;

@mixin size($width, $height: $width) {
  // …
}

@function opposite-direction($direction) {
  // …
}

// ?
$CSS_POSITIONS: (top, right, bottom, left, center);

// ?
$css-positions: (top, right, bottom, left, center);

$su-configuration: ( … );

@function su-rainbow($unicorn) {
  // …
}
复制代码

注释

  • CSS 规则集之前使用 c 风格 /** ... */ 的注释
  • 特定部分使用单行 // 注释
  • 搭配 SassDoc 请使用 ///

结构

请根据 项目/组件 进行拆分

响应式设计和断点

应该不针对特定设备使用媒体查询。尤其不应该专门针对某设备型号设计媒体查询。媒体查询应该关注屏幕尺寸,直到当前设计遇到阻断再继续下一个媒体查询。

毕竟现在手机?像砖头,还有 「你的下一台电脑何必是电脑」。

听说响应式设计应该从手机做起是这样吗?没有太多响应式设计的经验,可以在评论区给出你的观点!

// ?
$breakpoints: (
  'medium': (min-width: 800px),
  'large': (min-width: 1000px),
  'huge': (min-width: 1200px),
);

// ?
$breakpoints: (
  'tablet': (min-width: 800px),
  'computer': (min-width: 1000px),
  'tv': (min-width: 1200px),
);
复制代码

Tips

不要简单地认为单位只是字符串,认为它会被安全的添加到数字后面。可以把单位认为是代数符号,例如,在现实世界中,5 英寸乘以 5 英寸得到 25 英寸。Sass 也适用这样的逻辑。

将一个单位添加给数字的时候,应该让该数值乘以 1${需要添加的单位}

不要使用传统编程语言的字符串拼接来添加单位

$value: 42;

// ?
$length: $value * 1px;

// ?
$length: $value + px;
复制代码
$value: 42 + 0px;
// -> 42px

$value: 1in + 0px;
// -> 1in

$value: 0px + 1in;
// -> 96px
复制代码

去除单位,只需要除以 1${对应的单位}

$length: 42px;

// ?
$value: $length / 1px;

// ?
$value: str-slice($length + unquote(''), 1, 2);
复制代码

做点什么

夏天真是太热了,最好能下点雪!

from alphardex’s codepen, snow(pure css ? scss)

<div class="snow"></div> * 200
复制代码
body {
  height: 100vh;
  
  // 设置渐变背景
  background: radial-gradient(ellipse at bottom, #1b2735 0%, #090a0f 100%);
  
  overflow: hidden;
  
  // https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter-function/drop-shadow()
  filter: drop-shadow(0 0 10px white);
}

/// 函数 随机生成 $min ~ $max number
@function random_range($min, $max) {
  $rand: random();
  $random_range: $min + floor($rand * (($max - $min) + 1));
  @return $random_range;
}

.snow {
  // total number 雪❄️的总量
  $total: 200;
  
  position: absolute;
  
  // snow size & shape & style
  // 雪❄️的大小形状颜色
  width: 10px;
  height: 10px;
  background: white;
  border-radius: 50%;

  @for $i from 1 through $total {
    // 随机出现的 x 位置
    $random-x: random(1000000) * 0.0001vw;

    // random offset 随机偏移量
    $random-offset: random_range(-100000, 100000) * 0.0001vw;

    // 中途摆动的 x 偏移
    $random-x-end: $random-x + $random-offset;
    
    // 随机消失的 x 位置
    $random-x-end-yoyo: $random-x + ($random-offset / 2);
    
    // 随机摆动时间点(百分比)
    $random-yoyo-time: random_range(30000, 80000) / 100000;
    
    // 中途摆动的 y 偏移
    $random-yoyo-y: $random-yoyo-time * 100vh;
    
    // 雪❄️的随机大小
    $random-scale: random(10000) * 0.0001;
    
    // 随机的下落持续时长
    $fall-duration: random_range(10, 30) * 1s;
    $fall-delay: random(30) * -1s;

    // 设置对映动画 & style
    &:nth-child(#{$i}) {
      opacity: random(10000) * 0.0001;
      transform: translate($random-x, -10px) scale($random-scale);
      animation: fall-#{$i} $fall-duration $fall-delay linear infinite;
    }

    @keyframes fall-#{$i} {
      #{percentage($random-yoyo-time)} {
        transform: translate($random-x-end, $random-yoyo-y) scale($random-scale);
      }

      to {
        transform: translate($random-x-end-yoyo, 100vh) scale($random-scale);
      }
    }
  }
}
复制代码

更多

请阅读 官网文档 和 本文参考 guideline

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享