HTML 内容模型
用 WHATWG 内容模型理解元素可包含什么,解释非法嵌套时浏览器 DOM 修复为何让 CSS/JS 行为“幽灵化”。
#concept
#growing
#frontend
#html
[!info] related notes
HTML 内容模型
这个概念为什么出现
源码写了 <p><div>...</div></p>,页面“还能显示”,但 DevTools 里 div 已经跑出 p。于是:
.desc .card选不中- 事件委托挂错节点
- 水合/SSR 警告 DOM mismatch
根因通常不是 CSS,而是违反内容模型后的解析修复。
[!abstract] 一句话理解 每个元素都有“允许的子内容类别”;HTML 文本解析器按解析规则构建 DOM,结果可能与源码嵌套不同;不保证所有不合规内容都会被修正。
最小可观察示例
场景:商品描述组件把卡片塞进段落
<!DOCTYPE html>
<html lang="zh-CN">
<body>
<p id="desc" class="desc">
SKU-42 说明
<!-- 非法:div 不是 phrasing content,不能待在 p 里 -->
<div class="card">详细参数表</div>
</p>
<script>
// 教学点:打印真实父节点,而不是相信源码缩进
const card = document.querySelector('.card')
console.log(card.parentElement && card.parentElement.id)
console.log(document.getElementById('desc').innerHTML)
</script>
</body>
</html>
建议运行:浏览器打开控制台。
期望观察(典型结果):
在这个 text/html 示例中,card 的 parent 是 body,不是 p#desc
#desc 的 innerHTML 在 div 前被提前关闭
结合场景关注点
- 源码树 ≠ DOM 树(修复后)。
- 选择器与 React/Vue hydrate 都以 DOM 为准。
- 组件库输出 HTML 也必须遵守模型。
核心概念与准确模型
常见内容类别(教学压缩)
| 类别 | 直觉 | 例子 |
|---|---|---|
| Metadata | head 侧元数据 | link meta title |
| Flow | 大体 body 流 | p div section ul |
| Sectioning | 参与大纲分区 | article section nav aside |
| Heading | 标题 | h1-h6 |
| Phrasing | 段落内文本级 | span a strong img |
| Embedded | 嵌入 | img video iframe |
| Interactive | 交互 | button input(上下文受限) |
规范 vs 教学:完整类别与“可感知内容”细节以 HTML spec content models 为准;上表是工程导航模型。
高频非法嵌套
p只允许 phrasing content;div/section/ul不属于此类,与 CSS display 是 block 还是 inline 无关。\a内不允许交互内容、其他 a,或带 tabindex 的后代。\button内不允许交互内容或带 tabindex 的后代。\table内子元素放置错误;直接写 tr 本身可以合法,但 HTML 文本解析时通常会插入 tbody,改变直接子选择器的匹配。\
设计动机
若规范不约束子内容,每个浏览器“尽量显示”的修复策略会分叉,开发者无法共享同一 DOM 语义。内容模型是互操作合同。
边界情况
- 透明内容模型(如
a的部分情况):子内容要求随父上下文变化,不能死记硬背一个列表。 - DOM API 与文本解析不同:appendChild 等操作可能建立不符合内容模型的树,并不会自动套用 HTML 文本解析的所有修复。SSR 文本解析与客户端建树因此可能不一致。
- 模板/组件抽象:JSX 里“能编译”不等于 HTML 合法。
- Markdown 渲染:错误插件可能产出非法嵌套。
常见误区
[!warning] 常见误区:页面能看见 = HTML 正确 解析修复会掩盖错误,直到选择器、a11y 树或 SSR hydrate 爆。
工程实践
- 不确定就查 MDN 元素页的 “Permitted content/parents”。
- 对关键页面做 HTML validator 或组件快照。
- 在 UI 评审里同时审 DOM 结构,不只是视觉。
本节总结
内容模型是“树能否合法存在”的规则;调试前端诡异问题时,先看 DOM 是否被静默重写。
自测题
- 为什么
p > div是经典陷阱? - 框架组件如何避免产出非法嵌套?
参考答案
p的内容模型是 phrasing;在这里的 HTML 文本解析中,遇到 div 起始标签会提前关闭 p,导致父链变化。\- 设计 API 时限制 slot 可放内容类型;用 lint/测试检查输出 DOM;避免在 p/h* 等容器内无约束地渲染块级 children。
延伸阅读
| 资料 | 类型 | 支撑内容 |
|---|---|---|
| Content models | 规范 | 类别定义 |
| MDN: Content categories | 文档 | 可查表 |