Skip to content
This page has been auto-translated and may contain errors.View in English

语义化 HTML

docs.scrimba.com

两个页面看起来可能完全相同,但底层构建方式完全不同。一个使用以其内容命名的元素:头部、导航栏、主要区域。另一个将所有内容都包装在相同的匿名盒子中,并依靠类名来区分它们。在屏幕上看不出区别。在所有其他方面,它们根本不是同一个页面。按元素的含义来选择元素就是语义化 HTML 的全部内容。

语义化的含义

语义化 是指"与含义相关"。语义化元素是其名称说明了其内容含义的元素。段落放在 <p> 中是因为它是一个段落。导航菜单放在 <nav> 中是因为它是导航。标签名称说明了事物的本质。

你几乎可以用一个万用盒子构建任何页面,并通过样式使其看起来正确。但那样的话页面上就没有任何东西说明它是什么。选择命名内容的元素就像搬家时给箱子贴标签一样。一切仍然可以装在普通的无标签箱子里,但标签是让任何其他人或浏览器能够理解里面的东西而无需打开每一个的方式。

当元素的名称描述其内容的含义而不是内容的外观时,该元素是 语义化 的。<nav><article><button> 是语义化的:它们告诉你内容的用途。<div><span> 不是:它们是泛型容器,本身没有任何含义。

好处是含义存在于标记中,其他工具可以读取它。浏览器知道 <button> 是可点击的,无需你添加任何内容就可以用键盘聚焦。屏幕阅读器 可以将 <nav> 宣布为导航区域。搜索引擎将 <article> 视为独立的内容片段。如果元素是样式化的 <div>,这些都不会工作,因为 <div> 对其内容没有做任何承诺。

语义化是区分有含义的元素和只显示像素的元素的差异。当你写 <nav> 时,你不仅仅是在绘制一个盒子,你是在陈述关于文档的事实:这个区域是导航。这个事实被记录在 无障碍树 中(浏览器在 DOM 旁边建立的并行结构,描述每个元素的角色和状态以供辅助技术使用),这是除了视力正常的鼠标用户之外的工具所作用的对象。

实际的测试是替换。如果你用裸露的 <div> 替换一个元素并将其样式设置为看起来相同,你会失去什么?对于 <p> 或装饰性包装器,没有有意义的东西。对于 <button>,你会失去键盘焦点、Enter 和 Space 键处理,以及屏幕阅读器的"按钮"公告,你必须手动重建所有这些。那个差距就是语义化:浏览器因为元素的名称告诉了它内容是什么而免费给你的行为和含义。选择命名元素不是风格偏好,它是获得正确行为的最便宜方式。

Juno语义化的含义 语义化意味着标签是按其内容命名的:段落在 <p> 中,菜单在 <nav> 中。你可以把所有内容放在普通盒子中并样式化使其看起来相同,但那样就没有东西说明它是什么。可以把它看作为你的搬家箱子贴标签,这样任何人都能知道里面是什么。
Juno语义化的含义 语义化元素命名其含义,<div> 命名空无一物。好处是浏览器、屏幕阅读器和搜索引擎都可以直接从标记中读取含义。选择描述内容的元素,只有当真的没有什么可描述的时候才使用 <div>
Juno语义化的含义 运行替换测试:用样式化的 <div> 替换元素,问问你失去了什么。如果答案是键盘焦点、无障碍树中的角色或屏幕阅读器的公告,那就是你免费获得的语义化。手动重建它总是比使用命名元素更多工作。

标志元素

少量元素用于标记页面的大区域。这些是你首先要使用的:

  • <header> 是页面或章节的顶部:徽标、标题、介绍。
  • <nav> 是一组导航链接,例如主菜单。
  • <main> 是主要内容,页面存在的原因。
  • <section> 是相关内容的分组块,通常有自己的标题。
  • <article> 是独立的内容片段,即使单独显示也仍有意义,例如博客文章或新闻故事。
  • <aside> 是侧面内容:相关链接、注释、广告。
  • <footer> 是页面或章节的底部:版权、联系方式、小字号文本。

规则始终是一样的。如果一个区域是导航,它就在 <nav> 中,而不是 <div> 中。如果是页脚,它就在 <footer> 中。你用与该区域相匹配的元素为该区域命名。

html
<header>
  <h1>王的厨房</h1>
  <nav>
    <a href="/recipes">食谱</a>
    <a href="/about">关于</a>
  </nav>
</header>
<main>
  <h2>平日晚餐</h2>
  <p>下班后可以做的快手菜。</p>
</main>
<footer>
  <p>在北京制作。</p>
</footer>

七个元素将页面分割成其主要区域,它们统称为 标志元素,因为每一个都标记了一个读者或工具可以导航到的区域:

  • <header> 介绍其最近的容器:在页面顶部它保存网站品牌,在 <article> 内它保存该文章的标题和署名。
  • <nav> 包装一块导航链接。将其用于重要的链接,主菜单或目录,而不是用于页面上的每个随意链接。
  • <main> 保存文档的主要内容,即这个页面特有的部分而不是在整个网站中重复的部分。
  • <section> 分组属于一起的相关内容,通常由标题引入。
  • <article> 是一个完整、独立的单元:帖子、评论、产品卡。测试是它是否仍然有意义,即使被拉出来单独显示。
  • <aside> 保存与其周围内容相关的内容:侧栏、拉引号、相关链接。
  • <footer> 用总结材料关闭其最近的容器:署名、链接、法律文本。

关键思想是这些大多是上下文相关的。<header><footer> 描述它们所在的任何东西,因此单个页面可以包含多个,页面一对,每个 <article> 一对。<main> 是例外,在下一部分中介绍。

html
<article>
  <header>
    <h2>烤番茄汤</h2>
    <p>王 著,20 分钟</p>
  </header>
  <p>将番茄对切并烘烤至软。</p>
  <footer>
    <p>四份。</p>
  </footer>
</article>

七个区域元素是 HTML 标志:在无障碍树中公开可导航区域的元素。每个都映射到一个辅助技术可以列出和跳转到的角色,这是它们除了在源中看起来整洁之外重要的全部原因。

  • <header> 在页面级别映射到 banner 角色,但当它嵌套在 <article><aside><main><nav><section> 内时不会,此时它只是一个介绍性的分组,没有标志角色。
  • <footer> 反映这一点:在页面级别为 contentinfo,当嵌套在这些分节元素之一内时为普通分组。
  • <nav>navigation 角色。一个页面通常有多个,一个主菜单和一个页面内目录,这是下面标签的来源。
  • <main>main 角色,标记主要内容。每个页面一个。
  • <aside>complementary 角色:支持主要内容但独立于它的内容。
  • <section> 仅当它有可访问的名称时才成为 region 标志,通常来自与其关联的标题;未命名的 <section> 不公开标志,对于无障碍树来说,接近 <div>
  • <article> 携带 article 角色,一个独立的作品,工具可以枚举但这不是与其他角色相同意义上的导航标志。

设计目标是一个非视觉用户可以像视力正常的用户扫描布局一样快速地调查的文档。屏幕阅读器可以生成每个标志的列表,让用户直接跳到"main"或"navigation"而无需听到中间的内容。这仅在区域用真实标志元素标记时有效。满墙的 <div> 会产生一个完全没有标志的文档,所以通过它的唯一方式是从上到下。

html
<body>
  <header>            <!-- banner: 页面级别 -->
    <nav>...</nav>    <!-- navigation -->
  </header>
  <main>              <!-- main: 唯一的主要区域 -->
    <article>...</article>
    <aside>...</aside> <!-- complementary -->
  </main>
  <footer>...</footer> <!-- contentinfo: 页面级别 -->
</body>
Juno标志元素 有七个值得了解的区域标签:<header><nav><main><section><article><aside><footer>。每一个都命名了页面的一部分,你选择与你正在构建的区域相匹配的那一个。你今天不需要记住它们,它们会反复出现直到你掌握。
Juno标志元素 七个元素将页面分割成区域,其中大多数描述它们所在的任何东西,所以 <header><footer> 可以出现多次。救过我很多次的 <article> 测试:如果你拉出它,这能单独成立吗?如果是,它就是文章;如果它是一个有标题的分组块,它就是章节。
Juno标志元素 这些是标志,每一个都映射到屏幕阅读器可以跳转到的角色,这是为什么它们胜过一堆 <div> 的原因。两个值得记住的细节:<header><footer> 仅在顶级时才成为 bannercontentinfo 标志,一个 <section> 仅在它有可访问的名称后才是标志。对于辅助技术,未命名的章节基本上就是 div。

div 和 span,非语义化的备选方案

页面的并非每一部分都有值得命名的含义。有时候你只需要一个盒子来分组,这样你就可以对其进行样式化,或者包装几个单词来给它们着色。为此有两个纯粹的元素,它们本身没有含义。

<div> 是一个泛型块:一个空容器,当没有其他元素适合时使用。<span> 对于一行内的小文本块是相同的想法。它们都没有说明其内容的任何东西。它们纯粹作为样式和分组的钩子而存在。

html
<p>今日总额为 <span class="price">¥299.00</span>。</p>

<div class="card">
  <h2>特别优惠</h2>
  <p>所有面食买一送一。</p>
</div>

<span> 包装价格,这样你可以给它着色。<div> 分组卡片,这样你可以在它周围放一个边框。仅当没有更有意义的东西适合时才使用这些,因为它们对浏览器没有说明其内容的任何东西。

<div><span> 是仅有的完全没有含义的两个元素,这正是它们的用途。<div> 是一个泛型块级容器;<span> 是一个泛型内联容器。你用它们作为 样式钩子:当你需要分组或定位内容但没有任何有意义的东西要说时,挂一个类或 id 的地方。

html
<div class="grid">
  <article class="card">...</article>
  <article class="card">...</article>
</div>

<div class="grid"> 仅用于在网格中布置卡片。这是一个表现性的工作,而不是有意义的工作,所以 <div> 是正确的选择,不是你应该感到难过的备选方案。错误是相反的:为有含义的东西使用 <div>。一个可点击的 <div> 应该是 <button>,一个充满导航链接的 <div> 应该是 <nav>,一个包装整个帖子的 <div> 应该是 <article>。当没有含义,只有布局或样式需求时,<div><span> 是正确的答案。

<div><span> 是在语义上为空的元素:分别是泛型块和泛型内联容器,在无障碍树中没有角色,对任何工具都没有含义。它们是一个工作的正确选择,即 表现性钩子:一个元素仅用于附加样式或为布局分组子级,但不携带屏幕阅读器或搜索引擎应该宣布的任何信息。

有一个完全没有含义的元素的价值在于它使有意义的元素保持诚实。网格包装器、flexbox 行、定位上下文:这些是没有语义内容的真实需求,强行给它们一个有意义的元素会是标记中的谎言。一个纯粹用于背景色的 <section> 声称要成为它不是的区域。所以规则双向运行。当命名元素适合时不要使用 <div>,当你只需要一个盒子时不要使用命名元素。

要留意的失败模式是交互式 <div>:一个在 JavaScript 中被赋予点击处理程序以充当按钮或链接的元素。它呈现且可以用鼠标点击,所以看起来完成了,但它没有角色,它不在制表符顺序中,它不响应 Enter 或 Space,屏幕阅读器也不宣布任何东西。用 tabindexrole 和键处理程序重建所有这些是一个脆弱的 <button> 重新实现,而 <button> 已经存在。如果某样东西的行为像控件,就使用控件元素;将 <div><span> 保留给仅仅是像素的部分。

Junodiv 和 span,非语义化的备选方案<div> 是一个纯粹的盒子,<span> 是几个单词的纯粹包装器,都没有任何含义。当你只需要分组或样式化某样东西而没有命名元素适合时使用它们。如果事物确实有含义,如菜单或按钮,就改用该元素。
Junodiv 和 span,非语义化的备选方案 当唯一的工作是布局或样式,没有含义要陈述时,<div><span> 是正确的选择。不要为它们感到难过,为相反的情况感到难过:一个应该是 <button> 的可点击 <div>,或应该是 <article> 的包装 <div>。为空含义使用空元素,为真实含义使用命名元素。
Junodiv 和 span,非语义化的备选方案<div><span> 在无障碍树中没有角色,这正是为什么它们适合纯样式钩子而不适合任何有意义的东西。咬你的一个是有点击处理程序的交互式 <div>:没有焦点、没有键盘、没有公告,以及一堆 tabindexrole 补丁来伪造 <button> 无条件给你的东西。如果它的行为像控件,就让它成为控件。

选择正确的元素

当你不确定使用哪个元素时,提出一个简单的问题:这是什么东西?不是它看起来像什么,而是它是什么。答案通常为你命名元素。一排穿过顶部的链接是导航,所以它是 <nav>。底部的一块小字号文本是页脚,所以它是 <footer>。你想给着色的价格并不真的是任何东西,所以它是 <span>

从含义开始,只有当真实的答案是"它只是一个盒子"时才回到 <div><span>。尽早养成这个习惯意味着你的页面会自我描述,当你或其他任何人不得不再次阅读标记时,这就会有回报。

选择正确的元素是一个你每次都可以运行的短决定。问问内容是什么,让答案选择元素:

  • 它是自己的完整东西,单独成立也有意义吗?<article>
  • 它是一个有标题的相关内容的分组块吗?<section>
  • 它是导航吗?<nav>。区域的介绍?<header>。总结?<footer>
  • 它是支持主要内容的侧面内容吗?<aside>
  • 没有含义,只有布局或样式需求吗?<div>,或一行内的 <span>

最棘手的分支是 <section><article><div>。按顺序走。如果块可以单独成立,它就是 <article>。如果不是,但它是一个有自己标题的主题分组,它就是 <section>。如果它没有标题也没有含义,仅用于样式或布局,它就是 <div><section> 应该几乎总是有一个标题;如果你想不出一个,那是一个强烈的提示,你想要的是 <div>

html
<main>
  <section>
    <h2>最新文章</h2>
    <article>
      <h3>为什么我改用纯 HTML</h3>
      <p>...</p>
    </article>
    <article>
      <h3>反对重型框架的论证</h3>
      <p>...</p>
    </article>
  </section>
</main>

这里 <section> 是一个主题分组"最新文章",其中的每个文章都是 <article>,因为它可以单独成立。嵌套反映了含义。

选择好来自于先读含义,从不读布局,但一些规则使常见的决定变得机械。

每个页面恰好有一个 <main>。它标记主要内容,即这个页面特有的部分,而不是在整个网站中重复的页眉、导航栏和页脚。无障碍树中多于一个 <main> 给屏幕阅读器用户两个"main"目标,没办法知道哪一个是真实的,所以保持它的单数性。所有网站范围的内容都在它外面。

另一个反复重要的规则是 标记重复的标志。当一个页面有两个相同标志类型的元素,一个主 <nav> 和一个页面内的目录,两个都宣布为"navigation",用户无法区分它们。你可以用 aria-label 消除歧义,这是一个为区域提供简短可访问名称的属性,而不在屏幕上显示任何内容:

html
<nav aria-label="Primary">
  <a href="/">首页</a>
  <a href="/blog">博客</a>
</nav>

<nav aria-label="On this page">
  <a href="#intro">介绍</a>
  <a href="#setup">设置</a>
</nav>

现在两个区域宣布为"Primary navigation"和"On this page navigation",标志列表是可用的。对任何重复的标志应用相同的标记,包括 <aside><section>。对于 <section>,记住它仅在它有可访问的名称后才成为标志,无论来自关联的标题还是 aria-label

所有这一切背后的元规则是:标记是对文档的机器可读描述,由屏幕阅读器、搜索爬虫、阅读模式工具和你未来的自己读取。按含义选择元素是使那个描述成为真实的。布局是 CSS 的工作,任何时候你发现自己为了看起来如何而选择一个元素,你已经用错误的工具跨越到了 CSS 的领地。

Juno选择正确的元素 当你卡住了,问这个东西是什么,而不是它看起来像什么。导航是 <nav>,小字号文本是 <footer>,你想样式化的纯粹盒子是 <div>。每次从含义开始,你的页面最后会自我描述。
Juno选择正确的元素 运行小检查清单:单独成立是 <article>,有标题的主题分组是 <section>,没有含义只有样式是 <div>。解决大多数 section-versus-div 论证的决胜者:如果你想不出它的标题名称,你想要的是 <div>。让含义选择标签,嵌套自己会排序。
Juno选择正确的元素 两条规则承载大部分权重:每页恰好一个 <main>,用 aria-label 标记重复的标志,这样两个 nav 就不会都说"navigation"。记住一个 <section> 仅在它有可访问的名称后才是标志。按含义选择元素,把样式留给 CSS,因为一旦你为其如何呈现而选择一个标签,你就在用错误的工具做 CSS 的工作。