接手过别人项目的同学应该都见过这种场面:一个页面从头到尾躺着一堆div,class 命名从box1、box2到box-list-item-footer-right,浏览器里看着没问题,可一旦你要改布局、调样式、做 SEO 优化,或者团队里换个人来维护,那种窒息感简直没法形容。我过去也被这种结构折磨过无数次,后来花了不少力气去啃 HTML5 的语义化标签,尤其是把section和div的边界彻底弄明白了之后,写页面的思路才真正打开。
这篇东西不是给你背标签定义,而是直接解决一个非常实际的问题:页面区域到底怎么划分才合理?section和div看着都能包内容,区别到底在哪?为什么明明效果一样,老手却坚持用section?以及最常见的坑——什么时候该用section、什么时候继续老老实实用div,而不是把页面里所有div无脑替换成section,那样反而会把语义搞得更乱。
先给个最简单的结论:div本身没有任何语义,它就是一个纯粹的块级容器;而section在 HTML5 里被赋予了“文档中的一个独立区域”这层语义,它代表的是一个有主题、有逻辑归属的内容分组。 只看渲染效果,两者几乎没有区别,但结构含义、可访问性、SEO 解读和维护成本完全是两码事。
下面我把这个结论展开,从一个实际页面的结构规划出发,手把手拆一遍。
1. 页面区域划分的痛点:为什么 div 用多了会失控
说section之前,得先搞清楚一个更底层的问题:我们到底为什么要在 HTML 里“划分区域”?
说白了,一个网页就是一堆信息的集合。拿最常见的博客详情页举例,它至少包含这几类信息:顶部导航、文章主体、作者信息、评论区、页脚推荐位。如果没有一套规则去约束这些内容的位置,浏览器渲染出来的就是一堵密不透风的“内容墙”,用户看着累,搜索引擎看不懂,屏幕阅读器更是无从下手。
div的诞生就是为了解决“把页面切成一块一块”这个需求的。早期 HTML 没有专门的结构标签,大家全靠<div id="header">、<div class="content">这种写法硬切,切完再配 CSS 浮动或定位,把页面搭出来。
这招在小页面里好用,页面一复杂就出问题。我见过最夸张的一个项目,一个列表页里嵌套了整整九层div,最内层的元素想改个样式,光选器就得写七八层。而且div最大的毛病在于:它只负责圈地,不负责告诉你这块地是干嘛的。
试想一下,你在审查元素面板里看到下面这段结构,能一眼判断出它是什么吗?
html复制<div>
<div>
<div>
<h2>如何优化前端性能</h2>
<p>这是一篇关于性能优化的文章……</p>
</div>
</div>
</div>
如果不看文字内容,你完全不知道哪个div是文章容器,哪个是列表项,哪个仅仅是为了布局加的外包层。而当页面里同时存在几十个这样的div时,维护者就只能在 class 命名和注释里找线索。
这也是相关热词里很多人吐槽“vscode 中 div 很多容易分不清”的根本原因——工具只能帮你高亮括号,但没办法替你补全语义。
HTML5 推出section、article、nav、aside、header、footer这一组结构标签,目标非常明确:让标签自己开口说话。你不用靠 class 名去猜一块区域是导航还是正文,标签本身就声明了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. section 与 div 的本质差异:语义、文档大纲和可访问性
先把两者的定义摆出来。
div是 HTML 4 时代就存在的通用块级容器,W3C 对它的定义非常简短:它没有语义含义,纯粹是用来给 CSS 提供样式钩子,或者把一组元素包起来方便统一处理。
section是 HTML5 新增的结构元素,规范里说得比较文绉绉,翻译成人话就是:页面中的一个独立区域,这个区域有自己明确的主题,通常会在区域内部配一个标题(heading)来概括这个主题。
这个“有主题”就是两者最本质的分界线。
section意味着这块区域的内容在逻辑上是内聚的。比如一个电商商品详情页:
html复制<section>
<h2>商品参数</h2>
<ul>
<li>品牌:xxx</li>
<li>型号:xxx</li>
</ul>
</section>
<section>
<h2>售后保障</h2>
<p>七天无理由退货……</p>
</section>
这段代码即使去掉所有样式,搜索引擎和辅助技术也能轻松识别出:页面里有两个主题区块,一个讲参数,一个讲售后。这种结构信息叫“文档大纲”(document outline),HTML5 引入section这类标签的核心目的之一,就是重新构建一套完整的文档大纲体系,让页面结构可以被程序化地理解和提取。
而div在文档大纲里是完全隐形的。它包再多层,文档大纲里也不会多出任何节点。这对布局来说是好事——意味着你可以随意嵌套div来实现视觉效果而不污染结构;但对内容划分来说就是缺陷——如果整个页面全是div,那文档大纲就是一堆平铺的标题,谁属于谁完全无法判断。
可访问性方面差距同样明显。
屏幕阅读器(比如 VoiceOver、NVDA)在读取页面时,会利用标签的语义来提供导航快捷键。比如用户按下一个快捷键,可以直接跳转到下一个section或article区域。如果页面全用div,这种区域导航功能就会失效,用户只能用最原始的方式从上到下逐行读,体验极差。
有些同学可能觉得“我就是个小开发者,哪需要考虑屏幕阅读器”,这个想法我劝你尽早改。很多大厂的前端面试里,无障碍已经是必考题了;而且在实际项目中,代码质量检查工具(如 eslint-plugin-jsx-a11y)会直接把“设置了点击事件却没有键盘支持”这类问题标红。语义化标签是最廉价的无障碍基础建设,你不需要额外做任何事,只要用对了标签,辅助技术就能直接受益。
再补一句渲染层面的结论,免得有人纠结视觉差异:section和div在浏览器默认样式表里都是display: block,视觉效果完全一致,没有任何区别。所以你不会因为把div换成section而看到页面任何变化。这也是很多初学者觉得“这俩不是一样的吗”的直接原因——但没有视觉差异不等于没有差异,真正的差异在结构信息层。
3. 实战判定指南:什么场景用 section,什么场景继续用 div
光讲概念容易懂,一到实际项目里还是拿不准。我根据自己的实践,总结了一套非常应试的判定方法,基本覆盖日常开发的绝大多数场景。
3.1 一个很灵的提问法:这块内容单独拿出去,能独立成立吗?
这个方法是判断section用得对不对的黄金法则。
问自己:如果把这块区域从当前页面拿出去,单独放到另一个页面里,它读起来还是通顺、有完整含义的吗?
- 如果答案是“能”——恭喜你,这块内容是一个独立的主题单元,优先考虑用
section。 - 如果答案是“不能”——它只是某个大主题下的一个零碎片段,或者纯粹是为了布局拼接的壳,那就继续用
div。
举个反例。页面上有一行用户信息,包含头像和昵称,它们被包在一个容器里做水平排列:
html复制<!-- 推荐:用 div,因为这段内容单独拎出来不构成独立主题 -->
<div class="user-info">
<img src="avatar.png" alt="用户头像">
<span>张小明</span>
</div>
这段东西单独拿去任何页面都没有意义,它只是“评论列表”这个section内部的零碎内容。强行给它包一层<section>反而会让文档大纲多出一个没标题的噪音节点。
再说一个正例。一个营销活动页,底部有一个“常见问题解答”模块,里面有五六个问答对:
html复制<!-- 推荐:用 section -->
<section>
<h2>常见问题</h2>
<dl>
<dt>发货时间?</dt>
<dd>付款后 48 小时内发货……</dd>
</dl>
</section>
这组问答有明确主题(“常见问题”),能独立成立,非常适合section。
3.2 第二个参考:区域内有没有标题性的内容
section的规范定义里有一句话很关键:一个section通常应该有一个标题(heading)。虽然不强制,但实践中这是一个非常实用的判断标志。
如果你规划的区域,内部结构天然存在一个可以被h1到h6概括的主题,那这就是一个“有资格”成为section的区域。反之,没有标题的裸div才更诚实。
带标题用section,不带标题用div。我用这个标准在团队里收了大量代码,效果立竿见影。
还有一类容易混淆的情况:div在某些老旧代码里被当成“分区”用,比如早年会写<div class="section">来模拟区块。这就是典型的用 class 硬造语义,现在完全没有必要了,标签本身就表达了语义,class 应该专注表达样式相关的内容或者状态相关的钩子,而不是去模拟语义。
3.3 真正的分水岭:布局壳 vs 内容区
我在教新人的时候喜欢做一个类比:div是做装修用的石膏板,你想隔出多少个房间、什么形状都可以,纯粹服务于视觉和布局;section是真正意义上的房间,每个房间有自己的功能定位,比如卧室、厨房、客厅。
对应到代码里:
html复制<div class="page-wrapper">
<header>...</header>
<section>
<h2>今日热销榜单</h2>
<div class="product-grid">
<!-- 这里每个商品卡片又是一个独立内容 -->
<article>
<h3>无线耳机</h3>
<p>¥299</p>
</article>
<article>
<h3>机械键盘</h3>
<p>¥459</p>
</article>
</div>
</section>
<footer>...</footer>
</div>
在这个例子里:
.page-wrapper只是把整页包起来做整体宽度控制,没有自己的主题,用div。.product-grid只是用网格布局把商品卡片排列起来,本身不是内容主题,用div。- “今日热销榜单”这个区域有标题、有一组同类内容,整体主题明确,用
section。 - 每个商品卡片内部有独立标题和内容,可以脱离页面单看,用
article(这个后面细讲)。
这种结构拿到审查元素面板里看,层级一目了然:哪些是纯粹为了布局加的外壳,哪些是真正的信息区块,清清楚楚。维护起来,你根本不需要像以前那样逐层去查 class 对应的模板代码。
4. section 的误用重灾区:这些场景请住手
明白了用section的好处之后,很多同学容易走向另一个极端:把页面里能换的div全换成section,结果语义不但没变清晰,反而更乱了。下面几个场景是我在代码评审里见得最多的误用,列出来给大家避坑。
4.1 只为了包一层方便写样式
有人把一个按钮、一个输入框、一行文字用section包起来,仅仅是为了给这个组合加个边框或背景色。这就是典型的“用语义标签做布局”,纯粹浪费了section的语义,还污染了文档大纲。纯样式诉求直接用div。
4.2 区域内没有独立主题,只有零散元素
页脚里有一排社交链接图标,有人顺手用section包了一层。如果这排图标没有自己的标题和独立意义,它只是页脚内容的一部分,正确的做法是直接用div或者ul,社交链接列表用ul包才是正解。
4.3 把 section 当万能容器到处套
还有人在每个section外面再套一层section,理由是“感觉这样结构更深更规范”。文档大纲不是你嵌套层数越多越清晰,恰恰相反,过多无意义的section会让大纲层级变得冗长而难以理解。HTML5 的规范起草者一直在强调一个原则:不要为了用而用,标签的选取应该基于内容的真实结构需求。
4.4 不知道什么时候用 article 而不是 section
这是比section和div更常见的一个混淆点。article和section在规范里是兄弟级别,但侧重点不同:article代表一个独立成篇、可以整体分发或复用的内容单元,比如一篇博客、一条评论、一个商品卡片、一条新闻;section代表的是页面里一个主题分区,可以是article内部的组成部分。
拿杂志做类比:整本杂志是页面,里面每篇文章是article,而每篇文章里“作者简介”“正文”“参考来源”这些分区用section。
所以一个常见的嵌套结构是:
html复制<article>
<h2>如何写出高性能 CSS</h2>
<p>正文内容……</p>
<section>
<h3>关于作者</h3>
<p>作者是资深前端工程师……</p>
</section>
</article>
一个完整的商品卡片或者一条博客正文,优先考虑article而不是section,因为它能脱离页面被单独引用和复用(比如出现在推荐位或者搜索列表里)。这也是很多前端规范里强调的:article是“自成一体”的最小完整单元。
4.5 老生常谈:别再用 table 布局、别用 div 模拟标题
这个话题跟section关系没那么直接,但既然是讲页面区域划分,我多提一嘴:现在还有人在布局阶段用<div class="title">代替<h2>,理由是想完全控制标题样式。这种做法会把文档大纲彻底废掉。标题标签不是样式标签,它是文档结构的骨架。内容区域的标题一定要用h1~h6,然后section包着标题和内容,这样结构才是真正完整的。至于样式,你用 class 去覆盖h2的默认样式完全没问题。
5. 用 HTML5 语义化标签重构一个真实页面:前后对比
理论说再多不如来一次完整的实操。我拿一个比较典型的企业官网“关于我们”页面来做示范,这种页面内容区块多,非常适合演示区域划分。
5.1 重构前的 div 堆叠结构
假设原始页面长这样:
html复制<div id="header">
<div class="logo">公司Logo</div>
<div class="nav">
<a href="#">首页</a>
<a href="#">关于我们</a>
</div>
</div>
<div class="main">
<div class="banner">
<h1>关于我们</h1>
<p>成立十年,专注企业服务</p>
</div>
<div class="intro">
<h2>公司简介</h2>
<p>我们是一家……</p>
</div>
<div class="history">
<h2>发展历程</h2>
<div class="timeline">
<div class="event">
<h3>2020年</h3>
<p>完成 A 轮融资</p>
</div>
<div class="event">
<h3>2023年</h3>
<p>用户突破百万</p>
</div>
</div>
</div>
<div class="contact">
<h2>联系我们</h2>
<p>邮箱:xxx@example.com</p>
</div>
</div>
<div class="footer">
<p>版权所有</p>
</div>
我见过太多这种代码了。第一眼看着还行,每个块都起了 class 名,好像也“语义化”了。但这里的问题在于,class 的语义和标签的语义是两码事。浏览器、搜索引擎、屏幕阅读器根本不管你 class 叫content还是body,它们只认标签本身的含义。一堆div在机器眼里就是一堆无差别的块,class="intro"和class="history"对机器来说唯一的区别就是 CSS 选择器不同。
5.2 重构后的语义化结构
下面是重构版本:
html复制<header class="site-header">
<div class="logo">公司Logo</div>
<nav aria-label="主导航">
<a href="#">首页</a>
<a href="#">关于我们</a>
</nav>
</header>
<main>
<section class="page-banner">
<h1>关于我们</h1>
<p>成立十年,专注企业服务</p>
</section>
<section aria-labelledby="intro-title">
<h2 id="intro-title">公司简介</h2>
<p>我们是一家……</p>
</section>
<section aria-labelledby="history-title">
<h2 id="history-title">发展历程</h2>
<div class="timeline">
<article class="event">
<h3>2020年</h3>
<p>完成 A 轮融资</p>
</article>
<article class="event">
<h3>2023年</h3>
<p>用户突破百万</p>
</article>
</div>
</section>
<section aria-labelledby="contact-title">
<h2 id="contact-title">联系我们</h2>
<p>邮箱:xxx@example.com</p>
</section>
</main>
<footer class="site-footer">
<p>版权所有</p>
</footer>
对比一下:
- 原来的
id="header"换成了语义明确的<header>,这个标签本身就表示“页眉区域”。 - 原来
.main容器换成了<main>,表示页面主要内容区,一个页面里main建议只有一份,它直接对应文档大纲的核心内容。 - 原来的“公司简介”“发展历程”“联系我们”是三个有标题有主题的内容块,换成了三个
<section>,并且每个section都配了对应的标题。 - 发展历程里每个事件本身是独立的完整信息条目,换成了
<article>。
我还在 banner 区域的section上加了aria-labelledby去掉空标题的场景,这个属性可以把section和它的标题显式关联起来,对辅助技术更友好。这种做法在标准里是推荐的,相当于告诉屏幕阅读器“这个区域的名称是啥”。
5.3 重构后文档大纲发生了什么变化
在没有 CSS 的情况下,机器解读重构后代码的逻辑树大概是:
- 站点页眉(header)
- 页面主要内容(main)
- 关于我们横幅
- 公司简介
- 发展历程
- 2020年事件
- 2023年事件
- 联系我们
- 站点页脚(footer)
而重构前那一堆div,机器解读出来的大纲就只有几个孤零零的h1/h2/h3标题,像散落的珠子,没有线串起来。以后谁的代码可维护性更强,高下立判。
6. team 协作中怎么让 div 和 section 各归其位
前面讲的都是单兵作战时的判断方法,但实际开发里还有个麻烦的情况:一个项目好几个人一起写,每个人对语义化的理解不一样。有人喜欢全用div,有人喜欢到处是section,结果一锅粥。
我现在的团队里已经跑了一套比较成熟的协作方案,分享给大家参考。
6.1 先定场景规范,再写代码
我们在前端规范文档里明确了一张“场景对照表”,所有新代码都要按这个表来:
| 场景 | 推荐标签 | 原因 |
|---|---|---|
| 页面整体外壳、布局包裹 | div | 不产生语义噪音,容器职责纯粹 |
| 文章、新闻、商品卡片、评论等完整内容块 | article | 可直接复用和分发 |
| 页面中有标题的主题分区 | section | 提供文档大纲分支 |
| 顶部导航、侧边栏、页脚 | header / aside / footer | 使用 HTML5 结构标签,语义唯一 |
| 一组同类的导航链接 | nav | 语义明确,同时方便辅助技术跳过 |
| 没有任何语义的图组、按钮组、装样式的层 | div | 别给它加戏 |
这张表不是死的,但有了它以后,团队里的同学写代码时至少有一个共同的参照系,不会出现我之前见过的那种同一个页面里三种不同写法的混乱状态。
6.2 利用代码检查工具做强制约束
人总会偷懒或者遗忘,那就交给工具去管。我们的 CI 流程里加了 eslint-plugin-jsx-a11y,里面有一堆和语义化相关的规则。其中几条比较核心的:
no-static-element-interactions:禁止在div上直接绑点击事件还不加键盘事件,逼你换成button或加role等补偿。no-noninteractive-element-to-interactive-role:防止非交互标签强行扮演交互角色。heading-has-content:标题必须有内容,防止出现空标题的无意义节点。
团队里跑了一段时间之后,代码评审的效率明显高了,因为低级问题在提交前就被规则拦住了,评审只需要关注真正的逻辑和结构问题。
6.3 用浏览器插件检查文档大纲
还有一个我日常必用的调试技巧:在 Chrome 的 DevTools 里其实没有直接展示文档大纲的面板,但你可以装一个 HeadingsMap 之类的浏览器扩展,它能列出当前页面的完整标题层级。每当我重构完一个页面,都会先打开它的标题大纲看一眼:如果大纲干干净净、层级符合预期,说明语义化结构没问题;如果大纲乱糟糟的,有跳级、有落单的标题,那就说明结构还需要调整。
这个习惯帮我发现过不少问题,比如多个h1同时出现在一个页面里、某些区域忘了加标题导致大纲断裂、该用article的地方用了section导致内容无法独立成章等。
7. 从 div 到 section 的迁移技巧:老项目怎么逐步改进
最后一个部分,说说存量代码。老项目不能推倒重来,但是看着一屏div又难受,怎么办?我建议按下面的顺序渐进式重构,风险最小。
7.1 从页面的最高层开始替换
不要一头扎进最内层的细节里,从最外层的结构标签开始动手。把<div id="header">换成<header>,把<div class="footer">换成<footer>,把这个页面里最显眼的“大骨头”换好。因为这些标签的渲染样式几乎不会变(默认都是块级),所以这一步只要 CSS 里的选择器用的是 class 而不是 ID 或标签名,基本不会出问题。
如果 CSS 里写的是#header { ... }这种 ID 选择器,换掉标签后记得同步调整选择器。比较稳妥的做法是 CSS 全用 class,标签只管语义。比如:
css复制/* 不要这样 */
header {
background: #333;
}
/* 推荐这样 */
.site-header {
background: #333;
}
为什么?因为你的 CSS 一旦写在标签选择器上,将来某个页面里内嵌的<header>(比如article内部也有个header)也会被波及,虽然可以靠覆盖救回来,但没必要给自己埋这种雷。
7.2 找“有标题的内容块”做 section 化
第二优先级是那些已经有h2或者h3标题、却被div包裹着的内容块。这种块最容易被升级成section,因为条件完全满足。顺着页面大纲一个个找,凡是外面包着div、内部有独立标题的,基本都可以直接升级。这是投入产出比最高的改造。
7.3 单独改造“卡片类”循环内容
如果页面里有列表循环,比如新闻列表、商品列表、评论列表,这些循环项的内部结构通常每个都会由几个标签组成。这些循环项就是天然的article候选。把循环项的最外层从div改成article,对 SEO 和阅读器都有好处。
7.4 剩下的不要动
那些纯为了排列、间距、颜色而存在的半透明包装层,就让它们安安静静当div好了。语义化改造的目标是让内容结构的骨架清晰,不是把每一个元素都赋予语义。该低调的容器就低调,这才是div和section和谐共处的最佳状态。
再多说一句关于调试工具的心得。Chrome DevTools 的 Elements 面板支持按标签名搜索,重构的时候可以在搜索栏直接输入section、article、main,快速定位所有已语义化的区域和遗漏的区域。改完一轮,搜索div的时候会发现数量有明显下降,那种爽感还是挺真实的。
就我个人经验来说,花一晚上把整页的div合理地换成section之后,最大的感受是:后面加功能的时候,新代码放哪儿变得非常明确。导航往header里塞,主内容进main,单篇内容进article,独立主题分区进section,其他零碎东西丢div。不需要多想,也不容易放错。
HTML 标签的语义化不是性能优化,不会让你的页面加载快哪怕 1 毫秒;也不是炫技,短时间内在页面上看不出任何变化。它更像是一种代码层面的基础设施投入,短期不显眼,但到了重构、交接、SEO 调整、无障碍适配这些需要“读懂页面结构”的时刻,你会庆幸当初没有偷懒,没有让所有区域都淹没在无边无际的div里。
