BEM 命名规范 + CSS Reset 实战:从零搭建一个微信风格页面

217 阅读12分钟

前言:前端新手最容易忽略的两件事

刚学前端的时候,我的关注点全在"怎么把效果做出来"——调 margin、改颜色、加动画,折腾半天页面终于像样了。但回头一看代码,class 名起得乱七八糟:.box1.title2.red-btn……CSS 文件更是一团浆糊,这个元素多 8px,那个元素少 4px,全是"打补丁"修出来的。

后来我发现,真正让代码能维护的,不是你会多少花活,而是你是否遵循了一套命名规范和样式策略。写一个页面容易,维护一个项目难。而 BEM 命名规范和 CSS Reset,恰恰是前端工程化的第一课。

ChatGPT Image 2026年6月4日 11_40_48.png

本文以我最近练手的一个仿微信 UI 页面为例,把这两件事掰开揉碎讲清楚。

📌 适合人群:刚入门 HTML/CSS,想知道怎么"写规范代码"的前端新人。

📌 你将收获:BEM 命名规范的理解与实操、CSS Reset 的完整方案、HTML5 语义化标签的实战用法、微信 UI 细节的还原思路。


一、先看成品:我们要做个什么?

一个简单的微信风格页面——标题区 + 描述区 + 两个按钮:

<div class="page">
  <header class="page__hd">
    <h1 class="page__title">这是一个页面</h1>
    <p class="page__desc">这是一个页面的描述</p>
  </header>
  <main class="page__bd">
    <a href="#" class="weui-btn weui-btn_primary">主要按钮</a>
    <a href="#" class="weui-btn weui-btn_default">次要按钮</a>
  </main>
</div>

页面结构极其简单,但其中藏着三个我刻意练习的核心点:

核心点对应代码为什么要练
HTML5 语义化<header><main><h1>告别 <div><div>,让结构自带含义
BEM 命名规范.page.page__hd.weui-btn_primary统一命名方式,代码自注释,协作友好
CSS Reset50+ 行重置代码消灭浏览器默认样式,让页面在所有浏览器表现一致

接下来逐个展开。


二、CSS Reset:在做任何样式之前,先把纸擦干净

2.1 为什么要 Reset?

不同浏览器对同一个 HTML 元素有自己内置的默认样式。比如:

  • Chrome 给 <body> 加了 8px 的 margin
  • 不同浏览器对 <h1>font-size 默认值不一样
  • <ul> 前面默认有小圆点,各个浏览器的缩进量不同

如果不做 Reset,你的页面在 Chrome 里好好的,到 Safari 里可能就歪了。CSS Reset 的目的,就是把这些浏览器自带的不一致样式全部干掉,给你一张"白纸"。

2.2 为什么不用 * 通配符?

很多教程上来就教你写:

/* ❌ 不推荐 */
* {
  margin: 0;
  padding: 0;
}

这个方法简单粗暴,但有两个问题:

  1. 性能差* 是通配符选择器,浏览器需要匹配页面上的每一个元素(包括伪元素),渲染开销大
  2. 粒度太粗:一刀切地把所有元素的 margin/padding 都清零,有些你不想重置的也被重置了(比如你可能希望 <input> 保留一些默认样式)

正确的做法是——显式列出所有需要重置的 HTML 元素

/* ✅ 国际规范 CSS Reset | 兼容全浏览器 */
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
  padding: 0;
  border: 0;
  font-size: 100%;
  font: inherit;
  vertical-align: baseline;
  box-sizing: border-box;
}

这段代码看起来很长,但它来自经典的 normalize.css 思路,覆盖了几乎所有的 HTML 标签。每一行做了什么:

属性作用
margin: 0 padding: 0清空内外边距
border: 0去掉默认边框(如 <fieldset>
font-size: 100%统一字体大小为父元素的 100%
font: inherit强制继承父元素字体
vertical-align: baseline统一垂直对齐方式
box-sizing: border-box关键! 让 width/height 包含 padding 和 border,布局计算更直觉

🔑 box-sizing: border-box 可能是你学到的最有用的一行 CSS。

没有它,你设 width: 200px; padding: 20px,元素实际宽度是 200 + 20 + 20 = 240px。有了它,元素宽度就是 200px,padding 从里面扣。这个行为更符合直觉,建议全局设置。

2.3 HTML5 块级元素兼容

HTML5 引入了 <article><section><header><footer> 等语义化标签,但老版本 IE 不认识它们,会默认把它们当行内元素渲染。所以需要显式声明:

/* HTML5 块级元素兼容 */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
  display: block;
}

2.4 其他重置细节

body {
  line-height: 1;          /* 重置行高,避免继承到奇怪的值 */
  min-height: 100vh;        /* 确保 body 至少撑满一屏 */
}

ol, ul {
  list-style: none;         /* 去掉列表前的小圆点 */
}

blockquote, q {
  quotes: none;             /* 去掉引号的默认样式 */
}

table {
  border-collapse: collapse; /* 合并单元格边框,不合并会有间隙 */
  border-spacing: 0;
}

a {
  text-decoration: none;    /* 去掉链接下划线 */
  color: inherit;           /* 链接颜色跟随父元素 */
}

img, svg, picture, video {
  max-width: 100%;          /* 图片不超出容器 */
  display: block;           /* 消除图片底部的 3px 间隙 */
}

💡 每个属性都有它存在的理由,不是复制粘贴就完事。 比如 img { display: block } 这行——<img> 默认是行内元素,会被 baseline 对齐影响,导致图片底部多出 3px 的神秘空白。改成 block 就解决了。这些细节,自己踩过坑才会记住。


三、BEM 命名规范:解决前端界的"起名难"问题

3.1 命名混乱是前端最大的技术债

你有没有写过这样的 class:

.red-button { }      /* 后来按钮改成绿色了,名字还是 red */
.title1 { }          /* title2、title3 各是什么意思? */
.mainBox { }         /* 到底是驼峰还是下划线? */
.section_content { } /* 这跟 .section__content 有什么区别? */

这些问题的根源都一样:没有统一的命名规则。 每个人按自己的习惯来,三个月后自己都看不懂。

BEM 就是来解决这个问题的。

3.2 BEM 是什么?

BEM = Block(块)、Element(元素)、Modifier(修饰符),三个概念组成一套命名规则:

block__element--modifier
  │       │        │
  │       │        └─ 修饰符:表示元素的不同状态或变体
  │       └────────── 元素:块内部的组成部分
  └────────────────── 块:一个独立的、有意义的组件

连接符规则:

  • Block 和 Element 之间__(双下划线)
  • Block/Element 和 Modifier 之间_(单下划线)

看一个真实例子就全明白了:

/* Block:一个页面区块 */
.page { }

/* Element:页面区块里的头部 */
.page__hd { }
.page__title { }
.page__desc { }
.page__bd { }

/* Block:一个按钮组件(weui 框架中的按钮) */
.weui-btn { }

/* Modifier:按钮的不同状态 */
.weui-btn_primary { }  /* 主要按钮(绿色) */
.weui-btn_default { }   /* 默认按钮(灰色) */

对应的 HTML:

<div class="page">                      <!-- Block -->
  <header class="page__hd">             <!-- Element -->
    <h1 class="page__title">标题</h1>    <!-- Element -->
  </header>
  <main class="page__bd">               <!-- Element -->
    <a class="weui-btn weui-btn_primary">主要</a>  <!-- Block + Modifier -->
  </main>
</div>

3.3 为什么 BEM 值得学?

第一,解决了"起名难"。 BEM 把命名变成填空题——你只需要回答三个问题:

  • 这是什么组件?(Block 名)
  • 这是组件里的哪个部分?(Element 名)
  • 它现在是哪种状态?(Modifier 名)

比如微信的按钮组件,不用纠结叫 .greenBtn 还是 .mainBtn,BEM 直接规定:

  • 所有按钮都叫 .weui-btn
  • 主要按钮叫 .weui-btn_primary
  • 默认按钮叫 .weui-btn_default

第二,结构即文档。 任何人看到 .page__hd__title(虽然严格来说 BEM 不建议三级嵌套,但思路一样),立刻知道这个元素的层级关系:它是 page 块下 hd 区域里的 title。

第三,避免样式冲突。 所有 class 都是唯一的,不会出现 .title 被页面里三个不同组件同时用的情况。

🧠 我的理解:BEM 本质上是一种命名约定,不是框架、不是库。它的核心思想是——用名字表达结构和含义,而不是用名字描述样式。 .red-text 是描述样式(坏了,以后改成蓝色怎么办),.page__error 是表达含义。


四、实战:仿微信 UI 页面的完整拆解

4.1 页面布局:绝对定位 vs Flexbox

这个页面的布局用了 position: absolute 实现全屏铺满:

.page {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  /* 四边拉满 = 全屏 */
}

top: 0; left: 0; right: 0; bottom: 0 四边同时设为 0,效果等价于 width: 100vw; height: 100vh,但兼容性更好。

💡 一个调试小技巧:写布局的时候,给区块加个临时背景色,一眼就能看出它占据了多大空间。我在学习时管这叫"背景调试大法":

.page {
  /* background-color: rgba(255, 0, 0, 0.1); 调试完删掉 */
}

4.2 头部区域:标题和描述的层级关系

.page__hd {
  padding: 40px;        /* 微信页面惯用的大间距 */
}

.page__title {
  text-align: left;     /* 虽然默认就是 left,但显式写出来是意图声明 */
  font-size: 20px;      /* 比浏览器默认的 16px 大一圈 */
  font-weight: 400;     /* 正常粗细,不加粗 */
}

.page__desc {
  margin-top: 4px;
  color: rgba(0, 0, 0, 0.45);   /* 次要文字用灰色,弱化视觉权重 */
  text-align: left;
  font-size: 14px;
}

这里有三个设计细节值得注意:

  1. font-weight: 400:很多同学会直接写 bold700,但微信的设计语言偏轻盈,标题也不加粗
  2. color: rgba(0, 0, 0, 0.45):灰色文字不要用 #ccc#999,用 rgba 控制透明度,这样在不同背景上表现更一致
  3. margin-top: 4px:描述和标题之间的间距很小(4px),这是微信 UI 的紧凑设计风格

4.3 按钮组件:微信设计规范的精髓

按钮是整个页面最核心的视觉元素,也是最有技术含量的部分:

.weui-btn {
  position: relative;       /* 为可能的绝对定位子元素提供锚点 */
  display: block;           /* 块级元素,独占一行 */
  min-width: 184px;         /* 最小宽度,保证按钮不会太窄 */
  max-width: 280px;         /* 最大宽度,保证按钮不会太宽 */
  margin-left: auto;        /* 水平居中的另一种写法 */
  margin-right: auto;       /* 配合 margin-left: auto 实现居中 */
  padding: 12px 24px;       /* 上下 12px,左右 24px */
  font-weight: 500;         /* 中等粗细 */
  font-size: 17px;          /* ⭐ 微信特色的字体大小 */
  color: #fff;
  line-height: 1.41176471;  /* ⭐ 这个数字怎么来的?见下方 */
  border-radius: 8px;       /* 圆角 */
  -webkit-tap-highlight-color: rgba(0, 0, 0, 0);  /* 去掉移动端点击高亮 */
  user-select: none;        /* 文字不可选中,更像原生按钮 */
}

🔑 line-height: 1.41176471 是怎么算出来的?

这是 UI 设计师标注决定的:按钮文字 font-size: 17px,按钮高度应该是 24px。所以:

line-height = 按钮高度 / 字号 = 24 / 17 = 1.411764705882353

这个数字不是随便写的——它是从设计稿精确还原出来的。专业的前端开发,就是要能把设计师的标注精准地翻译成 CSS。

/* 主要按钮:微信绿 */
.weui-btn_primary {
  background-color: #07c160;   /* 微信的品牌绿色 */
}

/* 默认按钮:灰色半透明 */
.weui-btn_default {
  color: rgba(0, 0, 0, 0.9);
  background-color: rgba(0, 0, 0, 0.25);
}

4.4 相邻按钮之间的间距

/* 相邻兄弟选择器:选择紧跟在另一个 .weui-btn 后面的 .weui-btn */
.weui-btn + .weui-btn {
  margin-top: 12px;
}

这里用了一个很巧妙的选择器——相邻兄弟选择器 +。它的含义是:选择紧跟在另一个 .weui-btn 后面.weui-btn

效果是:

  • 第一个按钮没有 margin-top(因为它前面没有兄弟 .weui-btn
  • 第二个、第三个按钮各有 12pxmargin-top

045fb74cd4510e920058671d0d9ea61c.png 这样就实现了"按钮之间有间距,但第一个按钮不往下偏移"的效果——不需要额外加 class,不需要 :first-child,优雅。


五、HTML5 语义化标签:HTML 不只是 <div>

5.1 <div> 不是万能标签

很多新手(包括以前的我)写 HTML 只有一个套路:

<div class="header">   <!-- 用 div 表示头部 -->
<div class="main">     <!-- 用 div 表示主体 -->
<div class="footer">   <!-- 用 div 表示底部 -->

能跑,但不够好。HTML5 提供了 语义化标签,让每个标签自带含义:

标签含义替代的写法
<header>页面或区块的头部<div class="header">
<main>页面的主要内容<div class="main">
<footer>页面或区块的底部<div class="footer">
<nav>导航链接组<div class="nav">
<article>独立的文章内容<div class="article">
<section>文档中的一个章节<div class="section">
<aside>侧边栏内容<div class="sidebar">

我们的页面就用了 <header><main>

<header class="page__hd">
  <h1 class="page__title">这是一个页面</h1>
</header>

<main class="page__bd">
  <!-- 主体内容 -->
</main>

5.2 语义化标签好在哪?

第一,对搜索引擎友好(SEO)。 搜索引擎爬虫能理解 <main> 里的内容是页面核心,<header> 是头部导航。

第二,对屏幕阅读器友好(可访问性)。 视障用户使用的屏幕阅读器,会依据语义化标签来构建页面的导航结构,帮助他们快速跳转到核心内容。

第三,代码可读性。<header> 就知道是头部,看 <main> 就知道是主体。语义化标签让 HTML 自注释


六、我的学习反思:慢下来,把基础打牢

写完这个页面,我有几个很深的感受。

6.1 先写结构,再写样式

这是我从这次练习中学到的最重要的工程习惯——

❌ 错误姿势:边写 HTML 边写 CSS,写一点调一点,最后结构混乱
✅ 正确姿势:先完成 HTML 结构,确认语义正确,再统一写 CSS

就像一个房子,你得先搭好框架,再装修。边盖墙边刷漆,房子会塌的。

6.2 CSS Reset 不是复制粘贴

CSS Reset 的每一行都在解决一个具体问题。写的时候思考"这一行在重置什么",而不是把网上的代码原封不动粘过来。

举个例子:img { display: block } 是为了消除图片底部的 3px 间隙。如果你不知道为什么,你可能在遇到"图片下面多了一条缝"的时候,以为是 margin 的问题,然后加了 margin-bottom: -3px——这就叫"打补丁"。补丁会越打越多,代码会越来越难维护。

6.3 BEM 让命名不再是负担

命名曾经是我写 CSS 最头疼的事。有了 BEM 之后,命名变成了"填空题"——看到什么就写什么。.page__title 就是"页面里的标题",不需要绞尽脑汁想一个"独特的名字"。

6.4 关注 UI 设计细节

以前我写 CSS,间距都是"凭感觉"——觉得差不多就写 margin-top: 10px。但真正的 UI 还原,是要像这样算的:

line-height = 设计稿标注的按钮高度 / 设计稿标注的字号
            = 24px / 17px
            = 1.41176471

像素级的还原,来自像素级的严谨。


七、总结

graph TD
    A[HTML5 语义化标签] --> D[干净的页面结构]
    B[CSS Reset 重置样式] --> D
    C[BEM 命名规范] --> D
    D --> E[可维护的前端代码]
    E --> F[团队协作友好]
    E --> G[自己三个月后还看得懂]

这三个东西都不是什么高深的技术——CSS Reset 是写了几十年的老知识,BEM 命名出来快十年了,HTML5 标签也是 2014 年的标准。但它们合在一起,构成了前端工程化的第一块基石

技术栈会变(今天 jQuery,明天 React,后天不知道什么),但这些基础规范不会变。花一个下午把 BEM 和 CSS Reset 吃透,比追新框架值。