首页
/ freeCodeCamp 应用无障碍课程:用 nav 地标让屏幕阅读器导航更轻松

freeCodeCamp 应用无障碍课程:用 nav 地标让屏幕阅读器导航更轻松

2026-09-06 12:24:38作者:尤辰城Agatha

本篇技术指南围绕 freeCodeCamp 课程中的挑战「Make Screen Reader Navigation Easier with the nav Landmark」(用 nav 地标让屏幕阅读器导航更容易)展开,讲清楚 HTML5 nav 语义元素的内置地标(landmark)特性、为何用 div 包裹导航链接会损害可访问性,以及如何用一段可验证的测试断言来确认你的语义化改造是否达标。读完本文,你将掌握:把无语义容器替换为语义元素的完整实操流程、该挑战的完整初始代码与参考解答,以及 freeCodeCamp 课程仓库中这道题在 Responsive Web Design 课程里的定位与前后衔接关系。

挑战在课程结构中的位置

这道挑战位于课程仓库 curriculum 目录下,文件为 nav 地标挑战,属于 Applied Accessibility(应用无障碍)模块。该模块的组织信息由 applied-accessibility.json 描述,其中 helpCategory 标记为 HTML-CSSchallengeOrder 数组给出了全部 22 道挑战的完成顺序。按此顺序,本挑战排在第 7 位:

从源码结构看,这三道挑战共享同一个「Camper Cat 忍者训练页」的场景骨架,形成 header → nav → footer 的地标递进练习。再往上一层,responsive-web-design.json 显示 Applied Accessibility 是 Responsive Web Design 课程的第 4 个 block(位于 basic-html-and-html5、basic-css、applied-visual-design 之后),即学习者先掌握基础 HTML/CSS 与视觉设计后,才进入本模块学习语义化与无障碍。

核心概念:nav 元素与内置地标特性

挑战文档的核心说明(原文 description 部分)只有一句话,但信息密度很高:

nav 元素是另一个带有内置地标特性的 HTML5 元素,用于方便屏幕阅读器导航。这个标签应包裹页面中的主要导航链接。

这里的关键点有两层:

  1. 地标(landmark)是浏览器自动赋予的nav 不需要额外的 ARIA 属性——role="navigation" 是隐式默认角色。屏幕阅读器和辅助技术(AT)通常提供「跳转到导航区域」之类的快捷操作,用户无需逐个朗读整个页面就能在多个区域之间跳跃。这与前面几道挑战中 mainheader 元素共享同一种机制:语义元素本身即地标。
  2. nav 的语义边界是「主要导航」。它应当包裹页面的主导航链接(如站点菜单、页内锚点跳转),而不是包裹一切看起来像链接的列表。

文档同时给出了一条重要的边界规则:如果页面底部存在重复的站点链接,不必再用 nav 标记它们,用 footer(下一道挑战的内容)即可。这条规则避免了把「页脚辅助链接」也声明为导航地标,从而防止地标导航树中出现语义重复的导航区域。

练习场景:把 div 换成 nav

挑战的 instructions 部分设定的场景是:Camper Cat 在他的训练页面顶部加入了导航链接,但把它们包在了一个 div 里。任务是把这个 div 改为 nav 标签,以提升页面可访问性。

挑战文件中的 seed(初始代码)如下,注意 header 内部第二个子元素就是那个「问题 div」:

<body>
  <header>
    <h1>Training with Camper Cat</h1>

    <div>
      <ul>
        <li><a href="#stealth">Stealth &amp; Agility</a></li>
        <li><a href="#combat">Combat</a></li>
        <li><a href="#weapons">Weapons</a></li>
      </ul>
    </div>

  </header>
  <main>
    <section id="stealth">
      <h2>Stealth &amp; Agility Training</h2>
      <article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
      <article><h3>No training is NP-complete without parkour</h3></article>
    </section>
    <section id="combat">
      <h2>Combat Training</h2>
      <article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
      <article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
    </section>
    <section id="weapons">
      <h2>Weapons Training</h2>
      <article><h3>Swords: the best tool to literally divide and conquer</h3></article>
      <article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
    </section>
  </main>
</body>

这段初始代码已经完成了上一道挑战的改造:页面顶部是 header,正文主体是 main,每个训练分类用 section + h2 组织,具体课程条目用 article + h3。唯一残留的无语义容器,就是包裹导航 ul 的那个 div

参考解答

挑战文件 solutions 部分给出的完整答案,只改动了导航容器这一处——<div> 变为 <nav>,闭合标签同步替换,其余内容原样保留:

<body>
  <header>
    <h1>Training with Camper Cat</h1>

    <nav>
      <ul>
        <li><a href="#stealth">Stealth &amp; Agility</a></li>
        <li><a href="#combat">Combat</a></li>
        <li><a href="#weapons">Weapons</a></li>
      </ul>
    </nav>

  </header>
  <main>
    <section id="stealth">
      <h2>Stealth &amp; Agility Training</h2>
      <article><h3>Climb foliage quickly using a minimum spanning tree approach</h3></article>
      <article><h3>No training is NP-complete without parkour</h3></article>
    </section>
    <section id="combat">
      <h2>Combat Training</h2>
      <article><h3>Dispatch multiple enemies with multithreaded tactics</h3></article>
      <article><h3>Goodbye world: 5 proven ways to knock out an opponent</h3></article>
    </section>
    <section id="weapons">
      <h2>Weapons Training</h2>
      <article><h3>Swords: the best tool to literally divide and conquer</h3></article>
      <article><h3>Breadth-first or depth-first in multi-weapon training?</h3></article>
    </section>
  </main>
</body>

值得注意的结构细节:nav 包在 header 内部,而不是独立于页头。这符合文档「包裹页面主要导航链接」的定位——导航属于页头区块的组成部分,同时 nav 作为独立地标仍可被辅助技术单独定位。

测试断言:如何验证改造达标

挑战 hints 部分实际上就是判定你代码是否通过的 4 条测试断言(基于 Jest/Chai 风格断言,其中 code 是你的原始代码文本,document 是解析后的 DOM)。逐条拆解:

断言 1:页面中应恰好有一个 nav 标签。

assert.lengthOf(document.querySelectorAll('nav'), 1);

querySelectorAll('nav') 在文档中查找所有 nav 元素,长度必须为 1。这既排除了「忘记改」,也排除了「多加了一个 nav」——与文档中「重复的站点链接不必再包 nav」的规则相呼应:多个导航地标会稀释地标导航的意义。

断言 2:nav 必须包裹 ul 及其列表项。

const nav = document.querySelector('nav');
const children = nav?.querySelectorAll(`:scope ${'ul'}`);
assert.lengthOf(children, 1);

:scope 前缀确保 querySelectorAll('ul') 只在 nav 元素内部查找,验证 ul(及三个 li 导航项)确实处于 nav 内部,而不是被挪到了 nav 外面。这保证了地标语义覆盖的是完整导航集合。

断言 3:代码中不能再出现任何 div 标签。

assert.lengthOf(document.querySelectorAll('div'), 0);

这条断言把「替换」锁死为「替换」而非「新增」:不允许在保留 div 的基础上另加 nav。页面此时应当完全由语义元素构成(headernavmainsectionarticle、标题与列表)。

断言 4:nav 必须成对闭合。

assert.isTrue(
  code.match(/<\/nav>/g)?.length === code.match(/<nav>/g)?.length
);

这条断言直接对原始代码文本做正则匹配,比较 <nav> 开标签与 </nav> 闭标签的数量是否相等。在 HTML 中 nav 是双标签元素(不同于 imgbr 等空元素),漏写闭合标签会导致 DOM 树被浏览器隐式修正,地标的语义边界随之不可预期。

规则要点与前后挑战的衔接

把文档内容归纳为三条可复用的实践规则:

  1. 主要导航用 nav 包裹:页面顶部的站点菜单、页内锚点导航等「主导航链接」集合应放进 nav,获得内置地标特性;
  2. nav 不是链接容器:页脚等位置重复出现的站点辅助链接,交给 footer 地标即可,不要声明第二个 nav
  3. 地标要成对闭合、数量克制:本挑战的 4 条断言(恰好一个 nav、包裹完整 ul、零 div、开闭标签配对)本质上就是这三条规则的可执行版本。

完成本挑战后,footer 地标挑战 的 seed 代码会直接复用本挑战的解答作为起点:页头中已是 headerh1nav 结构,待改造的是页面底部那个包裹 &copy; 2018 Camper Catdiv。可以看到课程设计上前一道挑战的 solution 就是后一道挑战的 seed,形成连续演进的页面语义化改造链。

延伸阅读(仓库内路径)

课程仓库中的挑战文件采用统一的 Markdown 格式组织:frontmatter 声明 idtitlechallengeType(本挑战为 0,即经典 HTML/CSS 代码挑战)与 dashedName,正文按 --description----instructions----hints----seed----solutions-- 等标记分段,hints 中的 JS 片段即为平台侧的验收测试代码。理解这一格式后,你可以按同样方式在 curriculum/challenges/english/blocks/ 下查阅任意一道挑战的知识点、验收逻辑与参考解答。

登录后查看全文
热门项目推荐
相关项目推荐