freeCodeCamp 应用无障碍课程:用 nav 地标让屏幕阅读器导航更轻松
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
本篇技术指南围绕 freeCodeCamp 课程中的挑战「Make Screen Reader Navigation Easier with the nav Landmark」(用 nav 地标让屏幕阅读器导航更容易)展开,讲清楚 HTML5nav语义元素的内置地标(landmark)特性、为何用div包裹导航链接会损害可访问性,以及如何用一段可验证的测试断言来确认你的语义化改造是否达标。读完本文,你将掌握:把无语义容器替换为语义元素的完整实操流程、该挑战的完整初始代码与参考解答,以及 freeCodeCamp 课程仓库中这道题在 Responsive Web Design 课程里的定位与前后衔接关系。
挑战在课程结构中的位置
这道挑战位于课程仓库curriculum目录下,文件为 nav 地标挑战,属于 Applied Accessibility(应用无障碍)模块。该模块的组织信息由 applied-accessibility.json 描述,其中helpCategory标记为HTML-CSS,challengeOrder数组给出了全部 22 道挑战的完成顺序。按此顺序,本挑战排在第 7 位:
- 前置挑战:Make Screen Reader Navigation Easier with the header Landmark——先把页面顶部的
div改为header地标; - 当前挑战:把
header内包裹导航链接的div改为nav地标; - 后置挑战:Make Screen Reader Navigation Easier with the footer Landmark——把页脚版权信息的
div改为footer地标。
从源码结构看,这三道挑战共享同一个「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 元素,用于方便屏幕阅读器导航。这个标签应包裹页面中的主要导航链接。
这里的关键点有两层:
- 地标(landmark)是浏览器自动赋予的。
nav不需要额外的 ARIA 属性——role="navigation"是隐式默认角色。屏幕阅读器和辅助技术(AT)通常提供「跳转到导航区域」之类的快捷操作,用户无需逐个朗读整个页面就能在多个区域之间跳跃。这与前面几道挑战中main、header元素共享同一种机制:语义元素本身即地标。 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 & 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 & 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 & 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 & 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。页面此时应当完全由语义元素构成(header、nav、main、section、article、标题与列表)。
断言 4:nav必须成对闭合。
assert.isTrue( code.match(/<\/nav>/g)?.length === code.match(/<nav>/g)?.length );这条断言直接对原始代码文本做正则匹配,比较<nav>开标签与</nav>闭标签的数量是否相等。在 HTML 中nav是双标签元素(不同于img、br等空元素),漏写闭合标签会导致 DOM 树被浏览器隐式修正,地标的语义边界随之不可预期。
规则要点与前后挑战的衔接
把文档内容归纳为三条可复用的实践规则:
- 主要导航用
nav包裹:页面顶部的站点菜单、页内锚点导航等「主导航链接」集合应放进nav,获得内置地标特性; nav不是链接容器:页脚等位置重复出现的站点辅助链接,交给footer地标即可,不要声明第二个nav;- 地标要成对闭合、数量克制:本挑战的 4 条断言(恰好一个
nav、包裹完整ul、零div、开闭标签配对)本质上就是这三条规则的可执行版本。
完成本挑战后,footer 地标挑战 的 seed 代码会直接复用本挑战的解答作为起点:页头中已是header包h1加nav结构,待改造的是页面底部那个包裹© 2018 Camper Cat的div。可以看到课程设计上前一道挑战的 solution 就是后一道挑战的 seed,形成连续演进的页面语义化改造链。
延伸阅读(仓库内路径)
- 挑战本体:587d7788367417b2b2512aa2.md
- 模块结构与挑战顺序:applied-accessibility.json
- 所属课程(superblock)配置:responsive-web-design.json
- 前一道(header 地标):587d7787367417b2b2512aa1.md
- 后一道(footer 地标):587d7788367417b2b2512aa3.md
课程仓库中的挑战文件采用统一的 Markdown 格式组织:frontmatter 声明id、title、challengeType(本挑战为0,即经典 HTML/CSS 代码挑战)与dashedName,正文按--description--、--instructions--、--hints--、--seed--、--solutions--等标记分段,hints 中的 JS 片段即为平台侧的验收测试代码。理解这一格式后,你可以按同样方式在curriculum/challenges/english/blocks/下查阅任意一道挑战的知识点、验收逻辑与参考解答。
【免费下载链接】freeCodeCampfreeCodeCamp.org's open-source codebase and curriculum. Learn math, programming, and computer science for free.项目地址: https://gitcode.com/GitHub_Trending/fr/freeCodeCamp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考