首页 > web前端 > js教程 > 正文

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

霞舞
发布: 2025-11-28 12:01:37
原创
108人浏览过

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

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

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

核心原理

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

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

立即学习前端免费学习笔记(深入)”;

准备您的 HTML 结构

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

  1. 章节标题: 使用语义化的标题标签(如 <h1>, <h2>, <h3> 等)来标识您的章节。为确保锚点链接的正确性,这些标题需要有唯一的 id 属性。如果标题本身没有 id,我们将通过 JavaScript 动态添加。
  2. 目录占位符: 在您希望目录出现的位置创建一个空的容器元素,例如一个 div 或 ul,并为其指定一个唯一的 id。

示例 HTML 结构:

讯飞绘文
讯飞绘文

讯飞绘文:免费AI写作/AI生成文章

讯飞绘文 118
查看详情 讯飞绘文
<!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>

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

</body>
</html>
登录后复制

动态生成目录的 JavaScript 逻辑

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

/**
 * 动态生成目录
 * @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 的 <ul> 元素用于存放目录项。
  • 接着,它分两步处理:
    1. 标记标题: 遍历 titleElementsSelectors 中定义的每个标题选择器。对于匹配到的每个标题元素,它会添加 title-element 类和 data-title-level 数据属性来指示其层级。如果标题没有 id,它会为其生成一个唯一的 ID。
    2. 构建目录: 再次遍历所有带有 title-element 类的元素。为每个元素创建一个 <li> 标签,并生成一个 <a> 锚点链接,其 href 指向标题的 id,文本内容为标题的 innerHTML。最后,将 <li> 添加到目录 <ul> 中。

集成到 paged.js 工作流

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

将以下代码添加到您的 HTML 文档的 <head> 部分,紧随 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 数组中的选择器应尽可能精确,以避免将不希望出现在目录中的元素包含进来。例如,如果您只想将文章主体内的 <h2> 包含进来,可以使用 article h2 而不是简单的 h2。
  3. ID 管理: createToc 函数已经包含了为没有 id 的标题自动生成 id 的逻辑,这增强了其健壮性。但如果您的标题本身就有稳定的 id,最好保持它们。
  4. 性能: 对于非常大的文档,querySelectorAll 操作可能会有性能开销。在大多数情况下,这种开销可以忽略不计,但如果遇到性能问题,可以考虑优化 DOM 查询。
  5. 可访问性: 确保生成的目录在语义上是正确的,并考虑到可访问性。例如,使用 <ul> 和 <li> 标签来表示列表结构。
  6. 页码显示: 再次强调,paged.js 会通过 CSS 的 @page 规则和 counter(page) 功能在页眉或页脚自动添加页码。我们这里构建的 JavaScript 目录是基于锚点链接,它确保了目录项能够正确地导航到对应的章节,而实际的页码会在打印时由 paged.js 和浏览器/打印机引擎负责呈现。

总结

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

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

HTML速学教程(入门课程)
HTML速学教程(入门课程)

HTML怎么学习?HTML怎么入门?HTML在哪学?HTML怎么学才快?不用担心,这里为大家提供了HTML速学教程(入门课程),有需要的小伙伴保存下载就能学习啦!

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号