1. 满屏 div 的真实代价——先聊聊我为什么劝你别再"万能容器"了
先说个我自己的真实经历。前几年接手过一个后台管理系统,打开页面源码的一瞬间我人麻了:整个页面的主体结构是<div>套<div>套<div>,最深的层级到了十几层,每个 div 还带着className="box1 box2 box3"这种毫无信息量的命名。当时排查一个样式错位问题,光定位这个 div 是哪一个就花了大半天,最后发现是某个组件内部多包了一层没有实际作用的容器。那个瞬间我就下了一个决心:能用语义化标签写清楚的,绝不用 div 凑合。
很多同学觉得"div 加 class 不也能用吗",这话没错,div 确实能完成所有布局需求,但它有一个致命的问题——它本身不携带任何含义。浏览器不知道它是导航还是页脚,屏幕阅读器不知道它是不是主要内容,SEO 爬虫分不清哪个区块才是正文,你的同事更看不懂这一层一层嵌套到底谁是谁。
你写的代码最终是给人看的。人看代码最快的方式是"扫结构",而结构是否清晰,很大程度取决于标签本身讲不讲话。<nav>一摆出来,谁都知道这里是导航;<footer>一出现,自然知道这里是页脚。而 div 呢?它只会沉默地站在那里,等你用 class 去给它贴标签——问题是 class 是你们团队的私有约定,换个人来看,等于没有。
再有,HTML5 语义化标签这事从来不是"理论正确但没用"的花架子。它直接影响三件实事:无障碍访问(屏幕阅读器依赖语义标签来朗读页面结构)、SEO 收录(搜索引擎给语义清晰的内容更高权重)、开发协作效率(结构即文档,省去大量沟通成本)。这三条随便哪一条落到实处,都是实打实的收益。
所以这篇我把自己在实际项目中总结的语义化标签使用经验完整盘一遍,包括每个标签的适用场景、边界判断、常见误用,以及怎么从一团乱麻的 div 堆里做渐进式重构。适合刚接触 HTML5 不久的同学当入门图谱,也适合写了一阵子业务代码但没认真梳理过语义化逻辑的同行查漏补缺。
2. 核心标签逐个拆解——每个标签都该用在哪里、不该用在哪里
HTML5 新增的语义化标签一共就那么几个:header、nav、main、article、section、aside、footer,外加一个没怎么被讨论但很重要的figure和figcaption。逐个盘点一下,顺便把最常见的使用误区标出来。
2.1 header:不只是"顶部那一条"
<header>在很多人脑子里约等于"页面的顶部区域",其实它的定义比这宽——它代表的是一个区块的引导区域。也就是说,它既可以放在页面顶部,也可以放在article里、section里,甚至放在一个"商品卡片"里。
<article> <header> <h2>产品功能更新日志</h2> <p>发布时间:2024年6月18日 · 阅读约 3 分钟</p> </header> <p>正文内容……</p> </article>注意<header>并不一定出现在页面最顶端,它只要求在它所处的那个区块内承担"引导"职责。有些页面顶部是纯广告横幅或通知条,这种情况没有必要硬套<header>,把真正的站点头部区留给header更准确。
另一个容易犯的错是在<header>里堆太多东西。logo、搜索框、登录按钮、导航、副标题全部塞进去,标签本身失去"引导"的语义纯度。它跟nav是两回事,header负责呈现“这个区块的前言、标题、简介”,nav才负责“链接跳转的导航集合”,二者职责分开写更健康。
2.2 nav:不是所有链接列表都是导航
很多人看到页面上有一组链接就随手套<nav>,其实nav只服务于“主要导航区块”——比如站点主菜单、侧边栏的章节目录、分页器(pagination)。而像"友情链接"、"文章底部的相关阅读"这类次要链接集合,没有用nav的必要,ul li就足够。
判断标准很直接:删掉它,用户找关键路径是否受影响?如果是,那它是导航;如果只是内容性的链接罗列,别用nav抢戏。
导航里我特别推荐配合<ul>构建,而不是裸写一堆<a>。这个习惯对无障碍支持很关键,读屏工具会把ul内的项目自动作为列表朗读,用户能明确感知"这里有 5 个导航项"。
<nav aria-label="主导航"> <ul> <li><a href="/">首页</a></li> <li><a href="/posts">文章</a></li> <li><a href="/tools">工具箱</a></li> </ul> </nav>有个细节:一个页面允许有多个nav,但每个nav都应该配上aria-label区分用途(比如"主导航"、"页脚导航"、"文档目录"),这样依赖辅助技术的用户不会被两个相同的"导航"搞晕。这个细节我在实际无障碍测试里实测过,不标注的话读屏软件会念出两个一模一样的"导航 导航",体验很差。
2.3 main:一页只有一个,别客气
<main>的语义非常明确:代表页面独一无二的主体内容。它和article的区别在于,article 可复用,一个页面可以有多个;而 main 在一个页面里只能出现一次,而且不应该被包在其它语义区块里面。
实际项目里我会把 main 当作整个内容区域的锚点:
<body> <header>站点头部</header> <div class="layout"> <nav>目录导航</nav> <main> <article>正文</article> </main> <aside>侧边栏</aside> </div> <footer>页脚</footer> </body>有同学会问:main 里面不能有自己的 header 和 footer 吗?可以,这种结构完全合法,这也呼应了前面说的"header、footer 是局部的"这层意思。
一个要注意的操作点:单页应用里如果内容切换是动态渲染的,给main挂一个可见但可跳过的<a href="#main-content">跳至正文</a>链接是极好的无障碍实践,同时记得在main上加tabindex="-1"才能让焦点正确移动。这个小组合很多团队不做,但你做了,用户体感差别很明显。
2.4 section 和 article:一对最容易被搞混的兄弟
这俩是最多人用错的一组,核心区别一句话能说清:article 是完整的、可独立成篇的内容单元;section 是内容内部的专题分区。举个例子:一篇长文里有三个观点板块,那这篇文章是article,每个观点板块是section。如果一个页面上展示的是用户发布的多个帖子,那每个帖子都可以是一个article,它们之间互不依赖。
但只有这个判断还不够,我常用的实操标准有两板斧:
- 如果这段内容摘出来,放进一个独立的网页里依然成立、完整、有意义——是 article;
- 如果它是某个更大内容主题的一个子主题区块,单独拿出来略显单薄——是 section。
另外,section一般建议配一个标题标签(h1-h6),因为它暗示着一个独立主题的开始。没有标题却又要用 section,多半说明应该换成 div。
2.5 aside:别只当"侧边栏"使用
aside的官方语义是"和周边内容只有间接关系的部分",最常见的形态确实是侧边栏,但它也能用在正文中间——比如文章里的名词解释、引用说明、相关材料补充。
我比较常见的一个场景是产品详情页:"商品参数表格"用aside放在正文侧边,这样读屏用户能在正文阅读和参数查看之间明确切换。如果用 div,读屏会把它当作正文的一部分连续朗读,内容跳来跳去非常难受。
2.6 footer:页脚的微小信息也别闲置它
和header一样,footer出现在一个区块的结尾处即可,不一定非要页面底部。文章底部放 "作者信息、版权声明、标签集合" 是很合适的footer场景:
<article> <h1>深入理解 CSS 容器查询</h1> <p>正文内容……</p> <footer> <p>作者:某某 · 标签:#CSS #响应式</p> </footer> </article>版权信息、联系方式、站点地图链接放页面级 footer 没毛病,但是别学一些反模式:把 footer 当悬浮条、把整个页面的所有版权声明都堆进每个区块的 footer 里,那样反而让语义错乱。
2.7 figure 与 figcaption:图片专属的语义补位
<figure>这个标签被索引的概率远低于它应有的地位。它承载的是"插图、图表、代码片段等自带独立说明的内容",配合figcaption可以形成语义完整的图片单元。
<figure> <img src="architecture.png" alt="系统架构图"> <figcaption>图 1:订单模块整体架构</figcaption> </figure>如果你还在用<p>包<img>再在下面写一行灰色小字当图片说明,试试换成figure + figcaption,至少在语义上这个小单元变成了一块完整的内容,而且默认样式也省得你手调不少。
3. 最容易翻车的"选择困难症"——div、section、article 到底怎么快速拍板
写语义化标签最耗神的就是面对一块内容犹豫半天:"这是 section 还是 article?还是干脆用 div?" 我的建议是把判断变成条件分支,几步走完,不要靠感觉。实际写码时,我内心跑的是下面这套流程。
3.1 第一步:先看内容能否独立存在
问自己一个问题:如果把这段内容单独放到一个页面,读者会觉得突兀吗?不突兀,说明它有完整的独立性,直接选article。
举两个对比:
- 首页上展示的"最新 3 条公告",每条公告有自己的标题、摘要、时间——每条都可以单独抽出来看,所以每条公告都是一个
article; - 公告下面"热门标签"区域,里面的标签云只是容器内的聚合信息,哪来的"独立性"?它不是 article,也不是 section,用
ul或者干脆div最合适。
3.2 第二步:判断是否属于"主题下的子分区"
内容独立不了,但有明确的小主题(比如"第一章""核心优势""操作步骤"),并且这个小主题应该作为整个页面大纲的一部分出现,那用section。
关键点是 section 通常会让文档大纲多一层"子标题"。如果这个主题标题不进大纲也不影响理解,说明它只是视觉拆分的产物,div 更诚实。
3.3 第三步:两者都不是——恭喜你,可以安心用 div
div 不是敌人,无脑滥用 div 才是。真正该用 div 的场景特别朴素:
- 纯布局容器(比如 flex 排布的外层包裹)
- 没有独立语义的装饰性分组
- JS 操作需要但不需要语义的钩子
这类情况下硬套 section / article,属于"矫枉过正"。语义化追求的是忠实表达内容结构,不是给每个标签都贴金。能诚实用 div 的地方,就大方用 div;该语义化的地方,也别偷懒。
3.4 一个兜底的通用判断表
我把这个判断逻辑整理成了速查表,贴给我团队新人用的,这里也直接分享出来:
| 场景特征 | 推荐标签 | 不推荐原因 |
|---|---|---|
| 内容独立成篇、可单独传播 | article | section 会让文章独立性变弱 |
| 大主题下的子主题分区 | section | article 语义过重 |
| 主要链接集合(菜单、目录) | nav | div+p 无法传达导航语义 |
| 当前页面唯一主体区域 | main | 多个 main 违反 HTML 规范 |
| 与主内容只是间接相关 | aside | div 无法体现"间接性" |
| 纯布局包裹、无语义需求 | div | section/article 会造成语义污染 |
这个表我实际用下来准确率很高。先判断独立性,再判断主题性,两者皆否就放它去 div。
4. 从 div 到语义化:一次真实页面的渐进式重构全记录
理论讲再多,不如看一个真实重构过程。我拿之前给一个团队做的"产品文档首页"改造当案例拆解。原页面大概五六屏长,全部由 div 搭建,负责的同学说"当时只求快点上线,没想语义这回事"。这种说法我不止一次听到,但讲真,重构并不需要推翻一切,渐进式整改完全可行。
4.1 重构前的代码状态
原结构大致如下:
<div class="container"> <div class="top"> <div class="logo">LOGO</div> <div class="nav"> <a href="#">文档</a> <a href="#">API</a> <a href="#">社区</a> </div> </div> <div class="content"> <div class="sidebar"> <a href="#">快速开始</a> <a href="#">安装配置</a> </div> <div class="article"> <h2>快速开始</h2> <p>欢迎使用……</p> </div> </div> <div class="footer"> <span>© 2024</span> </div> </div>单从视觉看没什么毛病,但语义上几乎没给任何线索。盲人用户使用读屏工具时,听到的是一串没有层级结构的 div,根本无法跳过导航直接读正文。
4.2 第一次重构:标签替换,class 保留
第一次不用动太多,先把外层标签换了,class 可以留着当作样式挂载点:
<body> <header class="top"> <div class="logo">LOGO</div> <nav class="nav" aria-label="主导航"> <a href="#">文档</a> <a href="#">API</a> <a href="#">社区</a> </nav> </header> <div class="content"> <nav class="sidebar" aria-label="文档目录"> <a href="#">快速开始</a> <a href="#">安装配置</a> </nav> <main class="article"> <h1>快速开始</h1> <p>欢迎使用……</p> </main> </div> <footer class="footer"> <p>© 2024</p> </footer> </body>注意我做了两个额外动作:一是给两个nav加了aria-label区分,二是把原来的h2在 main 语义下提升成h1。后者是很多人忽略的:页面主体标题应该是文档大纲的第一级,从 h1 开始,否则大纲树缺失根节点。
4.3 第二次优化:进一步细分区块边界
第一次替换只解决了"表面标签",里面还有改进空间。产品文档首页有三个章节,每个章节下又有若干小节,这些内容应该是section加标题的组合:
<main> <h1>产品文档</h1> <section aria-labelledby="getting-started"> <h2 id="getting-started">快速开始</h2> <p>……</p> </section> <section aria-labelledby="installation"> <h2 id="installation">安装配置</h2> <p>……</p> </section> </main>aria-labelledby和标题 id 关联之后,每个 section 的名称会直接被读屏软件报出来,用户进入区块前就知道接下来这个区域讲什么。这一步在原始 div 阶段是根本做不到的。
4.4 重构后的实际效果
重构完成以后我做了三次检查:
- 控制台检查:无 HTML 嵌套错误,
main唯一、header/footer层级正确; - 大纲检查:用浏览器的 Reader View 和 W3C HTML Checker 过了一遍,文档大纲结构清晰,从 h1 到 h3 没有跳级;
- 读屏实测:用 NVDA 从页面顶部开始听,导航、正文、页脚都能被准确播报,跳转快捷键(按 H 跳标题、按 1 跳一级标题)完全生效。
重构前后视觉零变化,但"结构可感知度"完全不在一个级别。这也是语义化的核心价值:它不让你做得更多,但让你的代码向所有消费方打开一个信息通道。
5. 别栽在细节里:标题层级、兼容性和 SEO 那些事
标签选对了,几个容易"翻车"的细节也要处理干净。
5.1 标题层级:h1 的分布规则比想象中严格
HTML 规范不限制页面只能有唯一 h1,很多站点也会在多个区块设置 h1。但从文档大纲的清晰度考虑,一个页面的主导航入口最好只有一个 h1,副标题、区块标题用 h2/h3 逐级展开。如果多个区块各自有 h1,读屏用户快速浏览时听到的每一个一级标题都会被认为是"一个新页面的开始",误导性极强。
实际开发里我给团队定的规矩是这样:
| 页面区域 | 推荐的标题级别 |
|---|---|
| 页面级主体标题 | h1(页面内至多 1-2 个) |
| 区块标题(section/article 内) | h2 或 h3 |
| 区块内的子标题 | 按内容层级递进 |
| 页脚、次要辅助区域 | 不设置标题或 h3 以上 |
尽量不要跳级,不要从 h2 直接蹦到 h4,这样既照顾了大纲结构,也让阅读体验更顺畅。
5.2 兼容性:别被"老浏览器不支持"吓住
HTML5 语义标签的兼容性放在今天根本不是问题,所有现代浏览器原生支持。如果项目里确实存在必须兼容远古浏览器的场景(比如某些政府系统内部浏览器还是 IE8 级别),那就需要给这些标签补上默认样式和 HTML5 shiv 方案。
老项目里我见过比较稳妥的做法是:
npm install html5shiv然后手动给语义元素添加display: block。但说实话,这类存量项目的治理优先级极低,新项目请直接用 HTML5 语义标签,不要再搞降级方案,因为你每个降级样板都在给未来的维护者埋雷。
5.3 SEO 的真相:语义标签的作用是"更容易被理解"
关于 SEO,需要泼一点冷水:语义化标签不是排名开关,不是说加了article关键词排名就能立刻冲第一。它对 SEO 的真实作用在于——搜索引擎的爬虫和结构化解析器能更准确地理解页面的主题分布、主次关系、链接权重分配。
如果你的页面上有十个并列 div,爬虫对"哪些内容才是本页最重要的核心段"判断难度很大;但如果你用了main > article > section这种结构,爬虫能轻松定位核心内容区块。所以"语义化利于 SEO"这个说法没错,但它是间接收益,别把它当魔法。
5.4 aria 是语义化的延伸,不是替代
最后特别提醒一句:ARIA(Accessible Rich Internet Applications)是语义化标签的补充,不是备胎。有些同学一出手就喜欢给 div 挂一堆role="navigation"、role="main",等于把语义化标签应该做的事用 ARIA 硬拗回来。结果代码又长又难以维护。
正确顺序是:优先用原生语义化标签,原生的做不到再用 ARIA 补位。比如div加role="heading"这种事,不如直接用h2;div加role="navigation",不如直接用nav。原生标签不仅自带语义,还自带键盘交互和默认样式,ARIA 只负责雪中送炭,不负责锦上添花。
6. 一条务实路径:新项目怎么定规矩,老项目怎么逐步解套
看完上面的内容,如果你决定动手改造,我给一条实际的落地路径,按优先级排序。
6.1 新项目:用模板和 eslint 一起兜底
新项目开局就定好规矩最省事。我给团队推荐的基线方案:
- 页面骨架固定用
header / nav / main / footer四个标签搭底; - 正文列表项用
article包裹,配合h2标题; - 侧边辅助内容统一
aside; - 纯布局容器保留
<div>,但class命名禁止使用box1这种无意义名称; - 代码规范层面,可以装一个 eslint 插件做语义化标签检查,提前拦截明显的错误用法。
6.2 老项目:按"页面级 → 区块级 → 内联级"三层递进
老项目改造不建议一次性推翻,出一个大 Diff,code review 量大不说,还可能改出样式问题。我走的是三层递进路线:
- 页面级改造:只把最外层骨架的 div 替换成 header/nav/main/footer,视觉影响极小但结构和读屏收益立刻显现;
- 区块级改造:逐步把列表页的卡片容器、详情页的正文容器换成 article/section,模块内冲突只会影响局部;
- 内联级改造:把图片、引用、说明语块换成 figure/figcaption/blockquote 等更细的语义标签,这类改造不紧急,随每次迭代顺手做就行。
这种渐进式路线最大的好处是:每一次改动都可测试、可回滚、可单独合并,不会因为"语义化重构"而阻塞正常业务迭代。
6.3 保持"语义纯度"的日常小习惯
养成三个小习惯,语义化的意识自然就有了:
- 写完每一块 DOM,先回头看一眼标签是否说了"人话"——如果标签换成 class 才能懂,那这标签大概率选错了;
- 所有交互入口,优先保证是
<a>或<button>,不要只依赖 div 加onclick,不只语义好,键盘操作也天然可用; - 每次 commit 里如果出现大段 div,发 PR 时先默问一句——这里真的不需要语义吗?
我个人在实际项目中的体会是,语义化标签上手门槛极低,难的是克制和判断:选标签不是越语义越好,而是越准确越好。div 依旧是万能容器里最诚实的选择,但当你发现某个容器实际上承载着"导航""正文""独立内容"这些含义时,换掉它就不只是代码洁癖,而是对代码消费方最基本的尊重。下次再有人跟你抬杠"反正 div 也能实现",你可以温柔地回一句:能实现和表达清楚是两码事。