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

列表 ​

docs.scrimba.com

网页上有相当多的内容其实都是列表:导航菜单、搜索结果、商品网格、菜谱里的食材、重置密码的步骤。每当内容是一组相关的条目时,HTML 都有对应的元素来表达这一点,用上它,浏览器以及借助辅助软件阅读页面的人,就都能知道这些条目是归在一起的。

无序列表 ​

当条目的顺序无关紧要时,你用**无序列表**。想想购物清单:牛奶、面包、鸡蛋。先写哪一个都无所谓,它们只是归在一起的一堆东西。

构建它要用到两个标签。<ul> 标签包住整个列表,每个条目放在自己的 <li> 标签里("list item",意思是"列表项"):

html
<ul>
  <li>牛奶</li>
  <li>面包</li>
  <li>鸡蛋</li>
</ul>

浏览器会把每个条目单独放一行,前面加一个圆点。这些圆点和换行都不是你自己加的,是浏览器看到你告诉它这是个列表之后自动加上的。

无序列表是一组顺序不带任何意义的条目。<ul> 元素是容器,每个直接子元素都是一个装着单个条目的 <li> 元素:

html
<ul>
  <li>满 £40 免运费</li>
  <li>30 天内可退货</li>
  <li>两年保修</li>
</ul>

有一条规则要记牢:<ul> 唯一允许的直接子元素就是 <li>。你不能把 <p> 或 <div> 直接塞进 <ul> 里。如果你想在某个条目里放一个段落或链接,那就放进对应的 <li> 里,<li> 里面可以装任何你想要的内容。

圆点和缩进都是浏览器默认的样式,真正的含义存在于元素本身。等你学到 CSS 时,你可以随意修改或去掉这些样式,列表本质上还是列表。

无序列表标记的是一组顺序不重要的条目。<ul> 是容器,每个条目是一个 <li>;<ul> 唯一允许的直接子元素就是 <li> 元素(以及像 <template> 这类脚本支持元素),所以在 <ul> 和它的条目之间塞一个裸的 <div> 是无效的写法,解析器不会把它当作列表的一部分。

html
<ul>
  <li>满 £40 免运费</li>
  <li>30 天内可退货</li>
  <li>两年保修</li>
</ul>

圆点标记和缩进都来自浏览器内置样式表的默认展示效果,不带任何语义分量;list-style: none 可以去掉它们,而不改变元素本身的含义。这个区分很重要,因为含义存在于元素本身,圆点只是一层"油漆"。辅助软件(这是屏幕阅读器一类工具的统称,它们会为看不到页面的人把内容读出来)读的是结构,不是那个标记:它会宣布一个列表开始了、里面有多少个条目。这个宣布到底能给你带来什么好处,我们会在关于嵌套的 Deep Dive 里详细说。

Juno无序列表 顺序不重要的列表用 <ul>,每个条目包在自己的 <li> 里。圆点和换行浏览器会自动帮你加上,你永远不用自己手动打这些符号。我花了一段时间才敢真正相信这一点,一开始总忍不住想自己加点号。
Juno无序列表<ul> 是容器,它唯一的直接子元素是 <li> 条目。任何更丰富的内容,比如链接、段落,都要放进 <li> 里面,绝不能松散地放在 <ul> 里。圆点只是默认样式,之后可以随时改,所以选元素要看它的含义,不要看它长什么样。
Juno无序列表<ul> 只接受 <li> 作为直接子元素,所以里面出现一个孤立的 <div> 就是无效标记。圆点来自浏览器样式表,不属于语义的一部分,所以 list-style: none 能去掉圆点却保留列表本身。正是这个区分,让屏幕阅读器在你用 CSS 隐藏标记之后,依然能数出条目的数量。

有序列表及其属性 ​

有时候顺序恰恰就是重点。比如菜谱里的步骤、比赛的名次、需要按顺序一步步执行的操作。这时候你用**有序列表**,它会自动给条目编号。

它的用法和无序列表几乎一样,只是把容器换成 <ol> 而不是 <ul>:

html
<ol>
  <li>把烤箱预热到 180°C</li>
  <li>把面粉和糖混合均匀</li>
  <li>倒入烤盘,烤 25 分钟</li>
</ol>

现在浏览器会在每个条目前面标上 1、2、3,而不是圆点。如果你之后在中间插入一个新步骤,编号会自动重新排列。你从来不用自己打数字,这正是用 <ol> 的意义所在。

有序列表用于那些顺序本身带有意义的条目。结构和 <ul> 一样,只是把容器换成 <ol>,浏览器会自动给每个 <li> 编号。因为数字是自动生成的,插入或删除一个条目时,其余条目会自动重新编号,不用你操心。

<ol> 有三个值得了解的属性。start 设定列表从哪个数字开始:

html
<ol start="4">
  <li>把鸡蛋打散</li>
  <li>拌入面粉</li>
</ol>

这个列表会从 4、5 开始。当一个列表被其他内容分成两段、但逻辑上仍是同一个列表的延续时,这个属性就很有用。

type 用来改变编号的样式:1 表示数字(默认值),A 或 a 表示字母,I 或 i 表示罗马数字。

html
<ol type="a">
  <li>收集证据</li>
  <li>撰写摘要</li>
</ol>

这个列表会按 a、b 编号,而不是 1、2。而 reversed 则是倒着数,适合倒计时或从后往前读的"前五名"排行榜:

html
<ol reversed>
  <li>铜牌</li>
  <li>银牌</li>
  <li>金牌</li>
</ol>

有序列表标记的是顺序具有意义的条目;<ol> 会根据生成的数值给每个 <li> 编号,所以序号从来不是你手动输入的内容,即便条目移动了也一直保持正确。

三个属性可以控制编号。start 用整数设定第一个序号,不受显示样式影响,所以在罗马数字列表上设置 start="4" 会从 iv 开始。type 设定标记样式:1、A、a、I 或 i。reversed(一个布尔属性,意思是只要它出现就代表其值为真,不需要赋任何值)会从条目总数倒数到 1。

html
<ol type="I" start="4" reversed>
  <li>基础篇</li>
  <li>推广阶段</li>
  <li>回顾总结</li>
</ol>

这里有一个样式会掩盖的区别。type 只改变标记的绘制方式;而 start 使用、辅助软件读出来的那个数值,始终是底层的那个整数。所以一个用字母显示的列表,底层依然是位置 1、2、3。当序号本身就是内容的一部分时——比如法律条款按序号引用——比起在文字里硬编码数字,更好的做法是让 <ol> 自己管理编号,因为如果在 <li> 里硬写一个"3.",它不会跟着重新编号,而屏幕阅读器还会把它读两遍:一遍是自动生成的标记,一遍是文字本身。还有一个值得了解的控制方式:单个 <li> 上的 value 属性可以覆盖那一项的编号,之后的条目会从这个新值继续往下数。

Juno有序列表及其属性 当顺序有意义时,比如菜谱里的步骤,就用 <ol>,浏览器会自动给条目编号。增加或删除一个步骤,编号会自己调整好,你永远不用手动打 1. 或 2.。里面还是同样的 <li> 条目,只是外面的容器换了。
Juno有序列表及其属性<ol> 给你三个好用的属性:start 让编号从非 1 的数字开始,type 让编号用字母或罗马数字代替数字,reversed 让编号倒着数。这些属性依然都是让浏览器自动生成数字,这也正是用有序列表的意义所在。
Juno有序列表及其属性type 只是重新设置标记的样式;它背后的数值——也就是 start 使用、辅助技术读出来的那个值——始终是整数。所以千万别在 <li> 里手动写"3.",不然它不会跟着重新编号,屏幕阅读器还会把数字读两遍。让 <ol> 自己管理计数,要微调某一项时,就在那个 <li> 上用 value。

描述列表 ​

还有第三种列表,用在每个条目其实是一对信息的时候:一个词和它的含义,一个标签和它的值。词汇表是最典型的例子,每个术语后面跟着它的意思。

这就是**描述列表**,它用到三个标签。<dl> 包住整个列表,<dt> 装每个术语,<dd> 装和它配对的描述:

html
<dl>
  <dt>HTML</dt>
  <dd>描述页面结构的语言。</dd>
  <dt>CSS</dt>
  <dd>描述页面外观样式的语言。</dd>
</dl>

浏览器会把每个描述缩进显示在对应术语下面,让配对关系一目了然。可以把"dt"理解成你要定义的词,"dd"理解成它的定义。

当每个条目是一组配对而不是独立存在的东西时,描述列表比 <ul> 或 <ol> 更合适。它由三个元素组成:<dl> 是容器,<dt> 是术语,<dd> 是与之绑定的描述。

html
<dl>
  <dt>作者</dt>
  <dd>李娜</dd>
  <dt>发布日期</dt>
  <dd>2024 年 3 月 14 日</dd>
  <dt>阅读时长</dt>
  <dd>6 分钟</dd>
</dl>

这个例子展示了描述列表在词汇表之外的另一大用途:键值型元数据,也就是一组标签和它们对应的值。这种分组方式很灵活。当一个术语有多个描述时,一个 <dt> 后面可以跟多个 <dd> 元素;当多个术语意思相同时,多个 <dt> 元素也可以共用一个 <dd>。只要内容天然是"这个,以及它的细节"的关系,就用 <dl>;如果条目各自独立,就用 <ul> 或 <ol>。

描述列表把名称和值关联起来。<dl> 是容器;<dt> 是术语(名称);<dd> 是描述(值)。浏览器和辅助软件理解的结构是一系列分组,每个分组由一个或多个 <dt> 元素后跟一个或多个 <dd> 元素组成。

html
<dl>
  <dt>存储</dt>
  <dt>容量</dt>
  <dd>256 GB SSD</dd>

  <dt>颜色</dt>
  <dd>石墨黑</dd>
  <dd>银色</dd>
</dl>

这种灵活性正是关键所在:多个术语可以共用一个描述,一个术语也可以带有多个描述。有两种实际场景特别适合用它。第一种是术语与定义类的内容,比如词汇表。第二种是键值型元数据,比如规格表或一组文章详情,每个标签都对应着自己的值;<dl> 能表达出"这些是名称/值配对"的关系,这一点是一堆 <div> 永远做不到的。这里有个命名上的小细节值得一提:<dt> 和 <dd> 在历史上分别代表"definition term"(定义术语)和"definition description"(定义描述),但现在的规范特意把它们的用途扩展到了任何名称/值的关联,而不只限于词典式的定义。如果你的内容只是一堆平等并列、没有配对关系的条目,那 <dl> 就用错了,<ul> 才是老实的选择。

Juno描述列表 描述列表用于配对:一个术语和它的含义。整个列表外面包一个 <dl>,每个术语用 <dt>,紧跟着的描述用 <dd>。记住词汇表这个画面就好:词然后是定义,词然后是定义。
Juno描述列表 当每个条目是一组配对——一个术语和它的细节,或一个标签和它的值——而不是一堆并列的东西时,就用 <dl>。这种分组可以灵活调整:一个 <dt> 可以带多个 <dd>,多个 <dt> 也可以共用一个 <dd>。像作者和日期这类键值型元数据,正是它在词汇表之外派上用场的地方。
Juno描述列表<dl> 表达的是名称/值配对,规范把 <dt> 和 <dd> 的用途从词典式定义扩展到了任何标签与值的配对,所以元数据也完全适用。多个术语可以共用一个值,一个术语也可以带多个值,这是一堆 <div> 永远表达不出来的。如果条目都是平等并列、没有配对关系的,那应该用 <ul>,不是 <dl>。

嵌套列表 ​

列表可以放进另一个列表里面。一份菜单可能有一个"饮品"选项,下面又分冷饮和热饮,每一类下面还有各自的选择。把一个列表放进另一个列表里,叫作**嵌套**。

要做对的关键是:内层列表要放在 <li> 里面,不能悬空地夹在条目中间。你要把整个 <ul>(或 <ol>)放进它所属的那个列表项里:

html
<ul>
  <li>饮品
    <ul>
      <li>咖啡</li>
      <li>茶</li>
    </ul>
  </li>
  <li>小吃</li>
</ul>

"咖啡"和"茶"都属于"饮品",所以它们的小列表就放在"饮品"这个条目里面。浏览器会把内层列表多缩进一点,这样你就能看出它是属于上面那个条目的。

要表示某些条目从属于另一个条目,你就把一个列表嵌套进另一个列表。保持标记有效的规则是:嵌套列表是某个 <li> 的子元素,绝不能是外层 <ul> 或 <ol> 的直接子元素。内层列表放在它所展开的那个条目里面。

html
<ul>
  <li>水果
    <ul>
      <li>苹果</li>
      <li>香蕉</li>
    </ul>
  </li>
  <li>蔬菜
    <ul>
      <li>胡萝卜</li>
      <li>菠菜</li>
    </ul>
  </li>
</ul>

嵌套时你可以混用类型,比如在 <ul> 里嵌套 <ol>,或反过来都行,嵌套的深度可以跟内容本身的层级一样深。实际代码中最常遇到嵌套列表的地方,是带子菜单的导航菜单:一个顶层的分类列表,每个分类的 <li> 里再放着自己的页面列表。

嵌套是表达层级关系的方式:子列表要放在它所属的 <li> 里面,绝不能作为父级 <ul> 或 <ol> 的直接子元素。如果搞错了(比如一个 <ul> 直接放在另一个 <ul> 里面),标记就是无效的,解析器的纠错结果未必会符合你脑子里想的样子。

嵌套也是结构价值真正体现的地方——对那些看不到缩进的人来说尤其如此。屏幕阅读器在进入每一层时,都会宣布这是一个列表、它有多少条目、以及嵌套的深度:先说"列表,三个条目",进入子列表时再说"列表,两个条目",离开时也会告诉用户。这种持续的口头说明,就是眼睛读缩进能免费获得的信息的非视觉版本,而它只有在嵌套是真正的标记结构、而不是用间距硬挤出来的效果时才会存在。

这也带来了检验任何列表是否用得诚实的标准。导航菜单(带用户在页面之间跳转的一组链接,通常是大多数网站顶部的那一行)本质上就是一份目的地的列表,所以语义上正确的写法是用 <ul> 包一组链接,通常还外面套一层 <nav> 元素,标明这个区域是导航。当内容确实是一组相关条目时,就用真正的列表,不管之后 CSS 把它做成什么样子。但当内容不是这样时,就别硬套:为了借用列表的间距效果,把互不相关的几个区块塞进 <li> 里伪造出间隔,这会让辅助软件误以为"这三样东西是一组",而实际上它们根本不相关。标记可以隐藏,布局可以随便改,但选 <ul>、<ol> 还是 <dl>,永远应该忠实反映内容本身到底是什么。

Juno嵌套列表 要把一个列表放进另一个列表里,把内层列表放进它所属的 <li> 里面,不要松散地夹在条目中间。浏览器会自动缩进它,让你能看出它是上面那个条目的一部分。这个"放进 <li> 里面"的规则,是这里唯一要记住的事。
Juno嵌套列表 嵌套列表永远是某个 <li> 的子元素,绝不能是外层 <ul> 或 <ol> 的直接子元素。你可以在 <ul> 里混用 <ol>,嵌套深度可以跟内容真实的层级一样深。带子菜单的导航菜单是你最常碰到这种情况的地方,值得现在就熟悉起来。
Juno嵌套列表 把子列表放进它所属的 <li> 里面,因为屏幕阅读器会读出条目数量和嵌套深度,这种口头描述的结构,正是替代缩进给看不见的人提供信息的方式。导航菜单本质上确实是一份链接列表,所以就把它标记成列表;不要为了借用间距,把不相关的区块硬塞进 <li> 里。让元素忠实反映内容本身是什么,之后想怎么设置样式都行。