具有 HTML + CSS 的完美目录 PlatoBlockchain 数据智能。垂直搜索。人工智能。

HTML + CSS 的完美目录

今年早些时候,我自己出版了一本名为 了解 JavaScript 承诺 (免费下载)。 尽管我并没有打算把它变成一本印刷书,但有足够多的人询问我决定自行出版的印刷版本。我认为这将是一个使用 HTML 和 CSS 的简单练习生成 PDF,然后将其发送到打印机。 我没有意识到我对印刷书籍的一个重要部分没有答案:目录。

目录的构成

就其核心而言,目录相当简单。 每行代表一本书或网页的一部分,并指示您可以在哪里找到该内容。 通常,这些行包含三个部分:

  1. 章节或章节的标题
  2. 将标题视觉连接到页码的前导符(即那些点、破折号或线条)
  3. 页码

在 Microsoft Word 或 Google Docs 等文字处理工具中很容易生成目录,但因为我的内容是在 Markdown 中然后转换为 HTML,这对我来说不是一个好的选择。 我想要一些自动化的东西,它可以与 HTML 一起使用,以适合打印的格式生成目录。 我还希望每一行都成为一个链接,以便可以在网页和 PDF 中使用它来浏览文档。 我还想要标题和页码之间的点前导符。

于是我开始研究。

我遇到了两篇关于使用 HTML 和 CSS 创建目录的优秀博客文章。 第一个是 “从您的 HTML 构建目录” 朱莉布兰克。 朱莉工作 分页JS,一种用于 Web 浏览器中缺少的分页媒体功能的 polyfill,可以正确格式化文档以进行打印。 我从 Julie 的示例开始,但发现它并不适合我。 接下来,我找到了 Christoph Grabo 的 “使用 CSS 的响应式 TOC 领导线” 这篇文章介绍了使用 CSS Grid 的概念(相对于 Julie 的基于浮点的方法)来使对齐更容易。 不过,再一次,他的方法不太适合我的目的。

不过,在阅读了这两篇文章后,我觉得我对布局问题有了足够的了解,可以自己动手了。 我使用了两篇博客文章中的文章,并在方法中添加了一些新的 HTML 和 CSS 概念,以得出我满意的结果。

选择正确的标记

在决定目录的正确标记时,我主要考虑的是正确的语义。 从根本上说,目录是关于与页码相关的标题(章节或小节),几乎就像一个键值对。 这让我有两个选择:

  • 一种选择是使用表格(<table>) 一栏作为标题,一栏作为页面。
  • 然后是经常未使用和被遗忘的定义列表(<dl>) 元素。 它还充当键值映射。 所以,再一次,标题和页码之间的关系是显而易见的。

这两个似乎都是不错的选择,直到我意识到它们实际上只适用于单级目录,也就是说,只有当我想要一个只有章节名称的目录时。 但是,如果我想在目录中显示小节,我没有任何好的选择。 表格元素不适用于分层数据,虽然定义列表在技术上可以嵌套,但语义似乎并不正确。 所以,我又回到了绘图板上。

我决定借鉴 Julie 的方法并使用列表; 但是,我选择了有序列表(<ol>) 而不是无序列表 (<ul>)。 我认为在这种情况下有序列表更合适。 目录表示章节和小标题的列表,按它们在内容中出现的顺序排列。 顺序很重要,不应该在标记中丢失。

不幸的是,使用有序列表意味着失去标题和页码之间的语义关系,所以我的下一步是在每个列表项中重新建立这种关系。 解决这个问题的最简单方法是在页码之前简单地插入单词“page”。 这样,数字与文本的关系就很清楚了,即使没有任何其他视觉区别。

这是一个简单的 HTML 框架,它构成了我的标记的基础:

<ol class="toc-list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title</span> <span class="page">Page 1</span> </a> <ol> <!-- subsection items --> </ol> </li>
</ol>

将样式应用于目录

一旦我建立了我计划使用的标记,下一步就是应用一些样式。

首先,我删除了自动生成的数字。 如果您愿意,您可以选择将自动生成的编号保留在您自己的项目中,但通常书籍的章节列表中包含未编号的前言和后记,这会导致自动生成的编号不正确。

出于我的目的,我将手动填写章节编号,然后调整布局,使顶级列表没有任何填充(因此与段落对齐),并且每个嵌入列表都缩进两个空格。 我选择使用 2ch 填充值,因为我仍然不太确定我会使用哪种字体。 这 ch 长度单位允许填充相对于字符的宽度——无论使用什么字体——而不是可能最终看起来不一致的绝对像素大小。

这是我最终得到的 CSS:

.toc-list, .toc-list ol { list-style-type: none;
} .toc-list { padding: 0;
} .toc-list ol { padding-inline-start: 2ch;
}

萨拉·索伊丹(Sara Soueidan) 向我指出 WebKit 浏览器在删除列表语义时 list-style-type is none,所以我需要添加 role="list" 进入 HTML 以保留它:

<ol class="toc-list" role="list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title</span> <span class="page">Page 1</span> </a> <ol role="list"> <!-- subsection items --> </ol> </li>
</ol>
CodePen 嵌入后备

设置标题和页码的样式

根据我的喜好设置列表样式,是时候继续设置单个列表项的样式了。 对于目录中的每一项,标题和页码必须在同一行,标题向左,页码向右对齐。

你可能会想,“没问题,这就是 flexbox 的用途!” 你没有错! Flexbox 确实可以实现正确的标题页对齐。 但是在添加领导者时会出现一些棘手的对齐问题,所以我选择采用 Christoph 使用网格的方法,这是一个额外的好处,因为它也有助于多行标题。 这是单个项目的 CSS:

.toc-list li > a { text-decoration: none; display: grid; grid-template-columns: auto max-content; align-items: end;
} .toc-list li > a > .page { text-align: right;
}

网格有两列,第一列是 auto-sized 填满容器的整个宽度,减去第二列,其大小为 max-content. 页码向右对齐,这在目录中是传统的。

我在这一点上所做的唯一其他更改是隐藏“页面”文本。 这对屏幕阅读器很有帮助,但在视觉上是不必要的,所以我使用了 传统 visually-hidden 从视图中隐藏它:

.visually-hidden { clip: rect(0 0 0 0); clip-path: inset(100%); height: 1px; overflow: hidden; position: absolute; width: 1px; white-space: nowrap;
}

当然,需要更新 HTML 才能使用该类:

<ol class="toc-list" role="list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title</span> <span class="page"><span class="visually-hidden">Page</span> 1</span> </a> <ol role="list"> <!-- subsection items --> </ol> </li>
</ol>

有了这个基础,我继续向标题和页面之间的领导者讲话。

CodePen 嵌入后备

创建点领导者

领导者在印刷媒体中如此普遍,您可能想知道,为什么 CSS 不支持它? 答案是: 是的。 好吧,那种。

实际上有一个 leader() 在中定义的功能 分页媒体规范的 CSS 生成内容. 但是,与许多分页媒体规范一样,此功能并未在任何浏览器中实现,因此将其排除在一个选项之外(至少在我写这篇文章的时候)。 它甚至没有列出 caniuse.com,大概是因为没有人实施它,也没有他们将实施的计划或信号。

幸运的是,朱莉和克里斯托夫都已经在各自的帖子中解决了这个问题。 为了插入点前导符,他们都使用了 ::after 伪元素及其 content 属性设置为一串很长的点,如下所示:

.toc-list li > a > .title { position: relative; overflow: hidden;
} .toc-list li > a .title::after { position: absolute; padding-left: .25ch; content: " . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . "; text-align: right;
}

::after 伪元素设置为绝对位置,以将其从页面流中取出并避免换行到其他行。 文本向右对齐,因为我们希望每行的最后一个点与行尾的数字齐平。 (稍后会详细介绍这方面的复杂性。) .title 元素被设置为具有相对位置,因此 ::after 伪元素不会打破它的盒子。 同时, overflow 是隐藏的,所以所有这些额外的点都是不可见的。 结果是带有点领导者的漂亮目录。

但是,还有其他需要考虑的事情。

Sara 还向我指出,所有这些点都算作屏幕阅读器的文本。 那么你听到了什么? “Introduction dot dot dot dot...”直到所有的点都被宣布。 对于屏幕阅读器用户来说,这是一种糟糕的体验。

解决方案是插入一个额外的元素 aria-hidden 设置 true 然后使用该元素插入点。 因此 HTML 变为:

<ol class="toc-list" role="list"> <li> <a href="#link_to_heading"> <span class="title">Chapter or subsection title<span class="leaders" aria-hidden="true"></span></span> <span class="page"><span class="visually-hidden">Page</span> 1</span> </a> <ol role="list"> <!-- subsection items --> </ol> </li>
</ol>

CSS变为:

.toc-list li > a > .title { position: relative; overflow: hidden;
} .toc-list li > a .leaders::after { position: absolute; padding-left: .25ch; content: " . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . " ". . . . . . . . . . . . . . . . . . . . . . . "; text-align: right;
}

现在屏幕阅读器将忽略这些点,让用户免于听多个点被宣布的挫败感。

CodePen 嵌入后备

画龙点睛

在这一点上,目录组件看起来不错,但它可以使用一些小的细节工作。 首先,大多数书籍在视觉上将章节标题与小节标题相抵消,因此我将顶级项目加粗并引入了一个边距以将小节与随后的章节分开:

.toc-list > li > a { font-weight: bold; margin-block-start: 1em;
}

接下来,我想清理页码的对齐方式。 当我使用固定宽度字体时,一切看起来都很好,但对于可变宽度字体,当它们调整到页码的宽度时,前导点最终可能会形成锯齿形图案。 例如,任何带有 1 的页码都会比其他页码窄,从而导致前导点与前一行或后一行上的点错位。

目录中的数字和点未对齐。
HTML + CSS 的完美目录

为了解决这个问题,我设置 font-variant-numerictabular-nums 所以所有数字都以相同的宽度处理。 通过还将最小宽度设置为 2ch,我确保所有一位或两位数字的数字都完全对齐。 (您可能希望将其设置为 3ch 如果您的项目超过 100 页。)这是页码的最终 CSS:

.toc-list li > a > .page { min-width: 2ch; font-variant-numeric: tabular-nums; text-align: right;
}
目录中对齐的前导点。
HTML + CSS 的完美目录

这样,目录就完成了!

CodePen 嵌入后备

结论

创建一个只有 HTML 和 CSS 的目录比我预期的更具挑战性,但我对结果非常满意。 这种方法不仅足够灵活以容纳章节和小节,而且它可以很好地处理小节,而无需更新 CSS。 整体方法适用于您希望链接到内容的各个位置的网页,以及您希望目录链接到不同页面的 PDF。 当然,如果您想在小册子或书籍中使用它,它的印刷品也会看起来很棒。

我要感谢 Julie Blanc 和 Christoph Grabo 关于创建目录的出色博客文章,因为在我刚开始时,这两篇文章都非常宝贵。 我还要感谢 Sara Soueidan 在我参与这个项目时提供的可访问性反馈。


HTML + CSS 的完美目录 最初发表于 CSS技巧。 你应该 获取时事通讯.

时间戳记:

更多来自 CSS技巧