CSS 预处理器(如 SCSS/Sass、Less、Stylus)为 CSS 带来了变量、嵌套、混入(Mixin)、函数等编程语言的特性,极大地提升了样式代码的可维护性和复用性。然而,预处理器的灵活性也意味着更多的格式化和规范挑战。
本文将讲解 CSS 预处理器的格式化规则、嵌套指南、Mixin 约定,以及格式化工具如何处理预处理器语法。
一、主流 CSS 预处理器概述
预处理器对比
| 特性 | SCSS/Sass | Less | Stylus |
|---|---|---|---|
| 文件扩展名 | .scss / .sass | .less | .styl |
| 语法风格 | 类似 CSS / 缩进式 | 类似 CSS | 灵活(可省略括号和冒号) |
| 变量语法 | $variable | @variable | variable |
| 嵌套支持 | 是 | 是 | 是 |
| Mixin | @mixin / @include | .mixin() | mixin() |
| 继承 | @extend | 不支持(需 mixin 模拟) | @extend |
| 运算 | 完整数学运算 | 基础运算 | 完整数学运算 |
| 条件语句 | @if / @else | when 守卫 | if / else |
| 循环 | @each / @for / @while | 不支持(需 JS 插件) | for / while |
| 社区规模 | 最大 | 中等 | 较小 |
| 格式化支持 | 优秀 | 良好 | 一般 |
为什么选择 SCSS?
SCSS 是目前最广泛使用的 CSS 预处理器,原因包括:
- CSS 超集:所有合法 CSS 都是合法 SCSS,迁移成本最低
- 生态完善:Bootstrap、Foundation 等主流框架均使用 SCSS
- 工具链成熟:Stylelint、Prettier、EditorConfig 等均提供完善支持
- 学习曲线平缓:渐进式使用,可以只学嵌套和变量
二、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 (条件) { } |
七、预处理器格式化检查清单
编码规范
| 检查项 | SCSS | Less |
|---|---|---|
| 变量命名规范 | kebab-case,$ 前缀 | kebab-case,@ 前缀 |
| Mixin 命名 | kebab-case | kebab-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 | 部分 | 部分 | 属性排序 |
| 本站格式化工具 | 完善 | 完善 | 支持压缩、排序、美化 |
使用建议
对于团队协作项目,建议:
- 编辑器配置:在项目中放置
.prettierrc和.editorconfig - Git Hook:使用
lint-staged在提交前自动格式化
// package.json
{
"lint-staged": {
"*.scss": [
"stylelint --fix",
"prettier --write"
],
"*.less": [
"prettier --write"
]
}
}
- 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
好的格式化规范不仅让代码更美观,更能提升团队协作效率和项目的可维护性。