使用 paged.js 高效构建可打印HTML的动态目录

使用 paged.js 高效构建可打印html的动态目录

本教程旨在指导开发者如何利用 paged.js 库为可打印的 HTML 文档动态生成一个结构清晰的目录。文章将详细介绍如何通过 J*aScript 遍历文档中的标题元素、生成锚点链接,并将其集成到 paged.js 的处理流程中,从而在打印输出时实现一个功能完善、自动更新的目录,无需直接通过 J*aScript 获取页面号。

在构建可打印的 HTML 文档时,尤其对于长篇内容,一个带有章节页码的目录是必不可少的。传统的做法可能涉及在 J*aScript 中尝试获取特定元素所在的页码,但由于页码是打印布局的产物,并非 DOM 元素的固有属性,这种方法往往难以实现。paged.js 提供了一种优雅的解决方案,允许我们在其渲染流程中动态构建目录,从而间接实现页码关联的效果。

核心原理

paged.js 的核心在于将 HTML 内容流式地分页,并应用 CSS 打印样式。为了生成动态目录,我们不需要直接在 J*aScript 中计算页码。相反,我们可以在 paged.js 开始分页之前,通过 J*aScript 遍历文档中的所有章节标题,为它们创建唯一的 ID,并在目录区域生成指向这些 ID 的锚点链接。当 paged.js 完成分页并生成打印输出时,这些锚点链接将自然地指向其对应的章节起始位置,而打印机或 PDF 阅读器则会显示这些章节所在的实际页码。

paged.js 提供了处理程序(Handlers)机制,允许我们在其处理生命周期的不同阶段注入自定义逻辑。对于目录生成,beforeParsed 钩子是理想的选择,因为它在 HTML 内容被解析但尚未进行分页布局之前执行。

准备您的 HTML 结构

首先,确保您的 HTML 文档包含清晰的章节标题和目录的占位符。

  1. 章节标题: 使用语义化的标题标签(如

    ,

    ,

    等)来标识您的章节。为确保锚点链接的正确性,这些标题需要有唯一的 id 属性。如果标题本身没有 id,我们将通过 J*aScript 动态添加。

  2. 目录占位符: 在您希望目录出现的位置创建一个空的容器元素,例如一个 div 或 ul,并为其指定一个唯一的 id。

示例 HTML 结构:

<!DOCTYPE html>
<html>
<head>
  <title>可打印文档</title>
  <!-- 引入 paged.js -->
  <script src="https://unpkg.com/pagedjs/dist/paged.polyfill.js"></script>
  <!-- 您的自定义脚本和样式将在此处 -->
</head>
<body>

  <!-- 目录占位符 -->
  <div id="my-toc-content">
    <h2>目录</h2>
    <ul id="list-toc-generated">
      <!-- 目录项将在此处动态生成 -->
    </ul>
  </div>

  <!-- 文档内容 -->
  <h1 id="pre-digital_era" class="title-element" data-title-level="h1">
    数字时代前夕
  </h1>
  <p>这是数字时代前夕的相关内容...</p>

  <h2 id="early_computers">早期计算机</h2>
  <p>早期计算机的发展历史...</p>

  <h1 id="digital_era" class="title-element" data-title-level="h1">
    数字时代
  </h1>
  <p>这是数字时代的相关内容...</p>
                    <div class="aritcle_card">
                        <a class="aritcle_card_img" href="/ai/2108">
                            <img src="https://img.php.cn/upload/ai_manual/001/246/273/68b6ca42843ea121.png" alt="AppStruct">
                        </a>
                        <div class="aritcle_card_info">
                            <a href="/ai/2108">AppStruct</a>
                            <p>无代码应用开发平台</p>
                            <div class="">
                                <img src="/static/images/card_xiazai.png" alt="AppStruct">
                                <span>132</span>
                            </div>
                        </div>
                        <a href="/ai/2108" class="aritcle_card_btn">
                            <span>查看详情</span>
                            <img src="/static/images/cardxiayige-3.png" alt="AppStruct">
                        </a>
                    </div>
                

  <h2 id="internet_revolution">互联网革命</h2>
  <p>互联网如何改变世界...</p>

</body>
</html>

动态生成目录的 J*aScript 逻辑

接下来,我们将编写一个 J*aScript 函数来遍历文档中的标题,并根据它们生成目录项。

/**
 * 动态生成目录
 * @param {object} config - 配置对象
 * @param {HTMLElement} config.content - 文档内容根元素
 * @param {string} config.tocElement - 目录容器的选择器
 * @param {string[]} config.titleElements - 标题元素的选择器数组,按层级顺序
 */
function createToc(config) {
  const content = config.content; // 文档内容根元素
  const tocElementSelector = config.tocElement; // 目录容器选择器
  const titleElementsSelectors = config.titleElements; // 标题元素选择器数组

  // 查找目录容器,并创建或获取 ul 元素
  let tocContainer = content.querySelector(tocElementSelector);
  if (!tocContainer) {
    console.warn(`目录容器 ${tocElementSelector} 未找到.`);
    return;
  }
  let tocUl = tocContainer.querySelector("#list-toc-generated");
  if (!tocUl) {
    tocUl = document.createElement("ul");
    tocUl.id = "list-toc-generated";
    tocContainer.appendChild(tocUl);
  } else {
    // 清空现有目录,防止重复生成
    tocUl.innerHTML = '';
  }

  let tocElementNbr = 0; // 用于生成唯一 ID 的计数器

  // 1. 遍历所有标题元素,添加必要属性
  for (let i = 0; i < titleElementsSelectors.length; i++) {
    let titleHierarchy = i + 1; // 标题层级 (1, 2, 3...)
    let currentLevelTitles = content.querySelectorAll(titleElementsSelectors[i]);

    currentLevelTitles.forEach(function (element) {
      // 添加通用类名和层级数据属性
      element.classList.add("title-element");
      element.setAttribute("data-title-level", titleHierarchy);

      // 如果没有 ID,则生成一个唯一 ID
      if (element.id === "") {
        tocElementNbr++;
        element.id = `title-element-${tocElementNbr}`;
      }
    });
  }

  // 2. 遍历所有标记为 "title-element" 的元素,创建目录列表项
  let tocElements = content.querySelectorAll(".title-element");

  for (let i = 0; i < tocElements.length; i++) {
    let tocElement = tocElements[i];
    let tocNewLi = document.createElement("li");

    // 添加目录项的层级类名
    tocNewLi.classList.add("toc-element");
    tocNewLi.classList.add(`toc-element-level-${tocElement.dataset.titleLevel}`);

    // 复制标题元素的其他类名到目录项(可选,用于样式继承)
    let classTocElement = tocElement.classList;
    for (let n = 0; n < classTocElement.length; n++) {
      if (classTocElement[n] !== "title-element") { // 避免复制内部使用的类名
        tocNewLi.classList.add(classTocElement[n]);
      }
    }

    // 创建锚点链接
    tocNewLi.innerHTML = `<a href="#${tocElement.id}">${tocElement.innerHTML}</a>`;
    tocUl.appendChild(tocNewLi);
  }
}

代码解释:

  • createToc 函数接收一个配置对象,包含文档内容根元素、目录容器选择器和标题元素选择器数组。
  • 它首先查找目录容器,并确保其中有一个 ID 为 list-toc-generated 的
      元素用于存放目录项。
  • 接着,它分两步处理:
    1. 标记标题: 遍历 titleElementsSelectors 中定义的每个标题选择器。对于匹配到的每个标题元素,它会添加 title-element 类和 data-title-level 数据属性来指示其层级。如果标题没有 id,它会为其生成一个唯一的 ID。
    2. 构建目录: 再次遍历所有带有 title-element 类的元素。为每个元素创建一个
    3. 标签,并生成一个 锚点链接,其 href 指向标题的 id,文本内容为标题的 innerHTML。最后,将
    4. 添加到目录
        中。

集成到 paged.js 工作流

为了让 createToc 函数在 paged.js 处理文档时自动执行,我们需要创建一个 Paged.Handler 并将其注册到 paged.js。

将以下代码添加到您的 HTML 文档的

部分,紧随 paged.js 脚本之后:
<script>
  // 确保 createToc 函数已定义
  // (您可以将 createToc 函数放在这里,或者确保它在全局作用域中可用)

  class CustomTocHandler extends Paged.Handler {
    constructor(chunker, polisher, caller) {
      super(chunker, polisher, caller);
    }

    // beforeParsed 钩子在 HTML 内容被解析但尚未进行分页布局之前执行
    beforeParsed(content) {
      // 调用 createToc 函数,传入配置
      createToc({
        content: content, // paged.js 提供的文档内容根元素
        tocElement: "#my-toc-content", // 您的目录容器 ID
        titleElements: ["h1", "h2", "h3"], // 您希望作为目录项的标题选择器数组
                                          // 例如: [".mw-content-ltr h2", "h3"]
      });
    }
  }
  // 注册您的自定义处理程序
  Paged.registerHandlers(CustomTocHandler);
</script>

代码解释:

  • 我们定义了一个名为 CustomTocHandler 的类,它继承自 Paged.Handler。
  • 在 CustomTocHandler 中,我们重写了 beforeParsed 方法。这个方法会在 paged.js 解析完所有 HTML 内容,但尚未开始计算页面布局之前被调用。
  • beforeParsed 方法接收一个 content 参数,它是当前文档内容的根元素,我们可以用它来查询和操作 DOM。
  • 在 beforeParsed 中,我们调用之前定义的 createToc 函数,并传入必要的配置。
    • content: 直接使用 paged.js 提供的 content 元素。
    • tocElement: 指定您的目录容器的选择器。
    • titleElements: 指定您希望包含在目录中的标题元素选择器数组。可以根据实际需求调整,例如 ["h1", "h2", "h3"] 或者更具体的 [".chapter-title", "article h2"]。
  • 最后,通过 Paged.registerHandlers(CustomTocHandler) 将您的处理程序注册到 paged.js。

注意事项与最佳实践

  1. CSS 样式: 生成的目录只包含基本的 HTML 结构。您需要编写 CSS 样式来美化目录,例如添加缩进以体现层级关系、调整字体大小和颜色等。
    #my-toc-content ul {
      list-style: none;
      padding-left: 0;
    }
    .toc-element-level-1 {
      margin-left: 0;
      font-weight: bold;
    }
    .toc-element-level-2 {
      margin-left: 20px;
    }
    .toc-element-level-3 {
      margin-left: 40px;
      font-style: italic;
    }
    /* 您也可以使用 CSS counters 来添加页码,paged.js 会自动处理 */
    /* 例如,在 @page 规则中设置页码 */
    @page {
      @bottom-right {
        content: counter(page);
      }
    }
  2. 选择器精度: titleElements 数组中的选择器应尽可能精确,以避免将不希望出现在目录中的元素包含进来。例如,如果您只想将文章主体内的

    包含进来,可以使用 article h2 而不是简单的 h2。

  3. ID 管理: createToc 函数已经包含了为没有 id 的标题自动生成 id 的逻辑,这增强了其健壮性。但如果您的标题本身就有稳定的 id,最好保持它们。
  4. 性能: 对于非常大的文档,querySelectorAll 操作可能会有性能开销。在大多数情况下,这种开销可以忽略不计,但如果遇到性能问题,可以考虑优化 DOM 查询。
  5. 可访问性: 确保生成的目录在语义上是正确的,并考虑到可访问性。例如,使用
    • 标签来表示列表结构。
    • 页码显示: 再次强调,paged.js 会通过 CSS 的 @page 规则和 counter(page) 功能在页眉或页脚自动添加页码。我们这里构建的 J*aScript 目录是基于锚点链接,它确保了目录项能够正确地导航到对应的章节,而实际的页码会在打印时由 paged.js 和浏览器/打印机引擎负责呈现。

总结

通过 paged.js 的处理程序机制,我们可以优雅地解决为可打印 HTML 文档动态生成目录的问题。这种方法避免了在 J*aScript 中复杂地计算页码,而是利用了 paged.js 在分页前对 DOM 进行操作的能力,通过锚点链接实现了目录与章节的关联。结合适当的 CSS 样式,您可以为您的打印输出提供一个专业、功能完善且易于导航的动态目录。

以上就是使用 paged.js 高效构建可打印HTML的动态目录的详细内容,更多请关注其它相关文章!

本文转自网络,如有侵权请联系客服删除。