读书小组的网页需要的不只是依次排好的文字。标题应识别这一页,导航应带领读者到合适的位置,时间表则应保留每个活动与开始时间之间的关系。HTML 把这些关系写进文档,让浏览器及其他工具能够理解内容的组织方式。
第一步是准确描述内容。之后添加外观和交互功能时,便能沿用同一个清晰结构。本单元从一份小型文档出发,区分源文本、元素、属性,以及浏览器构建的文档结构。
标记描述内容的角色
HTML 是 **Hypertext Markup Language(超文本标记语言)**的缩写。浏览器解释标记,从而构建文档。当页面需要变量、条件分支及事件处理逻辑时,JavaScript 便提供这些行为,而 HTML 结构仍是这些行为所作用的基础。
定义
元素、标签与属性
HTML 元素代表文档的一部分,例如段落、标题或链接。标签在源代码中标示元素的范围。属性提供元素的附加信息,写在开始标签内。
<a href="week2.html" class="reading-link">第二次阅读讨论</a>
这里的 a 是元素名称,<a ...> 是开始标签,</a> 是结束标签。两者之间的文字是链接标签。href 属性指定目标地址;class 则分配一个可复用的名称,供 CSS 或 JavaScript 选取。类名本身不会产生视觉效果。
大部分元素能包含文字或其他合适的元素。嵌套关系表达内容的包含关系:
<p>讨论前请阅读<strong>前两节</strong>。</p>
strong 元素位于段落内,表示内容的重要性。浏览器可能把它显示成粗体,但它表达的意思比字体选择更具体。em 表示语气上的强调。b、i 和 u 也有各自的文本语义;只因默认外观合适而选择元素,便容易混淆含义与呈现方式。纯粹的外观要求应交由 CSS 处理。
有些元素是空元素,例如 img、br、meta 和 link。它们没有结束标签,也不能包含子内容。有语义需要的换行可写成 <br>。段落仍应使用 p;连续添加换行并不会建立段落结构。
在 HTML 语法中,一般 HTML 标签名称不区分大小写。使用小写名称、为属性值加引号,以及一致地缩进,会让源代码更容易检查。缩进帮助人阅读嵌套关系;真正决定结构的是标签。
文档把元数据与主要内容分开
例题
一份完整的小型读书小组文档
把以下文字保存为 index.html,再用浏览器打开。这是一份完整文档,没有脚本或外部样式表。
<!doctype html>
<html lang="zh-Hans">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>读书小组:活动安排</title>
</head>
<body>
<header>
<h1>读书小组</h1>
<p>每周讨论一篇短篇技术文章。</p>
</header>
<nav aria-label="本页导航">
<a href="#schedule">活动安排</a>
<a href="#preparation">事前准备</a>
</nav>
<main>
<section id="schedule">
<h2>活动安排</h2>
<p>下一次讨论于 16:00 开始。</p>
</section>
<section id="preparation">
<h2>事前准备</h2>
<p>带来一个关于阅读内容的问题。</p>
</section>
</main>
<footer>
<p>由读书小组组织。</p>
</footer>
</body>
</html>
<!doctype html> 声明选择标准 HTML 文档模式。html 是文档的根元素,其 lang 值识别主要语言。语言信息有助于工具选择合适的发音及文本处理方式。
head 包含文档的元数据。charset 声明字符编码;UTF-8 能表示本课程笔记使用的中英文。title 在浏览器标签页及其他场景中识别文档。视口声明要求移动设备浏览器以设备宽度作为布局视口宽度,并以比例 1 开始显示。
body 包含页面的文档内容,其中的 h1 是内容的一部分。因此 title 与 h1 的角色互相关联,但并不相同:更改标签页标题,不会自动更改页面内的标题。
元数据也可以指向样式表、脚本或图标。带有 rel="stylesheet" 的 link 元素指向样式表;带有 href 的 a 元素则建立可供导航的超链接。两者都使用 URL,并不意味着两种元素可以互换。
注释使用 <!-- ... -->。注释可以解释源代码,而不成为页面上呈现的文字。读者查看源代码时仍能看到注释。
按含义选择结构
首个例子使用语义元素,标明各区域的角色:
| 元素 | 在读书小组页面中的角色 |
|---|---|
header | 页面的介绍内容 |
nav | 一组主要导航链接 |
main | 文档的主要主题内容 |
section | 一组具有共同主题的内容,通常由标题引入 |
aside | 与周围主要内容相关、但属于旁支的信息 |
footer | 关于页面或所属章节的信息 |
用一个可见的 main 表达页面主要内容。页面可以有自己的页首,某一节也可以有自己的 header;周围结构决定它介绍哪一部分。若没有更具体的语义角色,则可用 div 作一般分组。
h1 至 h6 表达不同组织层级。例子中的 h1 命名整页,h2 命名各个主要章节。“事前准备”下面的小节可使用 h3。只因 h4 看起来较小而选择它,会让层级关系变得含糊;应先选对标题层级,再用样式调整大小。
id 识别文档中的一个元素。每个 ID 应保持唯一,让片段链接有明确目标。class 可以由多个元素共用,用来表示共同的样式或脚本分类。ID 和类都不会取代承载它们的元素本身的语义。
思考检查
文档的哪一部分会改变?
假设浏览器标签页显示“读书小组:活动安排”,页面内的标题则是“读书小组”。现在要把可见标题改成“秋季读书小组”,应修改哪个元素?现有 ID 是否也需要更改?
解答 · 标题与标识名称
修改 h1 内的文字。浏览器标签页标题由 title 控制。更改标题文字不要求更改元素的 id;ID 识别目标,与读者看到的文字互相独立。例子中的 h1 没有 ID,这次修改也不需要新增一个。
超链接连接文档与位置
带有 href 的锚点元素代表超链接。链接文字应描述目标,因为读者可能在没有周围段落的情况下接触该文字。
<a href="week2.html">第二次阅读讨论</a>
<a href="#schedule">跳至活动安排</a>
URL 可以是绝对地址,例如 https://example.test/club/week2.html,也可以是相对于文档基准 URL 的地址。在没有 base 元素改变基准的情况下,相对 URL 会以文档 URL 为基础解析。路径是否以 / 开头,会影响解析结果。
例题
从当前文档解析链接
假设文档 URL 是 https://example.test/club/reading/index.html。
href | 解析后的目标 |
|---|---|
week2.html | https://example.test/club/reading/week2.html |
../index.html | https://example.test/club/index.html |
/help.html | https://example.test/help.html |
#schedule | 当前文档中的 schedule 片段 |
第一个路径从当前目录出发。第二个先返回上一层目录。第三个以斜线开头,因此从源站的根目录出发。第四个保留同一份文档,并指定其中一个位置;前面的 id="schedule" 就提供了这个目标。
target 属性选择导航发生的位置。_self 使用当前浏览上下文,也是一般默认值。_blank 请求建立新的浏览上下文,浏览器通常会以新标签页呈现。它不会改变目标 URL。
片段目标必须与预期元素的 ID 相符。在这个例子中,若链接写成 href="#Schedule",元素却是 id="schedule",便不是所需的精确匹配。
列表与表格表达不同关系
项目只形成一组集合、没有有意义的先后次序时,使用无序列表。当次序是内容的一部分,例如准备步骤时,使用有序列表。
<h2>事前准备</h2>
<ol>
<li>阅读文章。</li>
<li>写下一个问题。</li>
<li>选一段文字作讨论。</li>
</ol>
每个 li 是一个列表项。有序列表可以使用 start="5" 从 5 开始编号,或用 type="A" 使用字母标记。内容仍应具有顺序。装饰性的标记形状通常属于 CSS 的工作。
表格表达行与列之间的关系。活动时间表适合用表格,因为每一行把一个主题与开始时间配对。若只是要把互不相关的页面区域排成数列,就不应以表格代替布局。
例题
明确标示表头的时间表
<table>
<caption>读书小组活动</caption>
<thead>
<tr>
<th scope="col">主题</th>
<th scope="col">开始时间</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">HTML 结构</th>
<td>16:00</td>
</tr>
<tr>
<th scope="row">超链接</th>
<td>16:30</td>
</tr>
</tbody>
</table>
tr 建立一行,th 识别表头单元格,td 则容纳数据单元格。scope="col" 把顶部表头与相应列关联;scope="row" 表示主题是该行的表头。caption 描述整个表格。即使改变表格外观,这些关系仍然有用。
图像需要资源,也需要角色
img 元素用 src 指向图像资源。alt 提供图像的替代文本。编写时,应先问这幅图像在当前场景中传达什么信息。
<img src="images/reading-room.jpg"
width="640" height="360"
alt="一间阅读室,六个座位围着一张共用桌子">
若图像提供信息,便描述其中相关的信息。若图像纯粹用作装饰,没有需要传达的附加信息,可以使用 alt=""。width 与 height 描述图像尺寸,也帮助浏览器在加载资源前确定宽高比。试用这段代码时,应把图像放在文档旁边的 images 目录内。
栅格图像以像素网格表示内容。SVG 则以形状与坐标表示矢量图形。简单的示意图可以直接用内嵌 SVG 描述几何结构:
<svg viewBox="0 0 120 60" role="img" aria-labelledby="circle-title">
<title id="circle-title">读书小组的圆形徽章</title>
<circle cx="60" cy="30" r="18" fill="currentColor"></circle>
</svg>
圆心在 SVG 坐标系中的 (60, 30),半径是 18。viewBox 定义坐标矩形。这幅图形通过 aria-labelledby,使用 title 获取名称。
picture 元素提供多个图像资源选项。它包含若干 source,最后接一个 img;后者提供替代文本及默认资源:
<picture>
<source media="(min-width: 48rem)" srcset="images/room-wide.jpg">
<img src="images/room-close.jpg" width="640" height="360"
alt="阅读室内的共用桌子">
</picture>
当媒体条件成立时,浏览器可选择较宽的构图;否则仍可使用默认图像。picture 容器本身不会取代 img 元素。
文本字符与空白受解析规则影响
某些字符在 HTML 语法中有特定用途。字符引用让源代码把这些字符表达成文字,而不是标记语法:
| 源文本 | 所代表的字符 |
|---|---|
< | 小于号 |
> | 大于号 |
& | 与号 |
" | 双引号 |
| 不换行空格 |
使用包含分号的完整形式。例如源代码 <p>2 < 5 & 5 < 8</p> 会显示文字 2 < 5 & 5 < 8。
在一般空白处理方式下,普通段落内连续的空格、换行及制表符会在显示时合并。源代码中把段落缩进若干空格,不意味着呈现结果也会缩进同样距离。不换行空格可让 Week 2 这类组合保持在同一行,但不应拿来代替布局间距。
pre 在默认样式下保留空白,适合预格式化文本。它不会停用 HTML 解析。若要显示 HTML 源代码,应转义标记字符,并把结果放在 code 内:
<pre><code><h2>活动安排</h2>
<p>带来一个问题。</p></code></pre>
结果会显示两行源代码,而不是建立标题与段落。sub 与 sup 则表达有相应含义的下标与上标,例如 H<sub>2</sub>O 和 r<sup>2</sup>。
从两个层次检查文档
“查看网页源代码”显示提供给浏览器的 HTML 源内容。浏览器开发者工具则显示当前的文档结构,包括解析过程或 JavaScript 所作的改动。两者回答不同问题:输入的源代码是什么,以及现在存在什么结构?
当页面不如预期时,先检查元素角色与嵌套关系,再检查目标属性、资源路径和文本内容。浏览器可能修复格式不正确的 HTML,因此能看到画面,并不等于源代码已表达预期结构。
常见错误
预格式化的区块仍会解析 HTML
把 <h2>活动安排</h2> 直接放进 pre,会建立元素,而不是输出尖括号。保留空白与解释字符是两套不同规则。当预期内容是源代码时,应使用字符引用。
练习
思考检查
练习 1:组织一份小型文档
读书小组网页有介绍标题、通往两节的导航、准备指示,以及组织者信息。选择合适的区域元素及标题层级,并解释为什么不能只因某种标题看起来较小,就决定选用该层级。
解答 · 练习 1 解答
页面 header 包含 h1;nav 包含主要链接;一个 main 包含两个按主题组织的 section,每节使用 h2。组织者信息放在页面 footer。标题层级表达组织关系,外观由 CSS 决定。主要章节下面的小节应使用 h3,即使期望的字体大小与另一层级相近。
思考检查
练习 2:解析路径与片段
在 https://example.test/club/reading/index.html,解析 ../archive.html 与 /archive.html。再写出一个通往 ID 为 questions 的章节链接,并说明 href 与可能加入的 target 属性有什么区别。
解答 · 练习 2 解答
第一个目标是 https://example.test/club/archive.html;第二个是 https://example.test/archive.html。章节链接可写成 <a href="#questions">讨论问题</a>。href 识别目标;target,例如 _self 或 _blank,则选择导航使用的浏览上下文。
思考检查
练习 3:解释一个表格单元格
在前面的时间表中,为什么“16:30”使用 td,“超链接”却使用带有 scope="row" 的 th?顶部的“开始时间”表头又提供什么信息?
解答 · 练习 3 解答
“16:30”是某次活动的开始时间数据。“超链接”识别该行所代表的活动,因此是行表头。“开始时间”列表头则指出这些时间值的含义。两个表头合起来,把单元格解释为“超链接”活动的开始时间。
思考检查
练习 4:显示源代码文本
写出 HTML,在预格式化代码块中显示字面文本 <p>2 < 5</p>。然后解释为什么在普通段落添加空格,不能可靠地把时间表各列对齐。
解答 · 练习 4 解答
<pre><code><p>2 < 5</p></code></pre>
字符引用把尖括号变成文本的一部分,pre 则保留代码的空白。在普通段落中,重复空格会被合并,而空格的视觉宽度也受字体影响。时间表具有行与列的关系,应由表格直接表达其结构。
要把文档结构与服务器提供的资源连起来,可回看浏览器、服务器与网页资源。