【学习笔记】CSS 预处理器

背景

  • CSS 自诞生以来,基本语法和核心机制一直没有本质上的变化,它的发展几乎全是表现力层面上的提高。最开始 CSS 在网页中的做用只是辅助性的装饰,轻便易学是最大的需求;然而现在网站的复杂度已经不可同日而语,原生 CSS 逐渐让开发者力不从心
  • 当一门语言的能力不足而用户的运行环境又不支持其它选择的时候,这门语言就会沦为 “编译目标” 语言,开发者将选择另外一门更高级的语言来进行开发,而后编译到底层语言以便实际运行
  • 因而在前端领域,CSS 预处理器应运而生,而 CSS 这门古老的语言以另外一种方式 “从新适应” 了网页开发的需求

百花齐放

  • CSS 预处理器是一个能让你经过预处理器本身独有的语法来生成 CSS 的程序
  • 市面上有不少 CSS 预处理器可供选择,且绝大多数 CSS 预处理器会增长一些原生 CSS 不具有或不完善的高级特性,这些特性让 CSS 的结构更加具备可读性且易于维护,当前社区表明的 CSS 预处理器主要有一下几种:
    • Sass:2007 年诞生,最先也是最成熟的 CSS 预处理器,拥有 Ruby 社区的支持和 Compass 这一最强大的 CSS 框架,目前受 LESS 影响,已经进化到了全面兼容 CSS 的 Scss
    • Less:2009 年出现,受 Sass 的影响较大,但又使用 CSS 的语法,让大部分开发者和设计师更容易上手,在 Ruby 社区外支持者远超过 Sass,其缺点是比起 Sass 来可编程功能不够,不过优势是简单和兼容 CSS,反过来也影响了 Sass 演变到了 Scss 的时代,著名的 Twitter Bootstrap 就是采用 Less 作底层语言的
    • Stylus:Stylus 是一个 CSS 的预处理框架,2010 年产生,来自 Node.js 社区,主要用来给 Node 项目进行 CSS 预处理支持,因此 Stylus 是一种新型语言,能够建立健壮的、动态的、富有表现力的 CSS。比较年轻,其本质上作的事情与 Sass/Less 等相似

预处理器赋予的 “超能力”

文件切分

  • 页面愈来愈复杂,须要加载的 CSS 文件也愈来愈大,有必要把大文件切分开来,不然难以维护
  • 传统的 CSS 文件切分方案基本上就是 CSS 原生的 @import 指令,或在 HTML 中加载多个 CSS 文件,这些方案一般不能知足性能要求

模块化css

  • 把文件切分的思路再向前推动一步,就是 “模块化”,一个大的 CSS 文件在合理切分以后,所产生的这些小文件的相互关系应该是一个树形结构
  • 树形的根结节通常称做 “入口文件”,树形的其它节点通常称做 “模块文件”,入口文件一般会依赖多个模块文件,各个模块文件也可能会依赖其它更末端的模块,从而构成整个树形
  • 模块化是一种很是好的代码组织方式,是开发者设计代码结构的重要手段,模块能够很清晰地实现代码的分层、复用和依赖管理,让 CSS 的开发过程也能享受到现代程序开发的便利

选择符嵌套

  • 选择符嵌套是文件内部的代码组织方式,它可让一系列相关的规则呈现出层级关系。在之前若要达到这个目的,写法以下,这种写法须要手工维护缩进关系,当上级选择符发生变化时全部相关的下级选择符都要修改;此外把每条规则写成一行也不易阅读,为单条声明写注释也很尴尬(只能插在声明之间了)html

    .nav {margin: auto /* 水平居中 */; width: 1000px; color: #333;}
      .nav li {float: left /* 水平排列 */; width: 100px;}
          .nav li a {display: block; text-decoration: none;}
    复制代码
  • 在 CSS 预处理语言中,嵌套语法能够很容易地表达出规则之间的层级关系,为单条声明写注释也很清晰易读前端

.nav {
     margin: auto  // 水平居中
     width: 1000px
     color: #333
     li {
         float: left  // 水平排列
         width: 100px
         a {
             display: block
             text-decoration: none
         }
     }
}    
复制代码

变量

  • 在变量出现以前,CSS 中的全部属性值都是 “幻数”,不知道这个值是怎么来的、它有什么意义等,有了变量以后就能够给这些 “幻数” 起个名字了,便于记忆、阅读和理解
  • 当某个特定的值在多处用到时,变量就是一种简单而有效的抽象方式,能够把这种重复消灭掉,变量让开发者更容易实现网站视觉风格的统一,也让 “换肤” 这样的需求变得更加轻松易行
// 原生 CSS 代码 
strong {
    color: #ff4466;
    font-weight: bold;
}
.notice {
    color: #ff4466;
}

// 用 Stylus 来写
$color-primary = #ff4466

strong
    color: $color-primary
    font-weight: bold

.notice
    color: $color-primary
复制代码

运算

  • 光有变量仍是不够的,还须要有运算,若说变量让值有了意义,运算则可让值和值创建关联
  • 有些属性值跟其它属性值是紧密相关的,CSS 语法没法表达这层关系;而在预处理语言中可用变量和表达式来呈现这种关系
// 只能用注释来表达 max-height 的值是怎么来的,且注释中 3 这样的值也是幻数,还须要进一步解释
// 将来当行高或行数发生变化的时候,max-height 的值和注释中的算式也须要同步更新,维护起来很不方便
.wrapper {
    overflow-y: hidden;
    line-height: 1.5;
    max-height: 4.5em;  /* = 1.5 x 3 */
}

// 预处理语言来改良
// 在后期维护时,只要修改那两个变量就能够了
.wrapper
    $max-lines = 3
    $line-height = 1.5

    overflow-y: hidden
    line-height: $line-height
    max-height: unit($line-height * $max-lines, 'em')
 
 // 这种写法还带来另外一个好处:$line-height 这个变量能够是 .wrapper 定义的局部变量也能够从更上层的做用域获取
// 这意味着 .wrapper 可向祖先继承行高,而不须要为这个 “只显示三行” 的需求把本身的行高写死
// 有了运算,就有能力表达属性与属性之间的关联,它令代码更加灵活、更加 DRY
$line-height = 1.5  // 全局统一行高
body
    line-height: $line-height
.wrapper
    $max-lines = 3
    max-height: unit($line-height * $max-lines, 'em')
    overflow-y: hidden
复制代码

函数

  • 把经常使用的运算操做抽象出来,就获得了函数
  • 开发者可自定义函数,预处理器本身也内置了大量的函数,最经常使用的内置函数应该就是颜色的运算函数,有了它们,甚至都不须要打开 Photoshop 来调色,就能够获得某个颜色的同色系变种了
  • 预处理器的函数每每还支持默认参数、具名实参、arguments 对象等高级功能,内部还可设置条件分支,可知足复杂逻辑需求
// 给一个按钮添加鼠标悬停效果
.button {
    background-color: #ff4466;
}
.button:hover {
    background-color: #f57900;
}
// 很难分清 #ff4466 和 #f57900 这两种颜色到底有什么关联
// 若代码是用预处理语言来写,那事情就直观多了
.button
    $color = #ff9833
    background-color: $color
    &:hover
        background-color: darken($color, 20%)
复制代码

Mixins

Mixins 是 CSS 预处理器语言中最强大的特性,简单点来讲 Mixins 可将一部分样式抽出,做为单独定义的模块,被不少选择器重复使用node

Sass 的混合git

  • Sass 样式中声明 Mixins 时须要使用 @mixin,后面紧跟 Mixins 名,也能够定义参数同时可给这个参数设置一个默认值,但参数名是使用 $ 符号开始且和参数值之间须要使用冒号:分开
  • 在选择器调用定义好的 Mixins 须要使用 @include,而后在其后紧跟要调用的 Mixins 名,不过在 Sass 中还支持老的调用方法,就是使用加号 + 调用 Mixins,在 + 后紧跟 Mixins 名
// 声明一个 Mixins 叫做 error 
@mixin error($borderWidth: 2px) {
  border: $borderWidth solid #f00;
  color: #f00;
}

// 调用 error mixins
.generic-error {
  @include error(); /*直接调用error mixins*/
}
.login-error {
  @include error(5px); /*调用error mixins,并将参数$borderWidth的值重定义为5px*/
}
复制代码

Less 的混合github

  • 在 Less 中,混合是指将定义好的 ClassA 中引入另外一个已经定义的 Class,就像在以前的 Class 中增长一个属性
  • LESS 样式中声明 Mixins 和 Sass 声明方法不同,它更像 CSS 定义样式,在 Less 可将 Mixins 当作是一个类选择器,固然 Mixins 也能够设置参数并给参数设置默认值,不过设置参数的变量名是使用 @ 开头,一样参数和默认参数值之间须要使用冒号:分隔开
// 声明一个 Mixin 叫做 error
.error(@borderWidth: 2px){
  border: @borderWidth solid #f00;
  color: #f00;
}
// 调用 error Mixins
.generic-error {
  .error(); /*直接调用error mixins*/
}
.login-error {
  .error(5px); /*调用error mixins,并将参数@borderWidth的值重定义为5px*/
}
复制代码

Stylus 的混合编程

  • Stylus 中的混合和前两款 CSS 预处理器语言的混合略有不一样,它可不使用任何符号就直接声明 Mixins 名,而后在定义参数和默认值之间用等号 = 来链接
// 声明一个 Mixin 叫做 error
error(borderWidth=2px){
  border: borderWidth solid #f00;
  color: #f00;
}
// 调用error Mixins
.generic-error {
  error(); /*直接调用error mixins*/
}
.login-error {
  error(5px); /*调用error mixins,并将参数$borderWidth的值重定义为5px*/
}
复制代码

以上三个示例都将会转译成相同的 CSS 代码浏览器

.generic-error {
  border: 2px solid #f00;
  color:#f00;
}
.login-error {
  border: 5px solid #f00;
  color: #f00;
} 
复制代码

缺点

  • 额外的编译配置:在写样式前须要作一些额外的编译配置工做,sass-node 安装以及编译的配置就能卡住一批前端新手 image.pngsass

  • 编译成本:每次修改代码都须要从新编译,占用时间和 CPU image.pngmarkdown

  • 学习成本:不一样的 CSS 预处理器语法不一样,增长学习成本。在同一个团队甚至项目里,可能同时使用了好几种样式预处理器

    // Sass
    $color: #f00;
    $images: "../img";
    @mixin clearfix {
      &:after {
        content: " ";
        display: block;
        clear: both;
      }
    }
    body {
      color: $color;
      background: url("#{images}/1.png");
      @include clearfix;
    }
    
    // Less
    @color: #f00;
    @images: "../img";
    .clearfix() {
      &:after {
        content: " ";
        display: block;
        clear: both;
      }
    }
    body {
      color: @color;
      background: url("@{images}/1.png");
      .clearfix;
    }
    复制代码
  • 调试:在使用 CSS 预处理器时,一般会配置 SourceMap 来辅助调试,但即便这样,仍是会碰到一些调试困难的状况

回归 CSS

各类 CSS 预处理器在更新迭代的过程当中功能愈来愈繁杂花哨,但绝大部分人用到的核心功能仍是那几样:Variables、Mixing、Nested、Module,顶多再加上一些工具类函数。既想要预处理器的优势,又不想要它带来的成本和缺点,有没有一箭双鵰的办法?CSS 这么多年一直也在从社区汲取营养加速进化和迭代,能不能从 CSS 标准里面找到答案呢?

Variables in CSS

  • CSS 自定义属性(CSS Custom Properties),又叫 CSS 变量(CSS Variable),容许在样式中声明变量并经过 var() 函数使用
  • CSS Custom Properties for Cascading Variables 规范在 2012 年 10 月首次做为 工做草案(WD) 提出,并在 2015 年 10 月到达候选人推荐标准(CR)阶段,如今浏览器支持程度已经接近 93%
  • CSS 变量定义及使用以下所示,可定义的类型极其丰富,不一样于 SASS 预处理器变量的编译时处理,CSS 变量是浏览器在运行时进行处理的,所以 CSS 变量会更增强大和灵活
/* declaration */
--VAR_NAME: <declaration-value>;
/* usage */
var(--VAR_NAME)

/* root element selector (global scope), e.g. <html> */
:root {
  /* CSS variables declarations */
  --main-color: #ff00ff;
  --main-bg: rgb(200, 255, 255);
  --logo-border-color: rebeccapurple;

  --header-height: 68px;
  --content-padding: 10px 20px;

  --base-line-height: 1.428571429;
  --transition-duration: .35s;
  --external-link: "external link";
  --margin-top: calc(2vh + 20px);
}
body {
  /* use the variable */
  color: var(--main-color);
}
复制代码

为何变量的定义以 -- 开头?缘由在这里:Let’s Talk about CSS Variables

Operators

  • 可使用 calc() 进行计算
:root {
  --block-font-size: 1rem;
}

.block__highlight {
  /* WORKS */
  font-size: calc(var(--block-font-size)*1.5);
}
复制代码

Generate Colors

能够用于经过 RGB 等函数生成和计算颜色:Generate Colors

CSS to JS

CSS 变量出现前,从 CSS 传值给 JS 很是困难,甚至须要借助一些 Hack 的手法。现使用 CSS 变量,可直接经过 JS 获取变量值并进行修改

.breakpoints-data {
  --phone: 480px;
  --tablet: 800px;
}
const breakpointsData = document.querySelector('.breakpoints-data');

// GET
const phone = getComputedStyle(breakpointsData).getPropertyValue('--phone');

// SET
breakpointsData.style.setProperty('--phone', 'custom');
复制代码

Custom Theme

  • 使用 CSS 变量,定制和动态切换网站主题很是简单方便
  • 首先定义好不一样主题下的变量,而后正常书写样式便可
  • 经过 JS 改变元素属性,动态切换主题
html {
  --hue: 210; /* Blue */
  --text-color-normal: hsl(var(--hue), 77%, 17%);
  ...
}
html[data-theme='dark'] {
  --text-color-normal: hsl(var(--hue), 10%, 62%);
  ...
}
// 经过 JS 改变元素属性,动态切换主题
document.documentElement.setAttribute('data-theme', 'dark')
document.documentElement.setAttribute('data-theme', 'light')
复制代码

Mixins in CSS

  • CSS 的有一个提案:CSS @apply Rule,按照该草案描述,用户可直接使用 CSS 变量存放声明块,而后经过 @apply rule 使用
  • 惋惜这个提案已被废弃,具体废弃缘由感兴趣的能够看看这篇文章:Why I Abandoned @apply。尽管 Mixins 如今 CSS 尚未好的实现标准,但咱们坚信早晚会有更优秀的规范涌现出来弥补 CSS 的这一块空白
:root {
    --pink-schema: {
        color: #6A8759;
        background-color: #F64778;
    }
}

body{
  @apply --pink-schema;
}
复制代码

Nesting in CSS

  • CSS 里已经有 Nesting 的规范出现,尽管如今只处于 Editor’s Draft 阶段:CSS Nesting Module Level 3,能够看到按照 CSS Nesting Module ,Nesting 规范基本和预处理器如出一辙
/* Dropdown menu on hover */
ul {
  /* direct nesting (& MUST be the first part of selector)*/
  & > li {
    color: #000;

    & > ul { display: none; }

    &:hover {
      color: #f00;

      & > ul { display: block; }
    }
  }
}
复制代码

Module in CSS

  • 其实 CSS 很早就有了模块化方案,即 @import,使用 CSS 的 @import 规则可引用其余的文件样式,这特性从 IE 5.5 开始就被全部的浏览器支持,那为何一直以来使用者寥寥无几呢,缘由不少:
    • 在一些老的浏览器有加载顺序的 bug
    • 没法并行加载
    • 致使过多的请求数量
    • ...
  • 如今前端项目基本都使用构建工具(Gulp、Webpack 等)打包后再上线,所以以上哪些缺点也就不存在了,而在 Webpack 的 css-loader 中,是能够配置是否开启 @import 的

Selector Helpers

  • 除了上面介绍的一些主要特性,CSS 还提供了一些全新的特性来帮助更优雅的书写样式

:matches pseudo-class(已改名为 :is())

  • :matches() CSS 伪类函数将选择器列表做为参数,并选择该列表中任意一个选择器能够选择的元素,这对于以更紧凑的形式编写大型选择器很是有用,并且浏览器支持程度也已经接近 93%
  • 想要了解更多详情能够查看规范:Selectors Level 4
/* 语法 */
:matches( selector[, selector]* )

.nav:matches(.side,.top) .links:matches(:hover, :focus) {
  color: #BADA55;
}

/* 至关于如下代码 */
.nav.side .links:hover,
.nav.top  .links:hover,
.nav.side .links:focus,
.nav.top  .links:focus {
  color: #BADA55;
}
复制代码
  • @custom-selector
    • 还可以使用自定义选择器来定义能够匹配复杂选择器的别名
    /* 语法 */
    @custom-selector: <custom-selector> <selector-list>;
    复制代码
    • 定义的方式和 CSS 变量相似,使用起来稍微有点区别
    @custom-selector:--text-inputs input[type="text"], input[type="password"];
    
    :--text-inputs.disabled,
    :--text-inputs[disabled] {
      opacity: 0.5
    }
    
    /* 至关于如下代码 */
    input[type="text"].disabled,
    input[type="password"].disabled,
    input[type="text"][disabled],
    input[type="password"][disabled] {
      opacity: 0.5
    }
    复制代码

用起来

  • 尽管上述的 CSS 特性还处于不一样阶段,浏览器的支持程度也不尽相同,但使用 postcss-preset-env,就能够抢先尝试 CSS 的最新特性
  • postcss-preset-env 的配置也十分简单,以 Webpack 为例
rules: [
  {
    test: /\.css$/,
    use: [
      'style-loader',
      { loader: 'css-loader', options: { importLoaders: 1 } },
      { loader: 'postcss-loader', options: {
        ident: 'postcss',
        plugins: () => [
          postcssPresetEnv(/* pluginOptions */)
        ]
      } 
    ]
  } 
]
复制代码

总结

  • 通过一番梳理,尽管 CSS 在社区的刺激下加快了更新迭代的速度,但到目前为止依然达不到 CSS 预处理器 VS CSS 的地步,只能说在使用 CSS 预处理器时,也可在项目中尝试一些优秀的 CSS 新特性,即:CSS 预处理器 + CSS
  • 依然坚信,在 W3C 的推进下,随着 CSS 自身不断完善,CSS 预处理器终究会像当年的 CoffeScript 、Jade 同样变成时代的过渡产物,到那时也就不用纠结各类 CSS 预处理器的环境配置和技术选型等,直接打开编辑器,就能愉快的书写样式
相关文章
相关标签/搜索