Appearance
Sass 最佳实践
Sass最佳实践涵盖了从项目结构到代码编写,从性能优化到团队协作的各个方面。本章将详细介绍Sass开发中的最佳实践,帮助您创建高质量、可维护的样式代码。
项目结构最佳实践
7-1模式架构
scss
// 推荐的Sass项目结构 - 7-1模式
/*
src/scss/
├── abstracts/ # 抽象层 - 变量、函数、混合宏
│ ├── _variables.scss # Sass变量
│ ├── _functions.scss # Sass函数
│ ├── _mixins.scss # Sass混合宏
│ └── _placeholders.scss # Sass占位符
├── base/ # 基础层 - 重置、排版、通用样式
│ ├── _reset.scss # 样式重置
│ ├── _typography.scss # 排版样式
│ ├── _animations.scss # 动画定义
│ └── _helpers.scss # 辅助类
├── components/ # 组件层 - 可重用UI组件
│ ├── _buttons.scss # 按钮组件
│ ├── _cards.scss # 卡片组件
│ ├── _carousel.scss # 轮播组件
│ └── _forms.scss # 表单组件
├── layout/ # 布局层 - 页面布局组件
│ ├── _header.scss # 页眉
│ ├── _footer.scss # 页脚
│ ├── _navigation.scss # 导航
│ ├── _grid.scss # 网格系统
│ └── _sidebar.scss # 侧边栏
├── pages/ # 页面层 - 特定页面样式
│ ├── _home.scss # 首页
│ ├── _about.scss # 关于页
│ └── _contact.scss # 联系页
├── themes/ # 主题层 - 主题相关样式
│ ├── _default.scss # 默认主题
│ ├── _dark.scss # 暗色主题
│ └── _admin.scss # 管理员主题
└── vendor/ # 第三方库 - 重置外部样式
├── _bootstrap.scss # Bootstrap定制
└── _normalize.scss # Normalize.css
*/
// main.scss - 主入口文件
// 1. 抽象层(无CSS输出)
@use 'abstracts/variables';
@use 'abstracts/functions';
@use 'abstracts/mixins';
@use 'abstracts/placeholders';
// 2. 基础层(少量CSS输出)
@use 'base/reset';
@use 'base/typography';
@use 'base/animations';
@use 'base/helpers';
// 3. 布局层(结构CSS)
@use 'layout/grid';
@use 'layout/header';
@use 'layout/footer';
@use 'layout/navigation';
// 4. 组件层(独立组件)
@use 'components/buttons';
@use 'components/cards';
@use 'components/forms';
// 5. 页面层(特定页面)
@use 'pages/home';
@use 'pages/about';
// 6. 主题层(覆盖样式)
@use 'themes/default';
模块化组织
scss
// abstracts/_variables.scss - 统一变量管理
// 颜色系统
$colors: (
'primary': #007bff,
'primary-dark': #0056b3,
'primary-light': #80bdff,
'secondary': #6c757d,
'success': #28a745,
'info': #17a2b8,
'warning': #ffc107,
'danger': #dc3545,
'light': #f8f9fa,
'dark': #343a40
);
// 间距系统
$spacers: (
0: 0,
1: 0.25rem,
2: 0.5rem,
3: 1rem,
4: 1.5rem,
5: 3rem,
6: 6rem
);
// 断点系统
$grid-breakpoints: (
'xs': 0,
'sm': 576px,
'md': 768px,
'lg': 992px,
'xl': 1200px,
'xxl': 1400px
);
// 容器最大宽度
$container-max-widths: (
'sm': 540px,
'md': 720px,
'lg': 960px,
'xl': 1140px,
'xxl': 1320px
);
// 字体系统
$font-family-sans-serif: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
$font-sizes: (
1: 1.5rem,
2: 1.25rem,
3: 1.1rem,
4: 1rem,
5: 0.875rem,
6: 0.75rem
);
// 基础变量
$line-height-base: 1.5;
$border-width: 1px;
$border-color: #dee2e6;
$border-radius: 0.25rem;
$border-radius-lg: 0.3rem;
$border-radius-sm: 0.2rem;
命名规范最佳实践
BEM方法论
scss
// BEM (Block Element Modifier) 命名规范
.card { // Block: 独立的组件
background: white;
border-radius: $border-radius;
&__header { // Element: 属于card的元素
padding: 1rem;
border-bottom: 1px solid $border-color;
}
&__title { // Element: 属于card的标题
margin: 0;
font-size: 1.25rem;
}
&__body { // Element: 属于card的内容区
padding: 1rem;
}
&__footer { // Element: 属于card的底部
padding: 1rem;
border-top: 1px solid $border-color;
}
&--featured { // Modifier: card的变体
border: 2px solid $primary;
}
&--compact { // Modifier: card的紧凑变体
padding: 0.5rem;
}
&__title--highlighted { // Element Modifier: 特殊的标题
color: $primary;
font-weight: $font-weight-bold;
}
}
// 复杂组件示例
.nav {
display: flex;
&__item {
position: relative;
&:not(:first-child) {
margin-left: 1rem;
}
}
&__link {
display: block;
padding: 0.5rem 1rem;
text-decoration: none;
color: $gray-700;
&:hover {
color: $primary;
}
&--active {
color: $primary;
font-weight: $font-weight-bold;
}
}
&--vertical {
flex-direction: column;
.nav__item:not(:first-child) {
margin-left: 0;
margin-top: 0.5rem;
}
}
}
语义化命名
scss
// 语义化命名最佳实践
// 好的命名 - 描述功能
.user-avatar { }
.search-input { }
.product-card { }
.site-header { }
.main-navigation { }
// 避免的命名 - 描述外观
.red-text { } // 应该是 .error-message
.big-box { } // 应该是 .hero-section
.left-column { } // 应该是 .sidebar
// 状态类命名
.is-active { }
.is-hidden { }
.has-error { }
.requires-attention { }
// 工具类命名
.u-text-center { } // 工具类前缀 'u-'
.t-card-layout { } // 主题类前缀 't-'
.c-button { } // 组件类前缀 'c-'
.l-grid { } // 布局类前缀 'l-'
代码组织最佳实践
抽象层组织
scss
// abstracts/_functions.scss - 工具函数
@use 'variables' as vars;
// 颜色函数
@function color($key) {
@return map-get(vars.$colors, $key);
}
@function theme-color-level($color-name, $level) {
$color: map-get(vars.$colors, $color-name);
$color-base: if($color, $color, $color-name);
@if $level == 0 {
@return $color-base;
}
@if $level > 0 {
@return darken($color-base, $level * 8%);
}
@if $level < 0 {
@return lighten($color-base, abs($level) * 8%);
}
}
// 间距函数
@function spacer($level: 1) {
@return map-get(vars.$spacers, $level);
}
// 断点函数
@function breakpoint($name) {
@return map-get(vars.$grid-breakpoints, $name);
}
// 抽象s/_mixins.scss - 混合宏
// 响应式混合宏
@mixin media-breakpoint-up($name, $breakpoints: vars.$grid-breakpoints) {
$min: map-get($breakpoints, $name);
@if $min != 0 {
@media (min-width: $min) {
@content;
}
} @else {
@content;
}
}
@mixin media-breakpoint-down($name, $breakpoints: vars.$grid-breakpoints) {
$max: map-get($breakpoints, $name);
@media (max-width: $max - 1px) {
@content;
}
}
@mixin media-breakpoint-between($lower, $upper, $breakpoints: vars.$grid-breakpoints) {
@media (min-width: map-get($breakpoints, $lower)) and (max-width: map-get($breakpoints, $upper) - 1px) {
@content;
}
}
// 布局混合宏
@mixin center-block {
display: block;
margin-right: auto;
margin-left: auto;
}
@mixin clearfix {
&::after {
display: block;
clear: both;
content: "";
}
}
@mixin text-truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
// 视觉混合宏
@mixin box-shadow($level: 1) {
@if $level == 1 {
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
} @else if $level == 2 {
box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
} @else if $level == 3 {
box-shadow: 0 10px 20px rgba(0,0,0,0.19), 0 6px 6px rgba(0,0,0,0.23);
}
}
@mixin transition($property: all, $duration: 0.3s, $timing: ease) {
transition: $property $duration $timing;
}
// 可访问性混合宏
@mixin visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
@mixin sr-only-focusable {
&:active,
&:focus {
position: static !important;
width: auto !important;
height: auto !important;
overflow: visible !important;
clip: auto !important;
white-space: normal !important;
}
}
性能优化最佳实践
CSS输出优化
scss
// 避免生成冗余CSS
// 不推荐
.button {
@include button-style; // 可能包含大量样式
&.primary {
@include button-style; // 重复包含相同样式
background-color: $primary;
}
&.secondary {
@include button-style; // 再次重复
background-color: $secondary;
}
}
// 推荐
%button-base {
padding: 0.5rem 1rem;
border: none;
border-radius: $border-radius;
cursor: pointer;
font-size: $font-size-base;
}
.button {
@extend %button-base;
&.button--primary {
background-color: $primary;
color: white;
}
&.button--secondary {
background-color: $secondary;
color: white;
}
}
// 使用占位符减少CSS大小
%form-element {
display: block;
width: 100%;
padding: $input-padding-y $input-padding-x;
font-size: $font-size-base;
line-height: $input-line-height;
color: $input-color;
background-color: $input-bg;
background-clip: padding-box;
border: $input-border-width solid $input-border-color;
border-radius: $input-border-radius;
transition: border-color $input-transition, box-shadow $input-transition;
}
.form-control {
@extend %form-element;
&:focus {
color: $input-focus-color;
background-color: $input-focus-bg;
border-color: $input-focus-border-color;
outline: 0;
box-shadow: $input-focus-box-shadow;
}
}
避免深度嵌套
scss
// 不推荐:过度嵌套
.component {
.sub-component {
.element {
.item {
.detail {
// 生成非常长的选择器:.component .sub-component .element .item .detail
color: red;
}
}
}
}
}
// 推荐:使用语义化类名
.component-detail {
color: red;
}
// 或者使用BEM
.component {
&__detail {
color: red;
}
}
// 限制嵌套深度(建议不超过3层)
.card {
// 第层
.card-header { // 第二层
.card-title { // 第三层
// 避免再嵌套
}
}
}
组件开发最佳实践
可复用组件
scss
// components/_button.scss - 按钮组件
@use '../abstracts/variables' as vars;
@use '../abstracts/mixins' as mx;
// 按钮变量
$btn-padding-y: 0.5rem !default;
$btn-padding-x: 1rem !default;
$btn-font-size: vars.$font-size-base !default;
$btn-border-width: vars.$border-width !default;
$btn-line-height: vars.$line-height-base !default;
// 按钮基础样式
%btn {
display: inline-block;
font-family: vars.$font-family-base;
font-weight: vars.$font-weight-normal;
line-height: $btn-line-height;
color: vars.$body-color;
text-align: center;
text-decoration: if(vars.$link-decoration == none, null, none);
white-space: $btn-white-space;
vertical-align: middle;
user-select: none;
background-color: transparent;
border: $btn-border-width solid transparent;
@include mx.transition($btn-transition);
@include mx.box-shadow($btn-box-shadow);
@include mx.hover {
color: vars.$body-color;
text-decoration: if(vars.$link-hover-decoration == underline, none, null);
}
&:focus,
&.focus {
outline: 0;
box-shadow: $btn-focus-box-shadow;
}
// 追加禁用状态样式
&.disabled,
&:disabled {
opacity: $btn-disabled-opacity;
@include mx.box-shadow(none);
}
&:not(:disabled):not(.disabled) {
cursor: if($enable-pointer-cursor-for-buttons, pointer, null);
&:active,
&.active {
@include mx.box-shadow($btn-active-box-shadow);
&:focus {
@include mx.box-shadow($btn-focus-box-shadow, $btn-active-box-shadow);
}
}
}
}
// 按钮大小混合宏
@mixin button-size($padding-y, $padding-x, $font-size, $border-radius) {
padding: $padding-y $padding-x;
@include mx.font-size($font-size);
border-radius: $border-radius;
}
// 按钮变体混合宏
@mixin button-variant($background, $border, $color: color-contrast($background)) {
color: $color;
@include gradient-bg($background);
border-color: $border;
@include box-shadow($btn-box-shadow);
&:hover {
color: $color;
@include gradient-bg($hover-background);
border-color: $hover-border;
}
.btn-check:focus + &,
&:focus {
color: $color;
@include gradient-bg($hover-background);
border-color: $hover-border;
@if $enable-shadows {
@include box-shadow($btn-box-shadow, 0 0 0 $btn-focus-width rgba($border, .5));
} @else {
// Avoid using mixin so we can pass custom focus shadow properly
box-shadow: 0 0 0 $btn-focus-width rgba($border, .5);
}
}
.btn-check:checked + &,
.btn-check:active + &,
&:active,
&.active,
.show > &.dropdown-toggle {
color: $color;
background-color: $active-background;
// Remove CSS gradients if they're enabled
background-image: if($enable-gradients, none, null);
border-color: $active-border;
&:focus {
@if $enable-shadows and $btn-active-box-shadow != none {
@include box-shadow($btn-active-box-shadow, 0 0 0 $btn-focus-width rgba($border, .5));
} @else {
// Avoid using mixin so we can pass custom focus shadow properly
box-shadow: 0 0 0 $btn-focus-width rgba($border, .5);
}
}
}
&:disabled,
&.disabled {
color: $color;
background-color: $background;
// Remove CSS gradients if they're enabled
background-image: if($enable-gradients, none, null);
border-color: $border;
}
}
// 按钮组件
.btn {
@extend %btn;
@include button-size($btn-padding-y, $btn-padding-x, $btn-font-size, $btn-border-radius);
&--primary {
@include button-variant(vars.$primary, vars.$primary);
}
&--secondary {
@include button-variant(vars.$secondary, vars.$secondary);
}
&--large {
@include button-size($btn-padding-y * 1.5, $btn-padding-x * 1.5, $btn-font-size * 1.25, vars.$border-radius-lg);
}
&--small {
@include button-size($btn-padding-y * 0.75, $btn-padding-x * 0.75, $btn-font-size * 0.875, vars.$border-radius-sm);
}
}
响应式设计最佳实践
移动优先方法
scss
// 响应式设计 - 移动优先
@use '../abstracts/variables' as vars;
@use '../abstracts/mixins' as mx;
// 移动端优先的组件设计
.product-card {
// 基础样式(移动端)
padding: vars.$spacer-3;
border: vars.$border-width solid vars.$border-color;
border-radius: vars.$border-radius;
// 在较小屏幕上为单列布局
margin-bottom: vars.$spacer-3;
// 平板端优化
@include mx.media-breakpoint-up(sm) {
margin-bottom: vars.$spacer-4;
}
// 桌面端优化
@include mx.media-breakpoint-up(md) {
display: flex;
flex-direction: row;
.product-image {
flex: 0 0 auto;
width: 200px;
margin-right: vars.$spacer-3;
}
.product-details {
flex: 1;
min-width: 0; // 防止flex项溢出
}
}
// 大屏幕优化
@include mx.media-breakpoint-up(lg) {
.product-image {
width: 250px;
}
}
}
// 响应式网格系统
.container {
width: 100%;
padding-right: vars.$spacer-2;
padding-left: vars.$spacer-2;
margin-right: auto;
margin-left: auto;
@each $breakpoint, $container-max-width in vars.$container-max-widths {
@include mx.media-breakpoint-up($breakpoint) {
max-width: $container-max-width;
}
}
}
// 响应式间距
.responsive-spacing {
padding: vars.$spacer-2;
@include mx.media-breakpoint-up(md) {
padding: vars.$spacer-3;
}
@include mx.media-breakpoint-up(lg) {
padding: vars.$spacer-4;
}
}
// 流体排版
@mixin fluid-type($min-vw, $max-vw, $min-font-size, $max-font-size) {
$slope: ($max-font-size - $min-font-size) / ($max-vw - $min-vw);
$y-intercept: $min-font-size - $slope * $min-vw;
$unit1: unit($min-vw);
$unit2: unit($max-font-size);
@if $unit1 == $unit2 {
font-size: calc(#{$y-intercept}#{$unit2} + #{strip-units($slope) * 100}vw);
}
@media (max-width: #{$min-vw}) {
font-size: $min-font-size;
}
@media (min-width: #{$max-vw}) {
font-size: $max-font-size;
}
}
// 移除单位函数
@function strip-units($number) {
@return $number / ($number * 0 + 1);
}
.hero-title {
@include fluid-type(320px, 1200px, 1.5rem, 3rem);
}
代码质量最佳实践
注释规范
scss
// 文件头部注释
/*
* 组件: 按钮组件
* 功能: 定义各种按钮样式
* 作者: 开发团队
* 日期: 2023-01-01
* 版本: 1.0.0
*/
// 组件级别注释
// ========================================
// BUTTON COMPONENT
// ========================================
//
// 这个组件提供各种按钮样式
//
// 用法:
// <button class="btn btn--primary">主要按钮</button>
// <button class="btn btn--secondary">次要按钮</button>
//
// 变体:
// - btn--primary: 主要按钮
// - btn--secondary: 次要按钮
// - btn--large: 大号按钮
// - btn--small: 小号按钮
//
// 混合宏文档注释
/**
* 响应式字体大小混合宏
* 根据屏幕尺寸调整字体大小
*
* @param {Number} $min-size - 最小屏幕下的字体大小
* @param {Number} $max-size - 最大屏幕下的字体大小
* @param {Number} $min-width - 最小屏幕宽度 (默认: 320px)
* @param {Number} $max-width - 最大屏幕宽度 (默认: 1200px)
*
* @example
* @include responsive-font(14px, 18px);
*/
@mixin responsive-font($min-size, $max-size, $min-width: 320px, $max-width: 1200px) {
font-size: $min-size;
@media (min-width: $min-width) {
font-size: calc(#{$min-size} + (#{$max-size} - #{$min-size}) * ((100vw - #{$min-width}) / (#{$max-width} - #{$min-width})));
}
@media (min-width: $max-width) {
font-size: $max-size;
}
}
// 复杂逻辑注释
.button-group {
display: flex;
// 垂直居中对齐按钮
align-items: center;
// 水平分布按钮,使用flex-grow填充剩余空间
.button {
flex: 1;
&:not(:first-child) {
// 第一个按钮不需要左边距
margin-left: 0.25rem;
}
}
}
// 临时解决方案注释
.feature-in-progress {
// TODO: 实现完整的响应式行为 (ISSUE-123)
// FIXME: 修复在小屏幕上的布局问题 (ISSUE-124)
display: block;
}
代码审查检查清单
scss
// 代码审查检查清单
/*
SASS CODE REVIEW CHECKLIST:
✅ 命名规范
- [ ] 变量命名遵循连字符分隔
- [ ] 混合宏命名描述性
- [ ] 选择器命名遵循BEM规范
- [ ] 避免过长的选择器链
✅ 代码结构
- [ ] 文件结构遵循7-1模式
- [ ] 逻辑分组合理
- [ ] 注释清晰有用
- [ ] 避免深度嵌套(不超过3层)
✅ 性能考虑
- [ ] 使用占位符减少CSS大小
- [ ] 合理使用混合宏
- [ ] 避免生成冗余CSS
- [ ] 响应式设计高效
✅ 可维护性
- [ ] 代码易于理解
- [ ] 功能模块化
- [ ] 重复代码已抽象
- [ ] 变量和函数可重用
✅ 一致性
- [ ] 代码风格统一
- [ ] 变量使用一致
- [ ] 命名约定一致
- [ ] 注释风格一致
*/
安全和可访问性最佳实践
可访问性增强
scss
// 可访问性最佳实践
@use '../abstracts/variables' as vars;
@use '../abstracts/mixins' as mx;
// 焦点样式
%focusable {
&:focus {
outline: 0;
box-shadow: 0 0 0 0.2rem rgba(vars.$primary, 0.25);
}
}
// 屏幕阅读器辅助类
%sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.sr-only {
@extend %sr-only;
}
// 高对比度支持
%high-contrast {
@media (prefers-contrast: high) {
border-width: 2px;
border-style: solid;
}
}
// 减少动画偏好
%reduce-motion {
@media (prefers-reduced-motion: reduce) {
transition: none;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
}
}
// 颜色主题支持
%theme-aware {
color: vars.$body-color;
background-color: vars.$body-bg;
@media (prefers-color-scheme: dark) {
color: vars.$gray-100;
background-color: vars.$gray-900;
}
}
// 可访问的颜色对比
@function ensure-accessible-color($fg, $bg: white, $level: 'AA') {
$contrast: contrast-ratio($fg, $bg);
$required: if($level == 'AAA', 7, 4.5);
@if $contrast < $required {
@warn 'Insufficient color contrast: #{$contrast}, required: #{$required}';
}
@return $fg;
}
@function contrast-ratio($background, $foreground: white) {
$back-lum: luminance($background);
$fore-lum: luminance($foreground);
@return if($back-lum > $fore-lum,
($back-lum + 0.05) / ($fore-lum + 0.05),
($fore-lum + 0.05) / ($back-lum + 0.05)
);
}
@function luminance($color) {
$r: red($color) / 255;
$g: green($color) / 255;
$b: blue($color) / 255;
$r: if($r < 0.03928, $r / 12.92, pow(($r + 0.055) / 1.055, 2.4));
$g: if($g < 0.03928, $g / 12.92, pow(($g + 0.055) / 1.055, 2.4));
$b: if($b < 0.03928, $b / 12.92, pow(($b + 0.055) / 1.055, 2.4));
@return 0.2126 * $r + 0.7152 * $g + 0.0722 * $b;
}
// 使用可访问的颜色
.accessible-button {
$accessible-color: ensure-accessible-color(vars.$white, vars.$primary);
background-color: vars.$primary;
color: $accessible-color;
}
通过遵循这些最佳实践,您可以创建更加专业、可维护和高效的Sass代码。