CSS 预处理器与格式化

工具相关 ·

CSS 预处理器(如 SCSS/Sass、Less、Stylus)为 CSS 带来了变量、嵌套、混入(Mixin)、函数等编程语言的特性,极大地提升了样式代码的可维护性和复用性。然而,预处理器的灵活性也意味着更多的格式化和规范挑战。

本文将讲解 CSS 预处理器的格式化规则、嵌套指南、Mixin 约定,以及格式化工具如何处理预处理器语法。


一、主流 CSS 预处理器概述

预处理器对比

特性SCSS/SassLessStylus
文件扩展名.scss / .sass.less.styl
语法风格类似 CSS / 缩进式类似 CSS灵活(可省略括号和冒号)
变量语法$variable@variablevariable
嵌套支持是是是
Mixin@mixin / @include.mixin()mixin()
继承@extend不支持(需 mixin 模拟)@extend
运算完整数学运算基础运算完整数学运算
条件语句@if / @elsewhen 守卫if / else
循环@each / @for / @while不支持(需 JS 插件)for / while
社区规模最大中等较小
格式化支持优秀良好一般

为什么选择 SCSS?

SCSS 是目前最广泛使用的 CSS 预处理器,原因包括:

  1. CSS 超集:所有合法 CSS 都是合法 SCSS,迁移成本最低
  2. 生态完善:Bootstrap、Foundation 等主流框架均使用 SCSS
  3. 工具链成熟:Stylelint、Prettier、EditorConfig 等均提供完善支持
  4. 学习曲线平缓:渐进式使用,可以只学嵌套和变量

二、SCSS 格式化规范

基础格式化规则

// ==========================================
// 文件头注释(描述文件用途)
// ==========================================

// -----------------------------------------
// 变量定义
// -----------------------------------------

// 颜色变量
$color-primary:       #007bff;
$color-secondary:     #6c757d;
$color-success:       #28a745;
$color-danger:        #dc3545;
$color-warning:       #ffc107;
$color-info:          #17a2b8;

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

// 字体变量
$font-family-base:    -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
$font-size-base:      16px;
$font-size-sm:        14px;
$font-size-lg:        18px;
$line-height-base:    1.5;

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

变量对齐规范

变量定义时,使用对齐来提升可读性:

// 好的:对齐冒号后的值
$color-red:     #ff0000;
$color-green:   #00ff00;
$color-blue:    #0000ff;
$color-yellow:  #ffff00;

// 不好的:未对齐
$color-red: #ff0000;
$color-green: #00ff00;
$color-blue: #0000ff;
$color-yellow: #ffff00;

嵌套规范

// 好的:清晰的嵌套结构,使用 & 连接
.nav {
  display: flex;
  align-items: center;
  gap: $spacing-md;

  // 子元素
  &__item {
    position: relative;
    padding: $spacing-sm $spacing-md;

    // 伪类
    &:hover {
      background: rgba($color-primary, 0.1);
    }

    &--active {
      color: $color-primary;
      font-weight: 600;

      &::after {
        content: '';
        position: absolute;
        bottom: 0;
        left: 0;
        right: 0;
        height: 2px;
        background: $color-primary;
      }
    }
  }

  &__link {
    color: inherit;
    text-decoration: none;
  }
}

// 不好的:嵌套过深,使用后代选择器
.nav {
  .nav-item {
    .nav-link {
      &:hover {
        .nav-icon {
          color: red; // 4层嵌套
        }
      }
    }
  }
}

嵌套深度控制规则

嵌套层级适用场景示例
0(顶层)基础样式、变量定义body { }, $var: value;
1 层组件子元素(BEM Element).card { &__title { } }
2 层状态/变体(BEM Modifier).card { &__title { &--large { } } }
3 层伪元素/伪类&__title { &::before { } }
4 层以上不推荐应考虑扁平化

三、Mixin 编写规范

什么是 Mixin?

Mixin 是可以复用的代码块,类似于函数,可以接收参数并生成 CSS 代码。

Mixin 命名规范

// 命名规则:
// 1. 使用 kebab-case(短横线连接)
// 2. 名称应描述功能而非实现
// 3. 响应式 mixin 使用 respond-to 前缀

// 好的命名
@mixin flex-center { }
@mixin text-truncate { }
@mixin respond-to($breakpoint) { }
@mixin button-variant($bg, $color) { }

// 不好的命名
@mixin center { }           // 太模糊
@mixin fc { }               // 缩写不清晰
@mixin r($bp) { }           // 名称过短
@mixin btn($a, $b) { }      // 参数名无意义

常用 Mixin 示例

// ===== 布局 Mixin =====

/// 弹性居中
/// @param {String} $direction [row] - flex 方向
@mixin flex-center($direction: row) {
  display: flex;
  flex-direction: $direction;
  align-items: center;
  justify-content: center;
}

/// 文本截断(单行省略号)
@mixin text-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

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

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

/// 响应式断点
/// @param {String} $breakpoint - 断点名称
@mixin respond-to($breakpoint) {
  $value: map-get($breakpoints, $breakpoint);

  @if $value {
    @media (min-width: $value) {
      @content;
    }
  } @else {
    @warn "断点 '#{$breakpoint}' 未定义";
  }
}

// ===== 组件 Mixin =====

/// 按钮变体
/// @param {Color} $bg - 背景色
/// @param {Color} $color [white] - 文字颜色
/// @param {Color} $border [$bg] - 边框颜色
@mixin button-variant($bg, $color: white, $border: $bg) {
  background: $bg;
  color: $color;
  border-color: $border;

  &:hover {
    background: darken($bg, 8%);
    border-color: darken($border, 8%);
  }

  &:active {
    background: darken($bg, 12%);
    border-color: darken($border, 12%);
  }

  &:disabled {
    background: lighten($bg, 20%);
    border-color: lighten($border, 20%);
    cursor: not-allowed;
    opacity: 0.65;
  }
}

/// 卡片样式
/// @param {String} $size [md] - 尺寸(sm, md, lg)
@mixin card($size: md) {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);

  @if $size == sm {
    padding: 12px;
  } @else if $size == md {
    padding: 20px;
  } @else if $size == lg {
    padding: 32px;
  }
}

Mixin 使用规范

// 使用示例
.card {
  @include card(md);

  &--featured {
    border: 2px solid $color-primary;
  }
}

.btn-primary {
  @include button-variant($color-primary);
}

.btn-success {
  @include button-variant($color-success);
}

.title {
  @include text-truncate;
  max-width: 300px;
}

.description {
  @include text-clamp(3);
}

// 响应式使用
.sidebar {
  display: none;

  @include respond-to(md) {
    display: block;
    width: 280px;
  }

  @include respond-to(xl) {
    width: 320px;
  }
}

四、文件组织规范

目录结构

styles/
├── abstracts/          # 抽象层(不会直接输出 CSS)
│   ├── _variables.scss
│   ├── _mixins.scss
│   ├── _functions.scss
│   └── _placeholders.scss
├── base/               # 基础样式
│   ├── _reset.scss
│   ├── _typography.scss
│   └── _base.scss
├── components/         # 组件样式
│   ├── _button.scss
│   ├── _card.scss
│   ├── _modal.scss
│   └── _nav.scss
├── layout/             # 布局样式
│   ├── _header.scss
│   ├── _footer.scss
│   ├── _sidebar.scss
│   └── _grid.scss
├── pages/              # 页面特定样式
│   ├── _home.scss
│   └── _contact.scss
├── themes/             # 主题样式
│   ├── _light.scss
│   └── _dark.scss
├── utils/              # 工具类
│   ├── _helpers.scss
│   └── _animations.scss
└── main.scss           # 主入口文件

入口文件(main.scss)

// ==========================================
// 主入口文件
// ==========================================

// 1. 抽象层(变量、Mixin、函数)
@import 'abstracts/variables';
@import 'abstracts/mixins';
@import 'abstracts/functions';
@import 'abstracts/placeholders';

// 2. 基础样式
@import 'base/reset';
@import 'base/typography';
@import 'base/base';

// 3. 布局
@import 'layout/header';
@import 'layout/footer';
@import 'layout/sidebar';
@import 'layout/grid';

// 4. 组件
@import 'components/button';
@import 'components/card';
@import 'components/modal';
@import 'components/nav';

// 5. 页面
@import 'pages/home';
@import 'pages/contact';

// 6. 主题
@import 'themes/light';
@import 'themes/dark';

// 7. 工具类
@import 'utils/helpers';
@import 'utils/animations';

五、格式化工具与预处理器

Prettier 配置

// .prettierrc
{
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "semi": true,
  "singleQuote": true,
  "trailingComma": "es5",
  "bracketSpacing": true,
  "overrides": [
    {
      "files": "*.scss",
      "options": {
        "parser": "scss",
        "singleQuote": true,
        "printWidth": 80
      }
    }
  ]
}

Stylelint 配置

// .stylelintrc.json
{
  "extends": [
    "stylelint-config-standard-scss",
    "stylelint-config-recess-order"
  ],
  "rules": {
    "scss/dollar-variable-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
    "scss/at-mixin-pattern": "^[a-z][a-z0-9]*(-[a-z0-9]+)*$",
    "scss/at-import-no-partial-leading-underscore": true,
    "max-nesting-depth": 3,
    "selector-max-id": 0,
    "selector-class-pattern": null,
    "scss/dollar-variable-colon-space-after": "always",
    "scss/dollar-variable-empty-line-before": [
      "always",
      {
        "except": ["first-nested"],
        "ignore": ["after-comment", "inside-single-line-block"]
      }
    ],
    "scss/at-rule-conditional-no-parentheses": true,
    "no-duplicate-selectors": true,
    "declaration-block-no-duplicate-properties": true,
    "shorthand-property-no-redundant-values": true,
    "color-hex-length": "short",
    "length-zero-no-unit": true
  }
}

格式化前后对比

格式化前

// 混乱的代码
$colorPrimary:#007bff;
$color-secondary :  #6c757d ;
$font-size-base:16px;

@mixin flexCenter() {
  display: flex;
  align-items:center;
  justify-content:  center;
}

.card{
  background:white;
  border-radius:  8px;
  box-shadow:0 2px 4px rgba(0,0,0,0.1);
  padding: 16px;

  .card-title{
    font-size: 18px;
    font-weight:  600;
    color:  $colorPrimary;
    margin-bottom:  12px;
  }

  .card-body{
    font-size: $font-size-base;
    color:  #333;
    line-height:  1.6;
  }

  .card-footer{
    margin-top:  16px;
    padding-top:  12px;
    border-top:  1px solid #eee;
  }
}

格式化后

$color-primary: #007bff;
$color-secondary: #6c757d;
$font-size-base: 16px;

@mixin flex-center {
  display: flex;
  align-items: center;
  justify-content: center;
}

.card {
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  padding: 16px;

  .card-title {
    font-size: 18px;
    font-weight: 600;
    color: $color-primary;
    margin-bottom: 12px;
  }

  .card-body {
    font-size: $font-size-base;
    color: #333;
    line-height: 1.6;
  }

  .card-footer {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid #eee;
  }
}

六、Less 格式化要点

Less 特有格式规范

// Less 变量使用 @ 前缀
@color-primary: #007bff;
@color-secondary: #6c757d;
@spacing-base: 16px;

// Less Mixin(使用括号调用)
.flex-center() {
  display: flex;
  align-items: center;
  justify-content: center;
}

// Mixin 带参数
.button-variant(@bg; @color: white) {
  background: @bg;
  color: @color;
  border: 1px solid darken(@bg, 5%);

  &:hover {
    background: darken(@bg, 8%);
  }
}

// 使用
.card {
  .flex-center();
  padding: @spacing-base;
  background: white;
  border-radius: 8px;
}

.btn-primary {
  .button-variant(@color-primary);
}

Less 格式化注意事项

注意点说明
变量前缀Less 使用 @,避免与 @media 等混淆
Mixin 调用使用 .mixin() 带括号调用
分号分隔参数Less Mixin 参数使用分号(;)分隔
避免与 CSS at-rule 冲突@ 前缀可能与 CSS 原生 at-rule 冲突
Guard 语法.mixin() when (条件) { }

七、预处理器格式化检查清单

编码规范

检查项SCSSLess
变量命名规范kebab-case,$ 前缀kebab-case,@ 前缀
Mixin 命名kebab-casekebab-case
嵌套深度不超过 3 层不超过 3 层
& 引用使用仅用于子元素和伪类仅用于子元素和伪类
注释格式/// 文档注释,// 单行/// 和 //
文件导入使用 _ 前缀和 @import使用 @import
运算空格($a + $b) 需要空格(@a + @b) 需要空格
属性排序遵循统一顺序遵循统一顺序

性能优化

// 1. 避免在循环中生成大量选择器
@each $color-name, $color-value in $colors {
  .text-#{$color-name} {
    color: $color-value;
  }
}

// 2. 使用 @extend 时要谨慎(会增加选择器复杂度)
// 不好:@extend 会复制所有相关规则
.error {
  @extend .message; // 可能产生大量选择器
}

// 更好:使用 Mixin
@mixin message-base {
  padding: 12px;
  border-radius: 4px;
}

.message {
  @include message-base;
}

.error {
  @include message-base;
  color: $color-danger;
}

// 3. 合理使用变量避免重复编译
$base-font-size: 16px;
$base-line-height: 1.5;

// 4. 使用 map 管理复杂配置
$breakpoints: (
  'sm': 576px,
  'md': 768px,
  'lg': 992px,
  'xl': 1200px
);

$theme-colors: (
  'primary':   #007bff,
  'secondary': #6c757d,
  'success':   #28a745,
  'danger':    #dc3545,
  'warning':   #ffc107,
  'info':      #17a2b8
);

八、在线格式化工具对预处理器的支持

工具支持情况

工具SCSS 支持Less 支持特点
Prettier完善不支持最流行的代码格式化器
Stylelint完善通过插件可自定义规则
CSScomb部分部分属性排序
本站格式化工具完善完善支持压缩、排序、美化

使用建议

对于团队协作项目,建议:

  1. 编辑器配置:在项目中放置 .prettierrc 和 .editorconfig
  2. Git Hook:使用 lint-staged 在提交前自动格式化
// package.json
{
  "lint-staged": {
    "*.scss": [
      "stylelint --fix",
      "prettier --write"
    ],
    "*.less": [
      "prettier --write"
    ]
  }
}
  1. CI 检查:在 CI 流程中加入格式化和 lint 检查
# .github/workflows/lint.yml
name: Lint
on: [push, pull_request]
jobs:
  stylelint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npx stylelint "src/**/*.scss"

总结

CSS 预处理器为样式开发带来了强大的能力,但也需要严格的格式化规范来保持代码质量:

  • 统一命名:变量、Mixin 使用一致的命名风格
  • 控制嵌套:嵌套深度不超过 3 层,优先使用扁平化
  • 规范 Mixin:Mixin 应有文档注释和明确的参数
  • 合理组织文件:按照抽象层、基础、组件、布局分层
  • 自动化格式:使用 Prettier + Stylelint 保持代码一致
  • 性能意识:谨慎使用 @extend,合理使用变量和 Map

好的格式化规范不仅让代码更美观,更能提升团队协作效率和项目的可维护性。

阅读 22