我在真实项目中总结的CSS规范实践与避坑指南
我的写法,亲测靠谱
先说结论:我项目里现在基本不用全局 class 名了。不是因为 BEM 多高级,而是真被 .btn 和 .title 给坑麻了。
以前我写个按钮,随手就是:
.btn {
padding: 8px 16px;
border-radius: 4px;
background: #007bff;
color: white;
}
结果某天运营提需求:“首页的按钮要圆角大一点,加个阴影;后台列表里的按钮要扁平化,hover 变灰。” 我改完 .btn,发现登录页的按钮也变了……再查 DOM,发现是另一个组件库也定义了 .btn,还用了 !important —— 那一刻,我盯着控制台缓存清了三遍。
后来我统一改成带命名空间的写法,比如:.user-card__btn、.article-list__action-btn。不图多炫酷,就图它不会被误伤。
具体怎么落地?我用的是 BEM 的简化版(不严格套规则,但思路照搬):
/* ✅ 推荐:模块名 + 元素名,层级清晰 */
.product-card {
border: 1px solid #e0e0e0;
border-radius: 8px;
}
.product-card__image {
width: 100%;
height: 180px;
object-fit: cover;
}
.product-card__title {
font-size: 16px;
font-weight: 600;
margin: 12px 0 8px;
}
.product-card__price {
color: #d32f2f;
font-weight: bold;
}
好处是什么?一目了然谁属于谁;二来,你删一个模块的 CSS,几乎不影响别的地方;三来,团队新人看代码,扫一眼 class 名就知道这玩意儿在哪个组件里。
注意:我不写 .product-card__btn--primary 这种修饰符。太绕,而且容易写漏。需要变体?直接拆成 .product-card__buy-btn 和 .product-card__add-to-cart-btn,语义更直白,也方便后续加 JS 钩子。
这几种错误写法,别再踩坑了
下面这几个,我在三个不同项目里都见过,每次修复都得花半天理样式优先级……
- 滥用 ID 选择器:
#header看着干净,但一旦页面里多个 header(比如弹窗里嵌了个小 header),ID 就撞了。更糟的是,CSS 中 ID 的权重是 100,比 class 高太多,后期想覆盖它,只能靠!important或者再塞个 ID —— 恶性循环开始了。 - 过度嵌套:
.page .section .content .item .text这种写法我一度以为很“严谨”,结果改个结构,全崩。现在我只要看到嵌套超过三层,就立刻问自己:是不是该抽个新 class? - 用标签名当样式钩子:
ul li a { color: #333; }—— 表面看没问题,但哪天产品说“这个列表里要加个 icon 图标”,你加个,它也会被上面这条样式影响。稳妥做法是:所有可样式化的元素,必须有 class。 - 直接写内联样式或 style 属性:曾经有同事为了快速改个颜色,直接写
style="color: red",然后忘了删。上线后用户反馈“标题全红了”,我们翻了二十个文件才定位到那一行。现在我团队约定:除非是动画临时态(比如 loading 时的 opacity 变化),否则禁用内联样式。
实际项目中的坑
第一坑:伪类顺序。我写了好几年 :hover,直到某次 a:hover 不生效,才发现它被 a:visited 盖住了。浏览器对 :link、:visited、:hover、:active 有固定顺序要求(LVHA),记不住?那就统一写成:
a {
color: #007bff;
}
a:visited {
color: #5a5a5a;
}
a:hover {
color: #0056b3;
text-decoration: underline;
}
a:active {
color: #004080;
}
第二坑:移动端的 rem 基准。我一开始用 document.documentElement.style.fontSize = window.innerWidth / 375 * 16 + 'px',结果 iOS Safari 横屏切回来,字体炸了。后来改用 viewport + media query 控制根字号,稳定多了:
html {
font-size: 16px;
}
@media screen and (min-width: 375px) {
html { font-size: calc(100vw / 375 * 16); }
}
@media screen and (min-width: 414px) {
html { font-size: calc(100vw / 414 * 16); }
}
第三坑:CSS 变量和主题切换。我们有个后台系统要支持暗色模式,本来想着用 :root 里一堆 --color-bg,结果发现某些第三方组件(比如日期选择器)压根不认变量,硬改又怕升级出问题。最后妥协方案:只在业务组件里用 CSS 变量,第三方组件统一套一层 wrapper class,靠 class 切换预设好的暗色 class 包,比如 .theme-dark .datepicker。虽然不够优雅,但上线一周没出问题,我就当它成功了。
字体、重置和一点点取舍
我不用 Normalize.css 全量引入,太重。我只抄它最关键的几行:表单控件默认样式归一、图片默认不撑容器、button 的 cursor: pointer 加上。其他像 pre、code 的样式?按需加,项目里根本没几个 pre。
字体方面,我本地开发用系统字体栈,线上加个兜底:
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
line-height: 1.5;
}
不用 Web Font?不是不想,是实测加载慢+ FOIT 问题严重。客户等不及那个“优雅降级”,他们只关心“点进去能不能看清字”。所以现在除非设计强制要求,否则我一律跳过。
还有个细节:我从不写 * { box-sizing: border-box }。听起来很省事,但它会把 textarea、select 等原生控件也卷进来,有些浏览器下表现诡异。我只写 *, *::before, *::after —— 不,等等,其实我连这个都不写。我直接在每个需要的组件里显式写 box-sizing: border-box。看起来啰嗦,但改起来心里踏实。
结尾
以上是我这几年在 CSS 规范上踩出来的经验,不是教科书,也没有银弹。有些方案不是最优,但足够稳;有些妥协不是因为懒,而是上线时间卡在那儿,你得选个能快速验证、回滚成本低的路。
比如那个暗色模式的 wrapper 方案,我知道有更好的——用 Shadow DOM 隔离、或者自研一套 CSS-in-JS 主题系统。但我没选,因为当前项目没那人力,也没那必要。
如果你有更好的解法,特别是处理第三方组件主题兼容、或者大规模 CSS 拆分治理的经验,欢迎评论区交流。这个主题我还会继续写,比如“如何让老项目渐进式接入 CSS Modules”,或者“PostCSS 插件链怎么配才不翻车”——反正坑还多着呢,咱慢慢填。

暂无评论