CSS / Less / Sass Review Guide
CSS 及预处理器代码审查指南,覆盖性能、可维护性、响应式设计和浏览器兼容性。
/* ❌ 硬编码 - 难以维护 */
.button {
background : # 3b82f6 ;
border-radius : 8px ;
}
.card {
border : 1px solid # 3b82f6 ;
border-radius : 8px ;
}
/* ✅ 使用 CSS 变量 */
: root {
--color-primary : # 3b82f6 ;
--radius-md : 8px ;
}
.button {
background : var (--color-primary );
border-radius : var (--radius-md );
}
.card {
border : 1px solid var (--color-primary );
border-radius : var (--radius-md );
}
/* 推荐的变量分类 */
: root {
/* 颜色 */
--color-primary : # 3b82f6 ;
--color-primary-hover : # 2563eb ;
--color-text : # 1f2937 ;
--color-text-muted : # 6b7280 ;
--color-bg : # ffffff ;
--color-border : # e5e7eb ;
/* 间距 */
--spacing-xs : 4px ;
--spacing-sm : 8px ;
--spacing-md : 16px ;
--spacing-lg : 24px ;
--spacing-xl : 32px ;
/* 字体 */
--font-size-sm : 14px ;
--font-size-base : 16px ;
--font-size-lg : 18px ;
--font-weight-normal : 400 ;
--font-weight-bold : 700 ;
/* 圆角 */
--radius-sm : 4px ;
--radius-md : 8px ;
--radius-lg : 12px ;
--radius-full : 9999px ;
/* 阴影 */
--shadow-sm : 0 1px 2px rgba (0 , 0 , 0 , 0.05 );
--shadow-md : 0 4px 6px rgba (0 , 0 , 0 , 0.1 );
/* 过渡 */
--transition-fast : 150ms ease;
--transition-normal : 300ms ease;
}
/* ✅ 组件级变量 - 减少全局污染 */
.card {
--card-padding : var (--spacing-md );
--card-radius : var (--radius-md );
padding : var (--card-padding );
border-radius : var (--card-radius );
}
/* ⚠️ 避免频繁用 JS 动态修改变量 - 影响性能 */
/* ✅ 工具类 - 明确需要覆盖 */
.hidden { display : none !important ; }
.sr-only { position : absolute !important ; }
/* ✅ 覆盖第三方库样式(无法修改源码时) */
.third-party-modal {
z-index : 9999 !important ;
}
/* ✅ 打印样式 */
@media print {
.no-print { display : none !important ; }
}
/* ❌ 解决特异性问题 - 应该重构选择器 */
.button {
background : blue !important ; /* 为什么需要 !important? */
}
/* ❌ 覆盖自己写的样式 */
.card { padding : 20px ; }
.card { padding : 30px !important ; } /* 直接修改原规则 */
/* ❌ 在组件样式中 */
.my-component .title {
font-size : 24px !important ; /* 破坏组件封装 */
}
/* 问题:需要覆盖 .btn 的样式 */
/* ❌ 使用 !important */
.my-btn {
background : red !important ;
}
/* ✅ 提高特异性 */
button .my-btn {
background : red;
}
/* ✅ 使用更具体的选择器 */
.container .my-btn {
background : red;
}
/* ✅ 使用 :where() 降低被覆盖样式的特异性 */
: where (.btn ) {
background : blue; /* 特异性为 0 */
}
.my-btn {
background : red; /* 可以正常覆盖 */
}
🔴 [ blocking] "发现 15 处 !important,请说明每处的必要性"
🟡 [ important] "这个 !important 可以通过调整选择器特异性来解决"
💡 [ suggestion] "考虑使用 CSS Layers (@layer ) 来管理样式优先级"
/* ❌ 性能杀手 - 浏览器检查所有可动画属性 */
.button {
transition : all 0.3s ease;
}
/* ✅ 明确指定属性 */
.button {
transition : background-color 0.3s ease, transform 0.3s ease;
}
/* ✅ 多属性时使用变量 */
.button {
--transition-duration : 0.3s ;
transition :
background-color var (--transition-duration ) ease,
box-shadow var (--transition-duration ) ease,
transform var (--transition-duration ) ease;
}
/* ❌ 每帧触发重绘 - 严重影响性能 */
.card {
box-shadow : 0 2px 4px rgba (0 , 0 , 0 , 0.1 );
transition : box-shadow 0.3s ease;
}
.card : hover {
box-shadow : 0 8px 16px rgba (0 , 0 , 0 , 0.2 );
}
/* ✅ 使用伪元素 + opacity */
.card {
position : relative;
}
.card ::after {
content : '' ;
position : absolute;
inset : 0 ;
box-shadow : 0 8px 16px rgba (0 , 0 , 0 , 0.2 );
opacity : 0 ;
transition : opacity 0.3s ease;
pointer-events : none;
border-radius : inherit;
}
.card : hover ::after {
opacity : 1 ;
}
/* ❌ 动画这些属性会触发布局重计算 */
.bad-animation {
transition : width 0.3s , height 0.3s , top 0.3s , left 0.3s , margin 0.3s ;
}
/* ✅ 只动画 transform 和 opacity(仅触发合成) */
.good-animation {
transition : transform 0.3s , opacity 0.3s ;
}
/* 位移用 translate 代替 top/left */
.move {
transform : translateX (100px ); /* ✅ */
/* left: 100px; */ /* ❌ */
}
/* 缩放用 scale 代替 width/height */
.grow {
transform : scale (1.1 ); /* ✅ */
/* width: 110%; */ /* ❌ */
}
/* ❌ 过深的嵌套 - 选择器匹配慢 */
.page .container .content .article .section .paragraph span {
color : red;
}
/* ✅ 扁平化 */
.article-text {
color : red;
}
/* ❌ 通配符选择器 */
* { box-sizing : border-box; } /* 影响所有元素 */
[class *= "icon-" ] { display : inline; } /* 属性选择器较慢 */
/* ✅ 限制范围 */
.icon-box * { box-sizing : border-box; }
/* ⚠️ 复杂阴影影响渲染性能 */
.heavy-shadow {
box-shadow :
0 1px 2px rgba (0 , 0 , 0 , 0.1 ),
0 2px 4px rgba (0 , 0 , 0 , 0.1 ),
0 4px 8px rgba (0 , 0 , 0 , 0.1 ),
0 8px 16px rgba (0 , 0 , 0 , 0.1 ),
0 16px 32px rgba (0 , 0 , 0 , 0.1 ); /* 5 层阴影 */
}
/* ⚠️ 滤镜消耗 GPU */
.blur-heavy {
filter : blur (20px ) brightness (1.2 ) contrast (1.1 );
backdrop-filter : blur (10px ); /* 更消耗性能 */
}
/* 使用 will-change 提示浏览器(谨慎使用) */
.animated-element {
will-change : transform, opacity;
}
/* 动画完成后移除 will-change */
.animated-element .idle {
will-change : auto;
}
/* 使用 contain 限制重绘范围 */
.card {
contain : layout paint; /* 告诉浏览器内部变化不影响外部 */
}
/* ✅ Mobile First - 基础样式针对移动端 */
.container {
padding : 16px ;
display : flex;
flex-direction : column;
}
/* 逐步增强 */
@media (min-width : 768px ) {
.container {
padding : 24px ;
flex-direction : row;
}
}
@media (min-width : 1024px ) {
.container {
padding : 32px ;
max-width : 1200px ;
margin : 0 auto;
}
}
/* ❌ Desktop First - 需要覆盖更多样式 */
.container {
max-width : 1200px ;
padding : 32px ;
flex-direction : row;
}
@media (max-width : 1023px ) {
.container {
padding : 24px ;
}
}
@media (max-width : 767px ) {
.container {
padding : 16px ;
flex-direction : column;
max-width : none;
}
}
/* 推荐断点(基于内容而非设备) */
: root {
--breakpoint-sm : 640px ; /* 大手机 */
--breakpoint-md : 768px ; /* 平板竖屏 */
--breakpoint-lg : 1024px ; /* 平板横屏/小笔记本 */
--breakpoint-xl : 1280px ; /* 桌面 */
--breakpoint-2xl : 1536px ; /* 大桌面 */
}
/* 使用示例 */
@media (min-width : 768px ) { /* md */ }
@media (min-width : 1024px ) { /* lg */ }
/* ❌ 固定宽度 */
.container {
width : 1200px ;
}
/* ✅ 最大宽度 + 弹性 */
.container {
width : 100% ;
max-width : 1200px ;
padding-inline : 16px ;
}
/* ❌ 固定高度的文本容器 */
.text-box {
height : 100px ; /* 文字可能溢出 */
}
/* ✅ 最小高度 */
.text-box {
min-height : 100px ;
}
/* ❌ 小触摸目标 */
.small-button {
padding : 4px 8px ; /* 太小,难以点击 */
}
/* ✅ 足够的触摸区域 */
.touch-button {
min-height : 44px ;
min-width : 44px ;
padding : 12px 16px ;
}
特性
兼容性
建议
CSS Grid
现代浏览器 ✅
IE 需要 Autoprefixer + 测试
Flexbox
广泛支持 ✅
旧版需要前缀
CSS Variables
现代浏览器 ✅
IE 不支持,需要回退
gap (flexbox)
较新 ⚠️
Safari 14.1+
:has()
较新 ⚠️
Firefox 121+
container queries
较新 ⚠️
2023 年后的浏览器
@layer
较新 ⚠️
检查目标浏览器
/* CSS 变量回退 */
.button {
background : # 3b82f6 ; /* 回退值 */
background : var (--color-primary ); /* 现代浏览器 */
}
/* Flexbox gap 回退 */
.flex-container {
display : flex;
gap : 16px ;
}
/* 旧浏览器回退 */
.flex-container > * + * {
margin-left : 16px ;
}
/* Grid 回退 */
.grid {
display : flex;
flex-wrap : wrap;
}
@supports (display : grid) {
.grid {
display : grid;
grid-template-columns : repeat (auto-fit, minmax (200px , 1fr ));
}
}
// postcss.config.js
module . exports = {
plugins : [
require ( 'autoprefixer' ) ( {
// 根据 browserslist 配置
grid : 'autoplace' , // 启用 Grid 前缀(IE 支持)
flexbox : 'no-2009' , // 只用现代 flexbox 语法
} ) ,
] ,
} ;
// package.json
{
"browserslist" : [
"> 1%" ,
"last 2 versions" ,
"not dead" ,
"not ie 11" // 根据项目需求
]
}
/* ❌ 过深嵌套 - 编译后选择器过长 */
.page {
.container {
.content {
.article {
.title {
color : red ; // 编译为 .page .container .content .article .title
}
}
}
}
}
/* ✅ 最多 3 层 */
.article {
& __title {
color : red ;
}
& __content {
p { margin-bottom : 1em ; }
}
}
/* 变量 - 用于单个值 */
$primary-color : #3b82f6 ;
/* Mixin - 用于可配置的代码块 */
@mixin button-variant ($bg , $text ) {
background : $bg ;
color : $text ;
& :hover {
background : darken ($bg , 10% );
}
}
/* Extend - 用于共享相同样式(谨慎使用) */
%visually-hidden {
position : absolute ;
width : 1px ;
height : 1px ;
overflow : hidden ;
clip : rect (0 , 0 , 0 , 0 );
}
.sr-only {
@extend %visually-hidden ;
}
/* ⚠️ @extend 的问题 */
// 可能产生意外的选择器组合
// 不能在 @media 中使用
// 优先使用 mixin
□ transition: all
□ 动画 width/height/top/left/margin
□ 大量 !important
□ 硬编码的颜色/间距重复 >3 次
□ 选择器嵌套 >4 层
□ 缺少响应式处理
□ 使用 Desktop First
□ 复杂 box-shadow 被动画
□ 缺少浏览器兼容回退
□ CSS 变量作用域过大
□ 可以使用 CSS Grid 简化布局
□ 可以使用 CSS 变量提取重复值
□ 可以使用 @layer 管理优先级
□ 可以添加 contain 优化性能