编码规范
2025年3月5日大约 12 分钟
编码规范
良好的Sass编码规范可以提高代码的可读性、可维护性和团队协作效率。本文将介绍Sass项目的目录结构、命名约定、格式化规则以及最佳实践。
7-1模式目录结构
7-1模式是一种流行的Sass项目结构组织方式,包含7个文件夹和1个主文件:
sass/
|
|– abstracts/
| |– _variables.scss # 变量
| |– _functions.scss # 函数
| |– _mixins.scss # 混入
| |– _placeholders.scss # 占位符
|
|– base/
| |– _reset.scss # 重置/标准化
| |– _typography.scss # 排版规则
|
|– components/
| |– _buttons.scss # 按钮
| |– _carousel.scss # 轮播
| |– _dropdown.scss # 下拉菜单
|
|– layout/
| |– _navigation.scss # 导航
| |– _grid.scss # 网格系统
| |– _header.scss # 头部
| |– _footer.scss # 底部
| |– _forms.scss # 表单
|
|– pages/
| |– _home.scss # 首页样式
| |– _about.scss # 关于页样式
|
|– themes/
| |– _theme.scss # 默认主题
| |– _admin.scss # 管理员主题
|
|– vendors/
| |– _bootstrap.scss # Bootstrap
| |– _jquery-ui.scss # jQuery UI
|
|– main.scss # 主Sass文件
各目录职责
- abstracts/: 包含项目中使用的所有Sass工具和辅助文件,不应输出任何CSS
- base/: 包含项目的基础样式,如重置、排版、动画等
- components/: 包含所有UI组件的样式,每个组件一个文件
- layout/: 包含网站主要部分的布局样式
- pages/: 包含特定页面的样式
- themes/: 包含不同主题的样式
- vendors/: 包含来自外部库和框架的CSS文件
主文件组织
main.scss
文件应该只包含导入语句,不包含实际的样式定义:
// 抽象部分
@use 'abstracts/variables';
@use 'abstracts/functions';
@use 'abstracts/mixins';
@use 'abstracts/placeholders';
// 第三方库
@use 'vendors/bootstrap';
@use 'vendors/jquery-ui';
// 基础样式
@use 'base/reset';
@use 'base/typography';
// 布局样式
@use 'layout/navigation';
@use 'layout/grid';
@use 'layout/header';
@use 'layout/footer';
@use 'layout/forms';
// 组件样式
@use 'components/buttons';
@use 'components/carousel';
@use 'components/dropdown';
// 页面样式
@use 'pages/home';
@use 'pages/about';
// 主题样式
@use 'themes/theme';
@use 'themes/admin';
命名约定
文件命名
- 所有部分文件以下划线开头(例如:
_variables.scss
) - 使用连字符分隔多个单词(例如:
_color-palette.scss
) - 使用描述性名称,清晰表达文件内容
选择器命名
BEM命名法
BEM(Block, Element, Modifier)是一种流行的CSS命名约定:
// Block
.card {
// ...
// Element
&__header {
// ...
}
&__body {
// ...
}
// Modifier
&--featured {
// ...
}
}
命名空间前缀
使用命名空间前缀增加选择器的可读性:
l-
: 布局组件(例如:l-grid
,l-container
)c-
: UI组件(例如:c-button
,c-modal
)u-
: 实用工具类(例如:u-hidden
,u-text-center
)is-
,has-
: 状态类(例如:is-active
,has-error
)js-
: JavaScript钩子(例如:js-toggle
,js-slider
)t-
: 主题(例如:t-dark
,t-light
)
// 布局组件
.l-grid {
display: grid;
grid-template-columns: repeat(12, 1fr);
}
// UI组件
.c-button {
// 按钮样式
&.is-active {
// 激活状态
}
}
// 实用工具类
.u-text-center {
text-align: center;
}
变量命名
- 使用连字符分隔单词
- 使用有意义的前缀分组相关变量
- 使用命名空间避免冲突
// 颜色
$color-primary: #007bff;
$color-secondary: #6c757d;
$color-success: #28a745;
// 字体
$font-family-sans: 'Helvetica Neue', Arial, sans-serif;
$font-family-serif: Georgia, 'Times New Roman', serif;
$font-family-mono: Menlo, Monaco, Consolas, monospace;
// 断点
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
$breakpoint-xl: 1200px;
// Z-index层级
$z-index-dropdown: 1000;
$z-index-sticky: 1020;
$z-index-modal: 1050;
$z-index-tooltip: 1070;
混入和函数命名
- 使用描述性动词命名混入
- 使用驼峰式命名法命名函数
- 为参数提供默认值
// 混入命名
@mixin create-gradient($start-color, $end-color, $direction: to right) {
background: linear-gradient($direction, $start-color, $end-color);
}
// 函数命名
@function calculateRem($size) {
$remSize: $size / 16px;
@return #{$remSize}rem;
}
格式化规则
缩进和空格
- 使用2个空格进行缩进(不使用制表符)
- 在操作符前后添加空格
- 在冒号后添加空格,冒号前不加空格
- 在逗号后添加空格,逗号前不加空格
.element {
display: block;
margin: 0 auto;
padding: 10px 15px;
border: 1px solid $color-border;
&:hover {
background-color: rgba(0, 0, 0, 0.1);
}
}
声明顺序
按照一定的顺序组织属性声明,提高可读性:
- 定位属性(position, top, right, z-index等)
- 盒模型属性(display, float, width, height, margin, padding等)
- 排版属性(font, line-height, text-align等)
- 视觉属性(color, background, border, opacity等)
- 其他属性(cursor, overflow等)
.element {
// 定位
position: absolute;
top: 0;
left: 0;
z-index: 10;
// 盒模型
display: block;
width: 100px;
height: 100px;
margin: 10px;
padding: 10px;
// 排版
font-family: $font-family-sans;
font-size: 16px;
line-height: 1.5;
text-align: center;
// 视觉
color: $color-text;
background-color: $color-background;
border: 1px solid $color-border;
border-radius: 4px;
// 其他
cursor: pointer;
overflow: hidden;
}
嵌套规则
- 限制嵌套深度,一般不超过3层
- 避免不必要的嵌套
- 使用空行分隔嵌套的规则
// 良好的嵌套实践
.card {
border: 1px solid #ddd;
border-radius: 4px;
&__header {
padding: 15px;
border-bottom: 1px solid #ddd;
h2 {
margin: 0;
font-size: 18px;
}
}
&__body {
padding: 15px;
}
}
// 避免过度嵌套
.card {
// ...
}
.card__header {
// ...
}
.card__body {
// ...
}
注释规范
文件头注释
每个文件开头应包含描述性注释:
//------------------------------------------------------
// 按钮组件
// 包含所有按钮样式和变体
//------------------------------------------------------
// 按钮变量
$button-padding: 10px 15px;
$button-border-radius: 4px;
区块注释
使用区块注释分隔代码的主要部分:
//------------------------------------------------------
// 主要按钮
//------------------------------------------------------
.button-primary {
// ...
}
//------------------------------------------------------
// 次要按钮
//------------------------------------------------------
.button-secondary {
// ...
}
行内注释
使用行内注释解释特定的声明:
.element {
position: relative; // 为绝对定位子元素创建上下文
z-index: 1; // 确保在其他元素之上
// 使用flexbox居中内容
display: flex;
justify-content: center;
align-items: center;
}
最佳实践
使用变量存储常用值
将常用值存储在变量中,便于维护和更新:
// 定义颜色变量
$color-primary: #007bff;
$color-secondary: #6c757d;
$color-success: #28a745;
$color-danger: #dc3545;
// 定义间距变量
$spacing-xs: 4px;
$spacing-sm: 8px;
$spacing-md: 16px;
$spacing-lg: 24px;
$spacing-xl: 32px;
// 使用变量
.alert {
padding: $spacing-md;
margin-bottom: $spacing-lg;
&--success {
color: darken($color-success, 10%);
background-color: lighten($color-success, 40%);
border: 1px solid $color-success;
}
}
创建可重用的混入
为常用模式创建混入,减少重复代码:
// 创建媒体查询混入
@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; }
}
}
// 使用混入
.container {
width: 100%;
padding: 0 $spacing-md;
@include respond-to(md) {
max-width: 720px;
margin: 0 auto;
}
@include respond-to(lg) {
max-width: 960px;
}
}
使用函数进行计算
创建函数处理复杂计算:
// 像素转换为rem
@function rem($pixels) {
@return ($pixels / 16px) * 1rem;
}
// 计算网格列宽
@function column-width($columns, $total-columns: 12) {
@return percentage($columns / $total-columns);
}
// 使用函数
.heading {
font-size: rem(24px);
margin-bottom: rem(16px);
}
.col-6 {
width: column-width(6);
}
避免魔法数字
避免使用无上下文的数字,使用有意义的变量代替:
// 不推荐
.element {
top: 37px;
left: 42px;
z-index: 999;
width: 314px;
}
// 推荐
$header-height: 60px;
$sidebar-width: 250px;
$z-index-dropdown: 1000;
$content-max-width: 800px;
.element {
top: $header-height / 2 + 7px;
left: $sidebar-width / 6;
z-index: $z-index-dropdown - 1;
width: $content-max-width - 486px;
}
模块化导入
使用模块化方式导入Sass文件:
// 使用@use代替@import
@use 'abstracts/variables' as vars;
@use 'abstracts/mixins' as mix;
.element {
color: vars.$color-primary;
@include mix.respond-to(md) {
display: flex;
}
}
代码审查清单
在提交代码前,检查以下几点:
- 文件组织:文件是否放在正确的目录中?
- 命名规范:选择器、变量、混入和函数是否遵循命名约定?
- 代码格式:缩进、空格和换行是否一致?
- 注释:是否有适当的注释说明?
- 性能:是否有可能导致性能问题的代码?
- 可维护性:代码是否易于理解和维护?
代码审查工具
使用自动化工具辅助代码审查:
# 安装stylelint及其Sass插件
npm install --save-dev stylelint stylelint-scss stylelint-config-standard-scss
配置.stylelintrc.json
:
{
"extends": "stylelint-config-standard-scss",
"rules": {
"indentation": 2,
"selector-class-pattern": "^[a-z]([a-z0-9-])*(__([a-z0-9]+-?)+)?(--([a-z0-9]+-?)+)?$",
"max-nesting-depth": 3,
"selector-max-compound-selectors": 4,
"selector-no-qualifying-type": true,
"property-no-vendor-prefix": true,
"declaration-block-no-duplicate-properties": true,
"no-descending-specificity": true
}
}
团队协作规范
版本控制
在使用Git等版本控制系统时,遵循以下规范:
- 忽略编译后的文件:在
.gitignore
中添加编译后的CSS文件 - 分支管理:为不同的功能或修复创建独立分支
- 提交信息:使用清晰的提交信息描述更改
# .gitignore示例
.sass-cache/
*.css.map
*.sass.map
*.scss.map
*.css
文档化
为项目创建样式指南,帮助团队成员理解和使用样式系统:
- 组件文档:为每个组件创建使用文档,包括示例和变体
- 变量清单:维护一个变量清单,说明每个变量的用途
- 样式指南:创建交互式样式指南,展示所有UI组件
样式指南示例(使用Storybook或Pattern Lab):
// .storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
};
代码评审流程
建立团队代码评审流程:
- 提交前自查:使用上述清单自查代码
- 同行评审:由团队成员进行代码评审
- 自动化检查:使用CI/CD流程中的自动化工具检查代码
- 定期回顾:定期回顾和改进编码规范
实际案例
变量组织示例
// _variables.scss
// 颜色系统
// 主色调
$color-primary: #007bff;
$color-primary-light: lighten($color-primary, 15%);
$color-primary-dark: darken($color-primary, 15%);
// 辅助色
$color-secondary: #6c757d;
$color-success: #28a745;
$color-info: #17a2b8;
$color-warning: #ffc107;
$color-danger: #dc3545;
// 中性色
$color-white: #ffffff;
$color-gray-100: #f8f9fa;
$color-gray-200: #e9ecef;
$color-gray-300: #dee2e6;
$color-gray-400: #ced4da;
$color-gray-500: #adb5bd;
$color-gray-600: #6c757d;
$color-gray-700: #495057;
$color-gray-800: #343a40;
$color-gray-900: #212529;
$color-black: #000000;
// 功能色
$color-text-primary: $color-gray-900;
$color-text-secondary: $color-gray-600;
$color-text-muted: $color-gray-500;
$color-link: $color-primary;
$color-border: $color-gray-300;
$color-background: $color-white;
$color-background-light: $color-gray-100;
// 排版
$font-family-base: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
$font-family-heading: $font-family-base;
$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
$font-size-base: 1rem;
$font-size-sm: $font-size-base * 0.875;
$font-size-lg: $font-size-base * 1.25;
$font-weight-light: 300;
$font-weight-normal: 400;
$font-weight-bold: 700;
$line-height-base: 1.5;
$line-height-sm: 1.25;
$line-height-lg: 2;
// 间距
$spacing-base: 1rem;
$spacing-xs: $spacing-base * 0.25; // 4px
$spacing-sm: $spacing-base * 0.5; // 8px
$spacing-md: $spacing-base; // 16px
$spacing-lg: $spacing-base * 1.5; // 24px
$spacing-xl: $spacing-base * 3; // 48px
// 断点
$breakpoint-xs: 0;
$breakpoint-sm: 576px;
$breakpoint-md: 768px;
$breakpoint-lg: 992px;
$breakpoint-xl: 1200px;
// Z-index层级
$z-index-dropdown: 1000;
$z-index-sticky: 1020;
$z-index-fixed: 1030;
$z-index-modal-backdrop: 1040;
$z-index-modal: 1050;
$z-index-popover: 1060;
$z-index-tooltip: 1070;
// 边框
$border-width: 1px;
$border-color: $color-border;
$border-radius: 0.25rem;
$border-radius-sm: 0.2rem;
$border-radius-lg: 0.3rem;
$border-radius-pill: 50rem;
// 阴影
$box-shadow-sm: 0 0.125rem 0.25rem rgba($color-black, 0.075);
$box-shadow: 0 0.5rem 1rem rgba($color-black, 0.15);
$box-shadow-lg: 0 1rem 3rem rgba($color-black, 0.175);
混入库示例
// _mixins.scss
// 清除浮动
@mixin clearfix {
&::after {
content: "";
display: table;
clear: both;
}
}
// 截断文本
@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 media-up($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; }
}
}
@mixin media-down($breakpoint) {
@if $breakpoint == xs {
@media (max-width: $breakpoint-sm - 1) { @content; }
} @else if $breakpoint == sm {
@media (max-width: $breakpoint-md - 1) { @content; }
} @else if $breakpoint == md {
@media (max-width: $breakpoint-lg - 1) { @content; }
} @else if $breakpoint == lg {
@media (max-width: $breakpoint-xl - 1) { @content; }
}
}
// 居中元素
@mixin center-block {
display: block;
margin-left: auto;
margin-right: auto;
}
// 绝对定位居中
@mixin absolute-center {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
// 创建三角形
@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;
}
}
// 创建渐变
@mixin gradient($start-color, $end-color, $direction: to right) {
background: $start-color;
background: linear-gradient($direction, $start-color, $end-color);
}
// 创建网格
@mixin grid($columns: 12, $gap: $spacing-md) {
display: grid;
grid-template-columns: repeat($columns, 1fr);
gap: $gap;
}
// 创建Flex容器
@mixin flex-container($direction: row, $wrap: nowrap, $justify: flex-start, $align: stretch) {
display: flex;
flex-direction: $direction;
flex-wrap: $wrap;
justify-content: $justify;
align-items: $align;
}
组件示例
// _buttons.scss
//------------------------------------------------------
// 按钮组件
// 包含所有按钮样式和变体
//------------------------------------------------------
// 基础按钮样式
.c-button {
display: inline-block;
font-weight: $font-weight-normal;
text-align: center;
white-space: nowrap;
vertical-align: middle;
user-select: none;
border: $border-width solid transparent;
padding: $spacing-sm $spacing-md;
font-size: $font-size-base;
line-height: $line-height-base;
border-radius: $border-radius;
transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
// 禁用状态
&:disabled,
&.is-disabled {
opacity: 0.65;
pointer-events: none;
}
// 尺寸变体
&--sm {
padding: $spacing-xs $spacing-sm;
font-size: $font-size-sm;
border-radius: $border-radius-sm;
}
&--lg {
padding: $spacing-md $spacing-lg;
font-size: $font-size-lg;
border-radius: $border-radius-lg;
}
// 主要按钮
&--primary {
color: $color-white;
background-color: $color-primary;
border-color: $color-primary;
&:hover,
&:focus {
color: $color-white;
background-color: darken($color-primary, 7.5%);
border-color: darken($color-primary, 10%);
}
&:active {
background-color: darken($color-primary, 10%);
border-color: darken($color-primary, 12.5%);
}
}
// 次要按钮
&--secondary {
color: $color-white;
background-color: $color-secondary;
border-color: $color-secondary;
&:hover,
&:focus {
color: $color-white;
background-color: darken($color-secondary, 7.5%);
border-color: darken($color-secondary, 10%);
}
&:active {
background-color: darken($color-secondary, 10%);
border-color: darken($color-secondary, 12.5%);
}
}
// 轮廓按钮
&--outline {
background-color: transparent;
&.c-button--primary {
color: $color-primary;
border-color: $color-primary;
&:hover,
&:focus {
color: $color-white;
background-color: $color-primary;
}
}
&.c-button--secondary {
color: $color-secondary;
border-color: $color-secondary;
&:hover,
&:focus {
color: $color-white;
background-color: $color-secondary;
}
}
}
// 链接按钮
&--link {
font-weight: $font-weight-normal;
color: $color-link;
background-color: transparent;
border-color: transparent;
&:hover,
&:focus {
color: darken($color-link, 15%);
text-decoration: underline;
background-color: transparent;
border-color: transparent;
}
}
// 块级按钮
&--block {
display: block;
width: 100%;
}
}
// 按钮组
.c-button-group {
display: inline-flex;
.c-button {
position: relative;
&:not(:first-child) {
margin-left: -$border-width;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
&:not(:last-child) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&:hover,
&:focus,
&:active {
z-index: 1;
}
}
}
总结
遵循一致的Sass编码规范对于提高代码质量、团队协作效率和项目可维护性至关重要。本文介绍的规范包括:
- 目录结构:使用7-1模式组织Sass文件
- 命名约定:采用BEM命名法和命名空间前缀
- 格式化规则:统一的缩进、空格和声明顺序
- 注释规范:文件头注释、区块注释和行内注释
- 最佳实践:使用变量、混入和函数,避免魔法数字
- 代码审查:使用清单和自动化工具进行代码审查
- 团队协作:版本控制、文档化和代码评审流程
通过实施这些规范,团队可以创建更加一致、高效和可维护的Sass代码库。
参考资源
- Sass Guidelines - 由Hugo Giraudel编写的全面Sass指南
- Airbnb CSS/Sass Styleguide - Airbnb的CSS和Sass风格指南
- Sass 7-1 Pattern - 7-1模式的示例项目
- BEM Methodology - BEM命名方法论官方文档
- stylelint - 强大的CSS Linter工具