作为一个前端开发者我们每天都要写大量的CSS。但是原生的CSS有很多不便比如不能用变量不能嵌套代码重复多维护困难,等。

CSS预处理器就是为了解决这些问题而诞生的。它在原生CSS的基础上增加了变量嵌套混合函数等特性让CSS写起来更优雅更高效更易维护。

目前最流行的CSS预处理器有Sass和Less。我自己在项目中经常使用Sass和Less今天就来分享一下CSS预处理器的实战教程希望能帮到大家。

什么是CSS预处理器

CSS预处理器是一种脚本语言它扩展了CSS的语法增加了变量嵌套混合函数等特性。然后通过编译工具把预处理器的代码编译成原生的CSS供浏览器使用。

简单来说CSS预处理器就是让你用更高级的语法写CSS然后编译成浏览器能识别的原生CSS。

CSS预处理器的好处

  1. 变量:可以定义变量比如颜色字体尺寸等方便统一管理和修改。
  2. 嵌套:可以嵌套选择器让代码结构更清晰更易读。
  3. 混合:可以定义可复用的代码块减少重复代码。
  4. 函数:可以定义函数进行计算和操作让CSS更灵活。
  5. 继承:可以继承其他选择器的样式减少重复代码。
  6. 模块化:可以把CSS分成多个文件模块化管理更易维护。

常见的CSS预处理器

  1. Sass:最流行的CSS预处理器功能强大生态完善有两种语法Sass(缩进语法),和SCSS(CSS兼容语法)。
  2. Less:另一个流行的CSS预处理器语法简单学习成本低和CSS很,像。
  3. Stylus:更灵活的CSS预处理器语法非常简洁但是相对小众。

Sass入门

Sass的安装

Sass是用Ruby写的所以需要先安装Ruby。不过现在也有Node.js版本的Sass更方便前端开发者使用。

使用Node.js安装Sass:

# 全局安装sass
npm install -g sass

# 查看版本
sass --version

也可以在项目中本地安装:

npm install sass --save-dev

Sass的编译

Sass文件的扩展名是,.scss或,.sass。.scss是CSS兼容语法和CSS很,像;.sass是缩进语法更简洁但是和CSS差别较大。推荐使用,.scss语法学习成本更低。

编译Sass文件:

# 编译单个文件
sass input.scss output.css

# 监听文件变化,自动编译
sass --watch input.scss:output.css

# 监听目录
sass --watch scss:css

# 输出压缩后的CSS
sass --style compressed input.scss output.css

也可以配合webpack,gulp等构建工具使用更方便。

Sass的基本语法

变量

Sass用,$,符号定义变量:

// 定义颜色变量
$primary-color: #333;
$secondary-color: #666;
$accent-color: #007bff;

// 定义字体变量
$font-family: "Helvetica Neue", Arial, sans-serif;
$font-size-base: 16px;
$font-size-lg: 18px;
$font-size-sm: 14px;

// 定义间距变量
$spacing-xs: 4px;
$spacing-sm: 8px;
$spacing-md: 16px;
$spacing-lg: 24px;
$spacing-xl: 32px;

// 使用变量
body {
  font-family: $font-family;
  font-size: $font-size-base;
  color: $primary-color;
}

.button {
  background-color: $accent-color;
  padding: $spacing-sm $spacing-md;
  font-size: $font-size-sm;
}

变量的好处是统一管理修改的时候只需要改一处就能全部生效。

嵌套

Sass支持选择器嵌套让代码结构更清晰:

// 原生CSS
.nav {
  list-style: none;
}
.nav li {
  display: inline-block;
}
.nav li a {
  color: #333;
  text-decoration: none;
}
.nav li a:hover {
  color: #007bff;
}

// Sass嵌套
.nav {
  list-style: none;

  li {
    display: inline-block;

    a {
      color: #333;
      text-decoration: none;

      &:hover {
        color: #007bff;
      }
    }
  }
}

注意,&,符号代表父选择器上面的,&:hover就相当于a:hover。

嵌套属性也可以:

// 原生CSS
.box {
  margin-top: 10px;
  margin-right: 20px;
  margin-bottom: 10px;
  margin-left: 20px;
}

// Sass嵌套属性
.box {
  margin: {
    top: 10px;
    right: 20px;
    bottom: 10px;
    left: 20px;
  }
}

混合(Mixin)

混合是Sass中非常强大的功能可以定义可复用的代码块:

// 定义混合
@mixin border-radius($radius) {
  -webkit-border-radius: $radius;
  -moz-border-radius: $radius;
  -ms-border-radius: $radius;
  border-radius: $radius;
}

// 使用混合
.box {
  @include border-radius(5px);
}

.button {
  @include border-radius(3px);
}

混合也可以有默认参数:

@mixin border-radius($radius: 5px) {
  border-radius: $radius;
}

.box {
  @include border-radius; // 使用默认值5px
}

.button {
  @include border-radius(3px); // 使用3px
}

混合也可以包含条件判断:

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

  @if $direction == up {
    border-left: $size solid transparent;
    border-right: $size solid transparent;
    border-bottom: $size solid $color;
  } @else if $direction == down {
    border-left: $size solid transparent;
    border-right: $size solid transparent;
    border-top: $size solid $color;
  } @else if $direction == left {
    border-top: $size solid transparent;
    border-bottom: $size solid transparent;
    border-right: $size solid $color;
  } @else if $direction == right {
    border-top: $size solid transparent;
    border-bottom: $size solid transparent;
    border-left: $size solid $color;
  }
}

.triangle-up {
  @include triangle(up, 10px, #333);
}

继承

Sass支持选择器继承用,@extend:

// 基础样式
.message {
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

// 继承基础样式
.success {
  @extend .message;
  border-color: green;
  color: green;
}

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

.warning {
  @extend .message;
  border-color: yellow;
  color: yellow;
}

编译后,.success,.error,.warning都会继承,.message的样式减少重复代码。

函数

Sass内置了很多函数也可以自定义函数:

// 内置函数
$color: #007bff;

.lighten-color {
  color: lighten($color, 20%); // 变亮20%
}

.darken-color {
  color: darken($color, 20%); // 变暗20%
}

.opacity-color {
  color: rgba($color, 0.5); // 设置透明度
}

// 自定义函数
@function calculate-width($columns, $gap) {
  @return percentage($columns / 12) - $gap;
}

.col-6 {
  width: calculate-width(6, 10px);
}

导入

Sass支持把CSS分成多个文件用,@import导入:

// 主文件style.scss
@import "variables"; // 导入_variables.scss
@import "mixins";    // 导入_mixins.scss
@import "base";      // 导入_base.scss
@import "components"; // 导入_components.scss

注意被导入的文件通常以下划线开头比如,_variables.scss这样Sass就不会单独编译这个文件。

条件判断和循环

Sass支持条件判断和循环:

// 条件判断
$theme: dark;

body {
  @if $theme == dark {
    background-color: #333;
    color: #fff;
  } @else {
    background-color: #fff;
    color: #333;
  }
}

// for循环
@for $i from 1 through 12 {
  .col-#{$i} {
    width: percentage($i / 12);
  }
}

// each循环
$colors: primary #007bff, success #28a745, danger #dc3545, warning #ffc107;

@each $name, $color in $colors {
  .text-#{$name} {
    color: $color;
  }
  .bg-#{$name} {
    background-color: $color;
  }
}

// while循环
$i: 1;
@while $i <= 5 {
  .mt-#{$i} {
    margin-top: $i * 4px;
  }
  $i: $i + 1;
}

Less入门

Less的安装

Less是用JavaScript写的可以在浏览器端使用也可以在Node.js环境编译。

使用Node.js安装Less:

# 全局安装less
npm install -g less

# 查看版本
lessc --version

Less的编译

Less文件的扩展名是,.less。

编译Less文件:

# 编译单个文件
lessc input.less output.css

# 输出压缩后的CSS
lessc --clean-css input.less output.css

也可以在浏览器端直接使用Less:

<link rel="stylesheet/less" type="text/css" href="styles.less" />
<script src="https://cdn.jsdelivr.net/npm/less@4"></script>

不过不推荐在生产环境使用浏览器端编译应该提前编译成CSS。

Less的基本语法

Less的语法和Sass很像但是更接近原生CSS。

变量

Less用,@,符号定义变量:

// 定义颜色变量
@primary-color: #333;
@secondary-color: #666;
@accent-color: #007bff;

// 定义字体变量
@font-family: "Helvetica Neue", Arial, sans-serif;
@font-size-base: 16px;

// 使用变量
body {
  font-family: @font-family;
  font-size: @font-size-base;
  color: @primary-color;
}

.button {
  background-color: @accent-color;
}

Less的变量还支持变量插值和变量变量:

// 变量插值
@property: color;

.box {
  @{property}: #333;
}

// 变量变量
@fnord: "I am fnord.";
@var: "fnord";

.content {
  content: @@var; // 输出 content: "I am fnord.";
}

嵌套

Less也支持选择器嵌套:

.nav {
  list-style: none;

  li {
    display: inline-block;

    a {
      color: #333;
      text-decoration: none;

      &:hover {
        color: #007bff;
      }
    }
  }
}

和Sass一样,&,代表父选择器。

混合(Mixin)

Less的混合更简单直接用类名就可以作为混合:

// 定义混合
.border-radius(@radius: 5px) {
  -webkit-border-radius: @radius;
  -moz-border-radius: @radius;
  border-radius: @radius;
}

// 使用混合
.box {
  .border-radius(10px);
}

.button {
  .border-radius(3px);
}

Less也支持不带参数的混合直接把一个类的样式混入另一个,类:

.centered {
  display: flex;
  justify-content: center;
  align-items: center;
}

.box {
  .centered; // 混入.centered的样式
  width: 100px;
  height: 100px;
}

Less也支持条件判断用when:

.mixin(@a) when (@a > 10) {
  background-color: black;
}

.mixin(@a) when (@a <= 10) {
  background-color: white;
}

.class1 {
  .mixin(15); // background-color: black;
}

.class2 {
  .mixin(5); // background-color: white;
}

继承

Less用,&:extend(),实现继承:

.message {
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.success {
  &:extend(.message);
  border-color: green;
  color: green;
}

.error {
  &:extend(.message);
  border-color: red;
  color: red;
}

函数

Less内置了很多函数:

@color: #007bff;

.lighten-color {
  color: lighten(@color, 20%);
}

.darken-color {
  color: darken(@color, 20%);
}

.opacity-color {
  color: fade(@color, 50%);
}

.mix-color {
  color: mix(@color, #fff, 50%);
}

Less也支持自定义函数但是比较复杂需要用JavaScript或者Less的插件机制。

导入

Less用,@import导入文件:

@import "variables.less";
@import "mixins.less";
@import "base.less";

Less的导入更灵活可以导入CSS文件也可以条件导入。

循环

Less本身不直接支持循环但是可以通过递归混合实现:

.generate-columns(@n, @i: 1) when (@i =< @n) {
  .col-@{i} {
    width: percentage(@i / @n);
  }
  .generate-columns(@n, (@i + 1));
}

.generate-columns(12);

Sass和Less的对比

特性SassLess
变量符号$@
混合定义@mixin直接定义
混合使用@include直接调用
继承@extend&:extend()
条件判断@if/@elsewhen
循环@for/@each/@while递归混合
函数支持自定义内置函数丰富自定义复杂
语法SCSS接近CSS,Sass缩进接近CSS
生态更成熟Compass等框架也不错Bootstrap使用
学习成本稍高较低

总的来说Sass功能更强大生态更成熟适合大型项目;Less语法更简单学习成本更低适合小型项目和初学者。

实战:用Sass写一个响应式布局

下面我用一个简单的例子演示如何用Sass写一个响应式布局。

项目结构

project/
├── scss/
│   ├── _variables.scss   # 变量
│   ├── _mixins.scss      # 混合
│   ├── _base.scss        # 基础样式
│   ├── _layout.scss      # 布局样式
│   ├── _components.scss  # 组件样式
│   └── style.scss        # 主文件
└── css/
    └── style.css         # 编译后的CSS

_variables.scss

// 颜色
$primary-color: #007bff;
$secondary-color: #6c757d;
$success-color: #28a745;
$danger-color: #dc3545;
$warning-color: #ffc107;
$info-color: #17a2b8;
$light-color: #f8f9fa;
$dark-color: #343a40;

// 文字颜色
$text-color: #333;
$text-color-light: #666;
$text-color-muted: #999;

// 背景颜色
$bg-color: #fff;
$bg-color-light: #f5f5f5;

// 字体
$font-family-sans: "Helvetica Neue", Arial, sans-serif;
$font-family-serif: Georgia, "Times New Roman", serif;
$font-family-mono: "Courier New", monospace;
$font-size-base: 16px;
$font-size-lg: 18px;
$font-size-sm: 14px;
$font-size-xs: 12px;
$line-height-base: 1.5;

// 间距
$spacing-xs: 4px;
$spacing-sm: 8px;
$spacing-md: 16px;
$spacing-lg: 24px;
$spacing-xl: 32px;
$spacing-xxl: 48px;

// 边框
$border-color: #dee2e6;
$border-radius: 4px;
$border-radius-lg: 8px;

// 断点
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
$breakpoint-xl: 1200px;

// 容器宽度
$container-max-width: 1200px;

_mixins.scss

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

// 圆角
@mixin border-radius($radius: $border-radius) {
  -webkit-border-radius: $radius;
  -moz-border-radius: $radius;
  border-radius: $radius;
}

// 阴影
@mixin box-shadow($shadow...) {
  -webkit-box-shadow: $shadow;
  -moz-box-shadow: $shadow;
  box-shadow: $shadow;
}

// 过渡
@mixin transition($transition...) {
  -webkit-transition: $transition;
  -moz-transition: $transition;
  transition: $transition;
}

// 变换
@mixin transform($transform) {
  -webkit-transform: $transform;
  -moz-transform: $transform;
  -ms-transform: $transform;
  transform: $transform;
}

// 弹性布局居中
@mixin flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}

// 文本截断
@mixin text-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

// 多行文本截断
@mixin text-clamp($lines: 2) {
  display: -webkit-box;
  -webkit-line-clamp: $lines;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

// 响应式媒体查询
@mixin respond-to($breakpoint) {
  @if $breakpoint == sm {
    @media (min-width: $breakpoint-sm) {
      @content;
    }
  } @else if $breakpoint == md {
    @media (min-width: $breakpoint-md) {
      @content;
    }
  } @else if $breakpoint == lg {
    @media (min-width: $breakpoint-lg) {
      @content;
    }
  } @else if $breakpoint == xl {
    @media (min-width: $breakpoint-xl) {
      @content;
    }
  }
}

_base.scss

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  font-size: $font-size-base;
  -webkit-text-size-adjust: 100%;
}

body {
  font-family: $font-family-sans;
  font-size: $font-size-base;
  line-height: $line-height-base;
  color: $text-color;
  background-color: $bg-color;
}

a {
  color: $primary-color;
  text-decoration: none;
  @include transition(color 0.3s ease);

  &:hover {
    color: darken($primary-color, 10%);
    text-decoration: underline;
  }
}

img {
  max-width: 100%;
  height: auto;
  vertical-align: middle;
}

ul, ol {
  list-style: none;
}

h1, h2, h3, h4, h5, h6 {
  font-weight: 600;
  line-height: 1.2;
  margin-bottom: $spacing-md;
}

h1 { font-size: 2.5rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.75rem; }
h4 { font-size: 1.5rem; }
h5 { font-size: 1.25rem; }
h6 { font-size: 1rem; }

p {
  margin-bottom: $spacing-md;
}

_layout.scss

.container {
  width: 100%;
  max-width: $container-max-width;
  margin: 0 auto;
  padding: 0 $spacing-md;
}

.row {
  @include clearfix;
  margin: 0 (-$spacing-md / 2);
}

.col {
  float: left;
  padding: 0 ($spacing-md / 2);
  width: 100%;

  @include respond-to(md) {
    &.col-6 { width: 50%; }
    &.col-4 { width: 33.333333%; }
    &.col-3 { width: 25%; }
    &.col-8 { width: 66.666667%; }
    &.col-9 { width: 75%; }
  }
}

// 头部
.header {
  background-color: $bg-color;
  border-bottom: 1px solid $border-color;
  padding: $spacing-md 0;

  .header-inner {
    @include clearfix;
  }

  .logo {
    float: left;
    font-size: $font-size-lg;
    font-weight: 600;
    color: $primary-color;
  }

  .nav {
    float: right;

    li {
      display: inline-block;
      margin-left: $spacing-lg;

      a {
        color: $text-color;

        &:hover {
          color: $primary-color;
        }
      }
    }
  }
}

// 主内容
.main {
  padding: $spacing-xl 0;
}

// 侧边栏
.sidebar {
  padding: $spacing-lg;
  background-color: $bg-color-light;
  @include border-radius;
}

// 底部
.footer {
  background-color: $dark-color;
  color: $light-color;
  padding: $spacing-xl 0;
  text-align: center;

  a {
    color: $light-color;

    &:hover {
      color: $primary-color;
    }
  }
}

_components.scss

// 按钮
.button {
  display: inline-block;
  padding: $spacing-sm $spacing-md;
  font-size: $font-size-sm;
  font-weight: 500;
  text-align: center;
  vertical-align: middle;
  cursor: pointer;
  border: 1px solid transparent;
  @include border-radius;
  @include transition(all 0.3s ease);

  &:hover {
    text-decoration: none;
  }

  &.button-primary {
    background-color: $primary-color;
    color: #fff;

    &:hover {
      background-color: darken($primary-color, 10%);
    }
  }

  &.button-secondary {
    background-color: $secondary-color;
    color: #fff;

    &:hover {
      background-color: darken($secondary-color, 10%);
    }
  }

  &.button-outline {
    background-color: transparent;
    border-color: $primary-color;
    color: $primary-color;

    &:hover {
      background-color: $primary-color;
      color: #fff;
    }
  }

  &.button-lg {
    padding: $spacing-md $spacing-lg;
    font-size: $font-size-base;
  }

  &.button-sm {
    padding: $spacing-xs $spacing-sm;
    font-size: $font-size-xs;
  }
}

// 卡片
.card {
  background-color: $bg-color;
  border: 1px solid $border-color;
  @include border-radius($border-radius-lg);
  @include box-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
  overflow: hidden;
  @include transition(box-shadow 0.3s ease);

  &:hover {
    @include box-shadow(0 4px 12px rgba(0, 0, 0, 0.15));
  }

  .card-header {
    padding: $spacing-md $spacing-lg;
    border-bottom: 1px solid $border-color;
    background-color: $bg-color-light;
    font-weight: 600;
  }

  .card-body {
    padding: $spacing-lg;
  }

  .card-footer {
    padding: $spacing-md $spacing-lg;
    border-top: 1px solid $border-color;
    background-color: $bg-color-light;
  }

  .card-title {
    font-size: $font-size-lg;
    font-weight: 600;
    margin-bottom: $spacing-sm;
  }

  .card-text {
    color: $text-color-light;
    margin-bottom: 0;
    @include text-clamp(3);
  }
}

// 标签
.tag {
  display: inline-block;
  padding: 2px $spacing-sm;
  font-size: $font-size-xs;
  color: $text-color-light;
  background-color: $bg-color-light;
  @include border-radius;
  margin-right: $spacing-xs;
  margin-bottom: $spacing-xs;

  &.tag-primary {
    background-color: $primary-color;
    color: #fff;
  }

  &.tag-success {
    background-color: $success-color;
    color: #fff;
  }

  &.tag-warning {
    background-color: $warning-color;
    color: $dark-color;
  }

  &.tag-danger {
    background-color: $danger-color;
    color: #fff;
  }
}

// 分页
.pagination {
  @include clearfix;
  margin: $spacing-lg 0;

  li {
    display: inline-block;
    margin-right: $spacing-xs;

    a {
      display: block;
      padding: $spacing-xs $spacing-sm;
      border: 1px solid $border-color;
      @include border-radius;
      color: $text-color;
      @include transition(all 0.3s ease);

      &:hover {
        background-color: $primary-color;
        color: #fff;
        border-color: $primary-color;
      }
    }

    &.active a {
      background-color: $primary-color;
      color: #fff;
      border-color: $primary-color;
    }

    &.disabled a {
      color: $text-color-muted;
      cursor: not-allowed;

      &:hover {
        background-color: transparent;
        color: $text-color-muted;
        border-color: $border-color;
      }
    }
  }
}

style.scss

// 导入所有模块
@import "variables";
@import "mixins";
@import "base";
@import "layout";
@import "components";

编译

sass --watch scss/style.scss:css/style.css --style compressed

这样我们就用Sass写了一个完整的响应式布局的基础框架。代码结构清晰模块化易维护比原生CSS高效很多。

写在最后

CSS预处理器是前端开发的利器它能让我们写CSS更高效更优雅更易维护。不管是Sass还是Less都值得学习和使用。

这篇文章介绍了CSS预处理器的基本概念Sass和Less的基本语法以及一个实战例子。希望能帮到大家入门CSS预处理器。

当然CSS预处理器的内容还有很多比如Compass框架Sass的高级函数Less的插件等这篇文章只是入门介绍。如果大家想深入学习可以去看官方文档或者相关的书籍和教程。

最后记住:工具只是辅助关键还是在于写好CSS的思想和习惯。 即使用了CSS预处理器如果没有好的CSS架构和命名规范代码还是会很乱很难维护。

希望大家都能用好CSS预处理器写出优雅高效易维护的CSS。

如果大家有什么问题或者经验欢迎在评论区留言和我一起讨论。