5213 words
26 minutes
为什么语义化 HTML 仍然重要

在某个时候,我们忘记了如何编写 HTML——或者说,忘记了为什么它如此重要。

现代开发工作流程优先考虑组件、工具类和 JavaScript 重度渲染。HTML 变成了一个副产品,而不是基础

这种转变是有代价的——在性能、可访问性、弹性以及机器(和人)如何解释你的内容方面。

我在其他地方写过关于 JavaScript 如何杀死 Web 的文章。但这个故事中最容易修复、最被忽视的部分是语义化 HTML

这篇文章讲述的是我们失去了什么——以及为什么它仍然重要。

语义化 HTML 是机器理解含义的方式#

HTML 不仅仅是我们在页面上放置元素的方式。它是一种语言——拥有表达含义的词汇表。

<article><nav><section> 这样的标签不是装饰性的。它们表达意图。它们传达层次结构。它们告诉机器你的内容是什么,以及它如何与其他所有内容相关联

搜索引擎、可访问性工具、AI 代理和基于任务的系统都依赖于结构信号——有时是显式的,有时是启发式的。并非每个系统都需要完美的标记,但当它们能够利用时,语义化 HTML 可以为它们提供清晰度。在一个充满结构模糊页面的 Web 中,这种清晰度可能是竞争优势。

语义化标记不能保证更好的索引或提取——但它创建了系统现在和将来可以使用的基础。这是质量结构意图的信号。

如果一切都是 <div><span>,那么就没有什么是有意义的

这不仅仅是糟糕的 HTML——而是无意义的标记#

很容易将此视为纯粹性问题。只要看起来正确,谁在乎你使用 <div> 还是 <section> 呢?

但这不是关于学究气。无意义的标记不仅让你的网站更难阅读——它还让网站更难渲染、更难维护、更难扩展

这种抽象导致的标记通常看起来像这样:

<div class="tw-bg-white tw-p-4 tw-shadow tw-rounded-md">
  <div class="tw-flex tw-flex-col tw-gap-2">
    <div class="tw-text-sm tw-font-semibold tw-uppercase tw-text-gray-500">ACME Widget</div>
    <div class="tw-text-xl tw-font-bold tw-text-blue-900">Blue Widget</div>
    <div class="tw-text-md tw-text-gray-700">Our best-selling widget for 2025. Lightweight, fast, and dependable.</div>
    <div class="tw-mt-4 tw-flex tw-items-center tw-justify-between">
      <div class="tw-text-lg tw-font-bold">$49.99</div>
      <button class="tw-bg-blue-600 tw-text-white tw-px-4 tw-py-2 tw-rounded hover:tw-bg-blue-700">Buy now</button>
    </div>
  </div>
</div> 

当然,这是有效的。它有样式。它能渲染。但它在语义上是死的。

它无法让你感知这个内容是什么。这是产品列表?博客文章?还是行动号召?

你无法一眼看出——屏幕阅读器、爬虫或试图提取定价数据的代理也无法看出。

这是具有有意义结构的相同内容:

<article class="product-card">
  <header>
    <p class="product-brand">ACME Widget</p>
    <h1 class="product-name">Blue Widget</h1>
  </header>
  <p class="product-description">Our best-selling widget for 2025. Lightweight, fast, and dependable.</p>
  <footer class="product-footer">
    <span class="product-price">$49.99</span>
    <button class="buy-button">Buy now</button>
  </footer>
</article>

现在它讲述了一个故事。有结构。有意图。你可以在 CSS 中定位它。你可以在爬虫中提取它。你可以在屏幕阅读器中导航它。它有意义。

语义化 HTML 是可访问性的基础。没有结构和意义,辅助技术无法解析你的内容。屏幕阅读器不知道要宣布什么。键盘用户会卡住。语音界面无法找到你埋在 div 中的内容。干净、有意义的 HTML 不仅仅是良好实践——它是人们访问 Web 的方式。

这并不是说框架本质上是坏的或不可访问的。Tailwind、原子类和内联样式绝对可以有用——特别是在复杂项目或大型团队中,一致性和速度很重要。它们可以减少认知开销。它们可以提高速度。

但它们是工具,不是答案。当每个组件都退化为几乎重复的工具类的汤——为每个布局和断点进行调整——你就失去了重点。结构消失了。目的被模糊了。

这不是关于抽象。这是关于你在过程中失去的东西。

这种损失不仅伤害语义——它还伤害性能。事实上,这是现代 Web 感觉更慢、更重、更脆弱的最大原因之一。

语义腐烂破坏性能#

我们已经将 HTML 仅仅是渲染目标的想法正常化了——我们可以向浏览器抛出任意标记,并相信它会解决问题。确实如此。浏览器在修复我们的混乱方面非常出色。

但这种宽容是有代价的。

渲染引擎被设计为容错的。它们会推断角色,修补糟糕的结构,并尝试按你的意图渲染内容。但每次它们必须这样做——每次它们必须猜测你的 <div>试图成为什么——都会消耗时间。这是 CPU 周期。这是 GPU 时间。这是功耗,特别是在移动设备上。

让我们分解膨胀在哪里以及如何打击最严重——以及为什么这很重要。

大型 DOM 渲染缓慢#

DOM 中的每个节点都会增加开销。在渲染过程中,浏览器遍历 DOM 树,构建 CSSOM ,计算样式,解析布局,并绘制像素。更多的节点意味着每个阶段都有更多的工作。

这不仅仅是关于下载大小(尽管这也很重要——更多的标记意味着更多的字节,可能压缩效率更低)。这是关于渲染性能。臃肿的 DOM 意味着更长的布局和绘制阶段,更多的内存使用,以及更高的能耗。

即使是简单的交互——比如打开模态框或展开列表——也可能触发在你臃肿的 DOM 中爬行的重排。突然间,你的”简单”页面开始滞后、卡顿或抖动。

你可以在 Chrome DevTools 中看到这一点。打开性能选项卡,记录跟踪,观察每次布局引擎空转时火焰图的亮起。

有趣的事实:解析不是瓶颈——像 Chromium 这样的浏览器可以在现代 CPU 上以每秒数十 GB 的速度处理 HTML。真正的成本来自 CSSOM 构建、布局、绘制和合成期间。此外,HTML 解析只有在遇到非延迟的 <script> 或阻塞渲染的样式表时才会阻塞——这再次强调了为什么干净的标记仍然重要,但你也需要智能的加载顺序。

复杂树结构导致布局抖动#

但这不仅仅是关于你有多少标记——而是关于它是如何结构化的。深度嵌套、包装器膨胀和过度抽象的组件创建了难以理解且渲染成本高昂的 DOM 树。浏览器必须更努力地弄清楚哪些变化影响什么——这就是事情开始崩溃的地方。

切换一个类,你可能会使整个视口的布局失效。这种变化通过父子链级联,触发布局偏移和视觉不稳定。组件意外地重新定位自己。滚动锚定失败,用户在交互过程中失去位置。整个体验变得不稳定。

因为这一切都是实时发生的——在每次交互中——它会影响你的帧预算。目标是 60fps?这给你每帧只有约 16ms。超出这个预算,用户会立即感受到延迟。

你会在 Chrome 的 DevTools 中看到这一点——在布局偏移区域图表中,错过的帧堆积起来。

当你改变 DOM 时,浏览器并不总是重新布局整个树——有增量布局处理。但深度嵌套或模糊的标记仍然会触发昂贵的祖先检查。像 Facebook 的 “Spineless Traversal” 这样的项目表明,当需要检查许多节点时,浏览器仍然会付出性能代价。

冗余的 CSS 增加重计算成本#

臃肿的 DOM 已经够糟糕了——但臃肿的样式表让情况变得更糟。

现代 CSS 工作流程——特别是在组件化系统中——经常导致重复。每个组件都声明自己的样式——即使它们重复。没有级联。没有共享上下文。特异性变成一团糟,覆盖成为默认行为。

例如,这通常看起来像这样:

/* button.css */
.btn {
  background-color: #006;
  color: #fff;
  font-weight: bold;
}

/* header.css */
.header .btn {
  background-color: #005;
}

/* card.css */
.card .btn {
  background-color: #004;
}

每个文件都重新定义相同的东西。浏览器必须解析、应用并协调所有这些。将此乘以数百个组件,你的 CSSOM——浏览器所有 CSS 规则的内部模型——就会膨胀。

每次有变化时(比如类切换),浏览器必须重新评估哪些规则适用于哪里。更多规则,更多重计算。在低端设备上,这成为瓶颈。

是的,像 Tailwind 这样的原子 CSS 系统可以减少文件大小并增加重用。但只有在有意使用时才行。当每个组件都被包装在十几层实用类中,每个实用类都被稍微调整(这里的边距,那里的字体),你最终会得到数千种独特的组合——其中许多几乎相同。

成本不仅仅是大小。而是流失

浏览器匹配选择器的方式:浏览器从右到左匹配选择器(例如,对于 div.card p span,它们检查 span → p → div.card 等)。这对于清晰、具体的选择器是高效的——但臃肿的深层树或通用级联规则会强制进行大量的过度扫描。

自动生成的类名破坏缓存和定位#

看到像 .sc-a12bc.jsx-392hf.tw-abc123 这样的类名已经变得很常见。这些通常是 CSS-in-JS 系统、作用域样式或构建时哈希的结果。意图很明确:本地化样式以避免全局冲突。这不是一个坏主意。

但这种方法带来了不同类型的脆弱性

如果你的类是短暂的——如果它们在每次构建时都会改变——那么:

  • 你的分析标签会失效。

  • 你的端到端测试需要持续维护。

  • 你的缓存策略会崩溃。

  • 你的标记差异变得不可读。

  • 你的 CSS 默认变得不可重用。

从性能角度来看,最后一点是关键的。缓存只有在事物可预测时才有效。浏览器缓存和重用解析样式表的能力取决于一致的选择器。如果每个组件、每次构建、每次部署都改变其类名,浏览器必须重新解析和重新应用一切。

更糟糕的是,它迫使工具依赖脆弱的变通方法。想通过标签管理器定位结账漏斗中的按钮?如果它被包装在三层哈希组件中,祝你好运。

这不是假设的。这是现代前端技术栈中的常见痛点,它会使一切膨胀——代码、工具、渲染路径。

可预测的语义化类名不仅让你的生活更轻松。它们让 Web 更快。

语义标签可以提供布局提示#

语义化 HTML 不仅仅是关于意义或可访问性。它是脚手架。结构。这种结构为你和浏览器提供了可以使用的东西。

<main><nav><aside><footer> 这样的标签不仅仅是语义化的——它们默认是块级的,并且自然地分割页面。这种分割通常与浏览器处理和绘制内容的方式一致。它们不保证性能提升,但它们为性能提升创造了条件。

当你的布局有明确的边界时,浏览器可以更有效地确定其工作范围。它可以隔离样式重计算,避免不必要的重排,并更好地管理滚动容器和粘性元素等内容。

更重要的是:在绘制合成阶段,浏览器可以将渲染工作分布到多个线程中。GPU 合成管道受益于结构良好的 DOM 区域——特别是当它们与 contain: paintwill-change: transform 等属性配对时。通过创建隔离层,你减少了重新栅格化页面大部分内容的开销。

如果一切都是嵌套 <div> 的巨大堆栈,就没有这种隔离的明确机会。每次交互、动画或调整大小事件都有触发影响整个树的重排或重绘的风险。你不仅让自己的工作变得更困难——你还在给渲染引擎制造瓶颈。

简单地说:语义标签帮助你浏览器合作,而不是对抗它。它们不是魔法,但它们让魔法成为可能。

动画和合成灾难#

动画是结构良好的 HTML 要么闪耀……要么灾难性失败的地方。

现代浏览器旨在将动画工作卸载到 GPU。这就是在 60fps 或更高帧率下实现丝滑过渡的原因。但要实现这一点,浏览器需要将动画元素隔离到自己的合成层上。只有某些 CSS 属性符合这种 GPU 加速处理的条件——最显著的是 transformopacity

如果你动画化像 topleftwidthmargin 这样的属性,你就在触发布局引擎。这意味着要为变化下游的所有内容重新计算布局。这是主线程工作,而且很昂贵。

在简单页面上?也许你能侥幸过关。

在具有数十个兄弟元素和依赖关系的深度嵌套组件上?每个动画都变成布局抖动。一旦你的动画帧预算超过 16ms(60fps 的限制),事情就会变得卡顿。动画结巴。交互延迟。滚动变得缓慢。

你可以在 DevTools 的性能面板中看到这一点——布局重计算、样式失效和绘制操作点亮了火焰图。

语义化 HTML 在这里也有帮助。适当的结构边界允许更有效地使用现代 CSS 包含策略:

contain: layout; 告诉浏览器它不需要重新计算元素外部的布局。

will-change: transform; 提示需要合成层。

isolation: isolate;contain: paint; 可以帮助防止视觉溢出并强制 GPU 层。

但这些工具只有在你的 DOM 是合理的时候才有效。如果你的动画组件嵌套在不可预测的通用 <div> 堆中,浏览器无法干净地隔离它。它不知道什么可能受到影响——所以它保守行事并重新计算一切。

这不是浏览器缺陷。这是开发者失误。

动画不仅仅是关于什么移动。它是关于什么不应该移动。

渲染优化:渲染和绘制在现代引擎中是并行操作。但 DOM/CSS 变化经常强制主线程同步,破坏了这种优势。

通过 will-change: transform 或更新的 layer() 语法进行 CSS 分层告诉 GPU 单独处理合成。这避免了主线程中的布局和绘制——但只有当 DOM 结构允许不同的分层容器时才行。

CSS 包含和可见性:强大但脆弱#

现代 CSS 为我们提供了管理性能的强大工具——但只有当你的 HTML 给它们喘息空间时,它们才有效。

contain 为例。你可以使用 contain: layoutpaint 甚至 size 来告诉浏览器”不要看这个盒子外面——这里面的任何东西都不会影响页面的其余部分。“这可以大幅减少布局重计算的成本,特别是在动态界面中。

但这只有在你的标记有明确的结构边界时才有效。

如果你的内容纠缠在非语义包装器的嵌套中,或者如果容器继承了意外的样式或依赖关系,那么包含就变得不可靠。你无法安全地包含你无法隔离的东西。浏览器不会冒这个风险。

同样,content-visibility: auto 是现代 CSS 武器库中最被低估的工具之一。它让浏览器跳过渲染屏幕上不可见的元素——有效地”虚拟化”它们。这对于长页面、信息流或无限滚动组件来说是巨大的。

但它有注意事项。它需要可预测的布局、滚动锚定和结构一致性。如果你的 DOM 很混乱,或者你的组件在树上下泄漏样式和依赖关系,它会适得其反——引入布局跳跃、渲染错误或损坏的焦点状态。

这些不是魔法子弹。它们是性能契约。混乱的标记会破坏这些契约。

语义化 HTML——以及干净、结构良好的 DOM——是使这些工具首先可行的原因。

CSS 包含性能:MDN 的文档强调了 contain: contentlayout+paint+style 的简写)如何让浏览器独立优化整个子树。现实世界的 A/B 测试显示,在使用 content-visibility: auto 的电商页面上,INP 延迟有所改善。

代理是新用户——它们关心结构#

Web 不再只是为人类服务。

搜索引擎是第一波——解析内容、提取含义,并基于结构和语义进行排名。但现在我们正在进入 AI 代理、助手、爬虫、任务运行器和 LLM 支持的自动化时代。这些系统不浏览你的网站。它们不滚动。它们不点击。它们解析。

它们查看你的标记并询问:

  • 这是什么?

  • 它是如何结构化的?

  • 什么是重要的?

  • 它与其他一切有什么关系?

干净的语义化 DOM 清楚地回答了这些问题。<div> 汤则不能。

当这些代理必须在十个都声称销售相同小部件的网站中选择时,更容易解释、提取和总结的那个将获胜。

这不是假设的。Google 的购物系统、像 Perplexity 这样的总结代理、像 Arc 这样的 AI 浏览器,以及可访问性的辅助工具都是这种转变正在进行的例子。你的网站不再只是视觉体验——它是一个界面。一个 API 。一个数据集。

如果你的标记无法支持这一点?你就被排除在对话之外了。

是的——智能系统可以并且确实在必要时推断结构。但这是额外的工作。这是不精确的。这是风险。

在竞争激烈的环境中,结构良好的标记不仅仅是优化——它是差异化因素。

结构就是韧性#

语义化 HTML 不仅仅是帮助机器理解你的内容。它是构建在压力下保持完整的界面。

干净的标记更容易调试。更容易适应。更容易渐进增强。如果你的 JavaScript 失败,或者样式表没有加载,或者布局在边缘情况的屏幕上崩溃——语义化 HTML 意味着那里仍然有可用的东西。

这不仅仅是良好实践。这是为现实世界构建软件的方式。

因为真实用户有不稳定的连接。真实设备有有限的电力。真实会话包括你没有测试过的边缘情况。

语义化标记给你一个基线。一个后备方案。一个基础。

结构不是可选的#

如果你想为性能、可访问性、可发现性或韧性而构建——如果你想让你的网站快速、可理解且适应性强——从有意义的 HTML 开始。

不要把标记当作事后想法。不要让你的工具埋没结构。不要构建只有在星星对齐且 JavaScript 加载时才工作的界面。

语义化 HTML 是基础。它快速。它健壮。它自描述。它面向未来。

它不会阻止你使用 Tailwind 。它不会阻止你使用 React 。但它确实要求你深思熟虑。有意图地设计你的结构。编写讲述故事的代码——不仅仅对人类,也对浏览器、机器人和代理。

这不是怀旧。这是基础设施。

如果 Web 要在下一波复杂性、自动化和期望中生存——我们需要记住如何正确构建它。

这从记住如何编写 HTML 开始——以及为什么我们要这样编写。不是作为 JavaScript 的副产品,或工具的输出,而是作为随后一切的基础。

为什么语义化 HTML 仍然重要
https://0bipinnata0.my/posts/weekly-translate/why-semantic-html-still-matters/
Author
0bipinnata0
Published at
2025-08-19 22:46:26