这期笔记原本该聊选择器优先级,但后台一直有读者追问同一个问题:class 名到底怎么起才算规范?说实话,这问题比优先级难回答得多。优先级是死规则,背下来就能用;命名是软功夫,得靠真实项目里的坑堆出来。今天我把做 H5 开发这些年沉淀下来的命名方法、踩过的坑、以及团队落地规范的经验一次性整理出来,也算是给自己做个阶段性总结。
1. 先想清楚:我们为什么需要命名规范
1.1 不规范命名的真实代价
前阵子接手一个二次迭代的移动端项目,打开样式文件第一眼就看到了 .div1、.box2、.content_33 这种命名。一开始我以为只是历史遗留,翻到最近提交记录才发现新代码也在这么写。问了下才知道,原开发觉得"反正是自己写的,能看懂就行"。
结果就是:改一个弹窗样式,得全局搜索 content 相关的所有类名,挨个试哪个是目标元素。明明十分钟能搞定的样式调整,硬生生花了两个小时。更要命的是,这种命名方式在团队协作时完全不具备任何信息传递能力——你根本不知道这个类名对应页面上哪个元素,也不知道它是布局类、组件类还是状态类。
还有个常见问题:视觉命名。有人喜欢用 .red-font、.big-title 这类描述外观的类名。当时看着挺直观,但产品经理说"这个按钮从红色改成渐变蓝"的时候,你就得去改 HTML 里的类名,而不是只改 CSS。类名一旦跟具体视觉绑定,就丧失了语义的稳定性,这是命名里最隐蔽的坑。
1.2 命名规范要解决的四个问题
我做了这么多项目,总结下来命名规范本质上是在解决四个核心问题:
第一是可读性。看到类名就知道它是干什么用的,是布局容器、功能组件、还是状态标记,不需要去翻 HTML 结构才能理解。
第二是可维护性。代码三个月后回来看,不用靠记忆力;人员交接时,不用靠口口相传。规范本身就是文档。
第三是可扩展性。新增一个卡片组件,或者给按钮加个新状态,不需要重新想一套命名,直接按既有模式套用就行。
第四是样式隔离。H5 项目经常要嵌到小程序 WebView、第三方 App 里,外部的样式随时可能污染你的页面。合理的命名空间机制能显著降低被覆盖的风险。
这四个问题解决好了,代码质量不管从哪个维度看都不会差。但关键是"怎么定规范"和"怎么落地",这比嘴上喊着"大家注意命名规范"要实在得多。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 主流的 CSS 命名方法论盘点
2.1 BEM:最经典的三段式命名
BEM 是 Block(块)、Element(元素)、Modifier(修饰符)三个单词的缩写,核心思想是把页面拆分成独立的块,每个块内部有若干元素,每种状态用修饰符表达。
经典的语法长这样:
html复制<div class="card">
<div class="card__header">
<h2 class="card__title">标题</h2>
</div>
<div class="card__body">内容</div>
<div class="card__footer">
<button class="card__btn card__btn--primary">确认</button>
</div>
</div>
对应 CSS 写法:
css复制.card { border-radius: 8px; background: #fff; }
.card__header { padding: 16px; border-bottom: 1px solid #f0f0f0; }
.card__title { font-size: 18px; font-weight: 600; }
.card__body { padding: 16px; }
.card__btn { padding: 8px 16px; border-radius: 4px; }
.card__btn--primary { background: #1677ff; color: #fff; }
card 是块,card__header 是块内的元素,card__btn--primary 是按钮的主要样式变体。这套命名的好处是:选择器优先级非常克制,全部是单类名选择器,不会出现嵌套地狱;结构关系通过命名直观呈现,.card__header 一看就知道它属于 .card。
实战中我建议大家注意两个细节:一是不要过度嵌套元素层级,.card__header__title 这种写法纯属给自己找麻烦,最多拆到两层就够用了;二是修饰符使用 -- 而非 _,这是为了在视觉上跟元素的分隔符 __ 做出明显区分。
2.2 OOCSS:结构样式与皮肤分离
OOCSS 的核心主张是把"结构样式"和"皮肤样式"分开管理。结构样式指的是尺寸、内边距、布局这类决定元素"骨架"的属性;皮肤样式指的是颜色、背景、边框这类决定元素"外观"的属性。
举个例子:
html复制<div class="btn btn-primary">确认</div>
<div class="btn btn-danger">删除</div>
css复制/* 结构 */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
line-height: 1.5;
}
/* 皮肤 */
.btn-primary { background: #1677ff; color: #fff; }
.btn-danger { background: #ff4d4f; color: #fff; }
这样设计的好处非常明显:所有按钮共享一套盒模型和字号,视觉差异交给独立的皮肤类来控制。如果需要新增一个"成功"按钮,只需要写一个 .btn-success,继承 .btn 的结构能力,不需要把整个按钮样式复制一遍。
OOCSS 跟 BEM 其实可以搭配使用。BEM 负责结构语义,OOCSS 负责样式复用。两者并不冲突,反而是很好的互补关系。
2.3 SMACSS:从分层角度考虑命名
SMACSS 的出发点不太一样,它不太纠结于单个类名怎么写,而是把样式整体划分成五个层级:
- 基础层(Base):元素默认样式,如
body、a、h1的默认重置样式。 - 布局层(Layout):页面骨架样式,我用
l-前缀区分,如.l-header、.l-container、.l-sidebar。 - 模块层(Module):可复用的组件样式,如
.card、.dialog、.dropdown。 - 状态层(State):跟用户交互状态相关的样式,用
is-前缀,如.is-active、.is-disabled。 - 主题层(Theme):主题相关样式,如
.theme-dark下的颜色调整。
SMACSS 的实用之处在于:它让团队对不同类名有了统一的认知框架。看到 l- 前缀就知道是布局,看到 is- 前缀就知道是状态,这对快速定位问题是很有帮助的。
2.4 各方法论对比与选型建议
| 方法论 | 核心思想 | 典型场景 | 上手难度 | 团队建议 |
|---|---|---|---|---|
| BEM | 块/元素/修饰符三段式 | 中小型项目、组件库 | 低 | 推荐首选 |
| OOCSS | 结构与皮肤分离 | 项目样式复用需求大 | 中 | 与 BEM 互补 |
| SMACSS | 分层分类管理 | 大型项目、多页面应用 | 中高 | 适合有经验团队 |
我个人建议:如果是三五个人的团队做中小型 H5 项目,直接用 BEM 就足够;如果项目规模较大且有样式复用的硬需求,BEM + OOCSS 的组合非常能打;SMACSS 则适合需要建立完整样式架构的团队。实际上这三种方法不是互斥选项,很多成熟项目是融合使用的——用 SMACSS 分层的思想做全局规划,用 BEM 的命名规则写组件细节,用 OOCSS 的理念来组织复用样式。
3. 神级命名技巧拆解
3.1 语义化命名:让类名自己说话
语义化命名不是某种具体规范,而是一种设计思维:类名应该描述元素的"身份",而不是它的"样子"。
反面教材:
html复制<!-- 反面教材:描述外观 -->
<div class="red-big-text">活动标题</div>
<div class="float-right">提交</div>
正面示范:
html复制<!-- 正面示范:描述身份 -->
<div class="promo-title">活动标题</div>
<button class="submit-btn">提交</button>
为什么要这样做?因为外观随时会变。.red-big-text 在改版后可能是蓝色加粗,但类名里的 red 改不掉,只能连带改 HTML。而 .promo-title 不管视觉怎么变,身份始终不变,CSS 里改样式即可,完全不需要动结构。
深入一层看,语义化命名的本质是建立"类名跟业务逻辑的连接"。做电商活动页的时候,我喜欢把模块直接命名为 .seckill-section、.flash-sale-panel,这样看样式文件就能直接对应到产品功能,沟通成本会低很多。
3.2 状态与功能类命名
交互状态是 H5 开发里绕不开的场景:弹窗开关、Tab 切换、按钮禁用、菜单展开。状态类命名有一个约定俗成的模式,我建议团队统一用以下前缀:
| 前缀 | 语义 | 例子 |
|---|---|---|
is- |
临时状态 | .is-active、.is-disabled、.is-hidden |
has- |
包含特定内容 | .has-error、.has-icon |
with- |
伴随特定样式 | .with-shadow、.with-border |
js- |
JavaScript 钩子 | .js-modal-trigger、.js-tab-switch |
特别强调一下 js- 前缀。很多团队的前端代码里,JavaScript 通过 document.querySelector('.modal-trigger') 直接获取元素,一旦样式重构改了类名,功能就崩了。用 js- 前缀单独标识"仅供脚本使用、不绑定样式"的类名,可以明确区分"改动影响样式"和"改动影响逻辑"两类安全等级。这是从 jQuery 时代流传下来的良好习惯,在框架时代依然有效。
3.3 实用缩写与简写规则
缩写用得好,可以让类名干净利落;用得不好,就是灾难。我的建议是:只缩写高频且公认的词,不要自创缩写。
推荐一套我在多个项目中验证过的高频缩写表:
| 缩写 | 全称 | 使用场景 |
|---|---|---|
wrap |
wrapper | 包裹容器 |
ctn |
container | 布局容器 |
hd |
header | 头部区域 |
bd |
body | 主体区域 |
ft |
footer | 底部区域 |
nav |
navigation | 导航 |
desc |
description | 描述文本 |
info |
information | 信息区域 |
icon |
icon | 图标 |
btn |
button | 按钮 |
dlg |
dialog | 弹窗 |
需要注意:.header 和 .hd 在同一项目里不能混用,要么全用全称,要么全用缩写,这是规范一致性最基本的要求。我个人的倾向是:公共组件、涉及团队协作的代码用全称,局部私有样式可以用缩写,前提是团队约定好。
3.4 H5 场景下的命名细节
H5 项目跟 PC 端有个很大的区别:大多数时候是嵌在 App WebView、微信公众号、小程序 WebView 里运行的。这种环境本身就是一个"样式污染高发区",表现在两个层面:
一是 宿主页面与 H5 页面的样式冲突。你在自己页面里写了 .header { position: sticky; top: 0; },但宿主 App 的 CSS 可能也定义了一个 .header,两个样式在渲染时可能互相覆盖。这个问题的解法是"命名空间隔离",我给 H5 项目定的规则是:所有业务组件类名都带项目前缀。
html复制<div class="xsj-header">我的H5页面头部</div>
css复制.xsj-header {
position: sticky;
top: 0;
z-index: 100;
background: #fff;
}
xsj 可以是项目代号或公司简写的拼音缩写,虽然类名会长一点,但换来的是极高的隔离安全性。这在长周期维护的项目里是非常值得的。
二是 移动端适配相关的命名。做 H5 适配时,经常遇到需要同时使用 px、rem、vw 的场景。我习惯给不同单位体系下的元素分别打上视觉标记,方便排查:
html复制<div class="banner banner--full">全屏banner</div>
<div class="card card--rem">使用rem适配的卡片</div>
</div>
css复制.banner--full { width: 100vw; height: 100vh; }
.card--rem { width: 6.9rem; padding: 0.32rem; }
html复制<div class="product-card js-product-card">商品卡片</div>
这个命名同时承载了三层信息:product-card 表明它是商品卡片组件,js- 前缀标明它是 JS 钩子,状态类 is-active 表明当前选中状态。在代码审查时,这种命名信息量非常高效,一眼就能确认它的职责边界。
3.3 命名缩写与简写规则
缩写用得好,可以让类名干净利落;用得不好,就是团队沟通的灾难。我的建议是只缩写那些高频出现、团队公认的词汇,不要自创缩写。长期稳定的缩写可以直接沿用行业惯例,比如:
| 缩写 | 全称 | 使用场景 |
|---|---|---|
wrap |
wrapper | 最外层包裹容器 |
ctn |
container | 布局容器 |
hd / bd / ft |
header / body / footer | 区域内三段式 |
nav |
navigation | 导航 |
desc |
description | 描述文本 |
info |
information | 信息区域 |
btn |
button | 按钮 |
这里要特别提醒:缩写方案一旦确定,必须全项目保持一致。.header 和 .hd 混用的情况会让后续维护者非常抓狂,因为你永远不知道某个样式到底写在哪个类名下面。我见过的一个真实案例是,项目里同时存在 .ft 和 .footer 两个类名指向同一个底部区域,样式各写了一半,排查问题的时候反复横跳,极其折磨。
3.4 H5 场景下的命名细节
H5 开发有个 PC 端不太在意的特殊场景:页面要嵌到各种宿主环境里——微信内置浏览器、小程序 WebView、App 原生 WebView,甚至第三方平台,每个宿主加载页面前的样式环境可能完全不同。早期做 H5 时,我踩过最大的坑就是宿主页面的全局样式污染。
比如有次把一个活动页嵌到某个 App 的 WebView 里,页面底部突然多了一条莫名的边框,查了半天才发现是宿主页面给 div 统一增加了 border-bottom。从那时起,我给 H5 项目定了一个死规矩:业务组件类名全部加项目前缀。
项目代号前缀我一般取两个到四个字母,比如 xx 是项目代号,btn 是组件名:
html复制<button class="xx-btn xx-btn--primary">立即参与</button>
css复制.xx-btn { padding: 10px 24px; border-radius: 4px; }
.xx-btn--primary { background: #ff6a00; color: #fff; }
这样无论宿主页面里有没有叫 .btn 的样式,我们的类名都很难被意外命中。很多人觉得前缀累赘,但换个角度想:类名长一点,换来的是一整条样式隔离的强保障。尤其是项目要同时嵌进微信、支付宝、抖音等多个 WebView 的时候,这个习惯能帮你省掉大量排查样式错乱的时间。
另一个 H5 场景下的命名细节是移动端适配的状态标记。做响应式时,经常需要判断当前是移动端还是 PC 端,我习惯给根节点挂上环境类名,比如:
html复制<html class="env-mobile"> <!-- 移动端 -->
<html class="env-desktop"> <!-- PC端 -->
css复制.env-mobile .xx-banner { height: 200px; }
.env-desktop .xx-banner { height: 320px; }
这种环境命名跟具体组件解耦,切换场景时只需要控制根节点的类名,所有子组件样式自动响应,比在组件内部各自判断要清爽得多。
4. 如何搭建一套可落地的命名体系
4.1 从实际项目出发定制规范
网上能搜到很多现成的 CSS 命名规范文档,但如果直接抄来用,十有八九落不了地。原因很简单:每个项目的团队规模、技术栈、业务类型都不一样,选型必须基于实际场景。
定制规范时我建议你先回答四个问题:
第一,项目是长期迭代还是短期活动?长期项目(如核心业务 H5)需要完善的组件级命名体系,短期活动页则可以简化,只约定基础规则。
第二,团队是多人协作还是单人维护?多人协作必须有书面规范,而且最好沉淀成文档;单人维护至少也要保证自己能看懂。
第三,技术栈是什么?原生 CSS 用类名硬隔离,还是使用 CSS Modules、Vue Scoped、Tailwind 这类自带隔离机制的工具?这直接影响命名策略。
第四,项目需不需要嵌套进第三方 WebView?如果有,命名空间前缀就是必选项。
拿我做过的一个典型营销 H5 项目举例,团队五个人,使用 Vue + SCSS,页面要嵌进微信公众号和小程序。最终的规范是:BEM 作为基础命名法,组件根类名统一加 mp- 前缀,状态类使用 is- 前缀,功能性类名使用 js- 前缀,布局类使用 l- 前缀。
这套定制下来的规则只有一页纸,团队花二十分钟就过完了。关键是每一条规则背后都有具体场景支撑,大家理解起来没有任何障碍。
4.2 与预处理器和框架协同的命名实践
现代前端开发基本离不开 SCSS、Less 这类预处理器,以及 Vue/React 这类框架。命名规范也要跟着技术栈做适配。
在 SCSS 中,BEM 的写法可以通过 @at-root 和嵌套语法保持整洁:
scss复制.mp-card {
border-radius: 8px;
&__header {
padding: 16px;
font-size: 16px;
}
&__btn {
padding: 8px 16px;
&--primary {
background: #1677ff;
color: #fff;
}
}
}
嵌套结构让类名的层级关系在代码里直接可见,编译出来依然是扁平的 .mp-card__header,既保证了可读性又没有优先级污染。但要注意:嵌套层级保持在两层以内,嵌套太深会导致选择器过长且难以维护。
在 Vue 中,我通常给组件根元素设置唯一的类名,并且开启 scoped,这样样式天然隔离。但 scoped 并不是万能的,第三方组件库的样式覆盖、以及内部子组件的样式穿透,仍然需要清晰命名来做安全边界。给根类名加组件名前缀仍然是我坚持的习惯。
React 项目如果使用 CSS Modules,类名会在构建时自动哈希,命名压力的确小很多。但这时我建议在组件内部仍然用语义化的类名,至少保证开发调试时 devtools 里能看懂。
4.3 命名规范在代码审查中的执行
规范定得再好,不执行等于零。代码审查是唯一能强制落地的环节。我在团队里推命名规范的经验是:审查时不搞"自由心证",而是把高频规则整理成检查清单。
审查时会重点检查以下几项:
- 类名是否使用了禁用词(如
red、left、big等视觉描述词)。 - 组件根类名是否携带了项目前缀。
- 状态类是否使用了
is-前缀,而不是裸写.active。 - JS 钩子是否单独用
js-前缀,而不是混在样式类里。 - 是否出现了连续三层以上的 BEM 嵌套。
审查工具也可以用。Stylelint 支持配置 selector-class-pattern 规则,用正则约束类名格式,命中不符合规范的类名会自动报错。比如我可以配置组件类名必须是小写字母、连字符分隔且带 mp- 前缀的模式。
json复制{
"rules": {
"selector-class-pattern": "^mp-[a-z]+(-[a-z]+)*(-[a-z]+)?$"
}
}
不过也别指望工具能解决所有问题。命名规范里关于"语义是否清晰"的判断,工具没法做,只能靠人在 review 时把关。所以我更倾向于把自动检查作为第一道防线,把人工 review 作为第二道防线。
5. 常见命名问题与排查经验
5.1 高频命名错误案例
这里整理几个我在实际项目中高频见到的命名错误,以及对应的处理方式。
第一个是视觉描述式命名。典型的是 .red-font、.border-bottom、.width-80。这种类名跟具体样式值绑定,一旦设计调整,类名就成了错误信息源。处理方式是用语义化命名替换,比如 .price-text、.divider、.w-80(如果项目确实需要宽度工具类,至少统一命名规则)。
第二个是层级关系命名。把父子结构写进类名里,比如 .menu-item-span、.dialog-title-text。这类类名的问题是过度耦合页面结构,稍微调一下 DOM 层级,类名就失去了意义。BEM 已经用 __ 解决了层级表达问题,不要自创另一套层级命名法。
第三个是无意义拼写。div1、box2、aaaa、temp_123 这种类名只能说是敷衍。哪怕项目再急,这种命名最终都会变成你加班排查问题的元凶。
第四个是状态跟结构混在一起。典型的写法是 .nav-active、.btn-hover。用 is-active 替代会更合理,因为 is-active 可以自由搭配任何组件,是一个通用状态类,而 .nav-active 只能用在导航上。
统一处理建议是:把这些错误类名列入 stylelint 黑名单,并在代码审查时重点盯防,出现一次就要整改一次。
5.2 命名重构的实战方法
命名的重构往往是项目里最吃力不讨好的事情,因为改类名会牵动 HTML、CSS、JS 三处。但只要方法得当,风险也是可控的。我经历过几次比较顺利的类名重构,分享下操作路径。
第一步是盘点范围。先用代码搜索找出目标类名的所有出现位置,包括模板文件、样式文件和脚本文件,形成一份影响清单。这份清单是你评估风险的基础,别嫌麻烦,省了这一步后面必然出大问题。
第二步是新增并同步。不要直接删除旧类名,而是先把新类名加到目标元素上,让新旧类名同时存在。CSS 里同时写两套样式,先确认新样式在目标浏览器上都正常显示,这期间旧类名不影响线上功能。
第三步是移除旧类名。确认新类名稳定后,再统一删除旧类名和旧样式。这一步要做全量回归测试,特别是涉及 JS 操作的类名,要检查脚本里是否还残留旧选择器。
第四步是清理残留。开发环境里全局搜索旧类名,确保没有遗漏。有些旧类名可能出现在测试代码或文档里,同样需要同步清理。
这套流程的关键原则是"先加后删",保证任何一个中间状态下页面都是能用的。你在重构时一定要盯着这个原则,一旦出现页面崩坏,你要能随时回退。
5.3 团队规范落地的三个心得
最后聊点软性的东西。根据我这些年带团队的经验,命名规范能不能落地,决定性因素往往不是规则本身,而是执行方式。
第一个心得是规范要简单。超过一页纸的命名规范基本没人会主动看,更别提记住。我见过的高效规范都是极简的:一条 BEM 基本写法、一个前缀规则、一组状态类命名、一个禁用列表。刚开始用不着覆盖所有场景,遇到新问题再补充,让规范跟着项目一起生长。
第二个心得是规则要有示例。光写"类名要语义化"是不够的,一定要给出好的命名和坏的命名的对比,并解释为什么好的好、坏的坏。人看示例是最容易理解和记忆的。
第三个心得是执行要有反馈。在代码审查时提出命名修改建议,语气上要就事论事,不指责人。我习惯在建议后面加一句"用了语义化命名以后这段样式就不需要看 HTML 就知道是干什么的了",这样对方能理解改动的价值,而不是觉得在吹毛求疵。
根据我个人的体会,命名规范是所有规范性要求里投资回报率最高的一项。它没有框架那么酷炫,也没有算法那么烧脑,但它能真正决定你在三个月后改代码时是淡定还是抓狂。如果你所在的项目命名还处于"随心所欲"的阶段,不妨从今天起把 BEM 和前缀规则用起来,不用追求一步到位,哪怕只是让新代码先规范起来,过一段时间你就能感受到差异了。
