You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用html2pdf.js实现目录准确动态页码的问题求助

html2pdf.js实现目录准确动态页码的问题求助

大家好,我现在在尝试用html2pdf.js生成带目录的PDF,要求目录里的页码能和实际章节在PDF中的位置精准对应,但目前的实现有问题——目录里的页码和最终PDF里章节的实际位置对不上,完全没达到预期效果。

我的需求和预期PDF结构

我想要的PDF结构是这样的:

  • 封面页(无页码)
  • 目录页(页码ii)
  • 章节1(页码1)
  • 章节2(页码3)
  • ……以此类推

我试过的解决方案:双次生成法

我本来想通过两次生成PDF的思路来解决页码匹配问题:

  1. 第一次生成PDF,获取各个章节在PDF中的位置
  2. 用获取到的页码更新DOM里的目录内容
  3. 第二次基于更新后的DOM生成最终的PDF

遇到的核心问题

第一次生成后提取的章节页码,更新到目录后,最终PDF里的章节实际位置和目录标注的还是对不上,页码偏差完全没解决。

我的代码实现

下面是我目前的完整代码:

const convertToPdf = async () => {
  const { default: html2pdf } = await import('html2pdf.js');
  if (typeof window === 'undefined' || typeof document === 'undefined') return;

  const content = document.getElementById('pdf-content');
  const opt = {
    filename: 'Incident Report Analysis.pdf',
    pagebreak: {
      mode: ['css', 'legacy'],
      before: '.avoid-page-break',
      avoid: ['div', '#breakdata', 'tr', 'table']
    },
    image: { type: 'jpeg', quality: 1 },
    html2canvas: { scale: 1.4, logging: true },
    jsPDF: { unit: 'in', format: 'A3', orientation: 'portrait' },
    margin: [0.1, 0, 0.30, 0],
  };

  // 第一次生成PDF,提取章节位置
  const firstPassPdf = await html2pdf().set(opt).from(content).toPdf().get('pdf');
  await new Promise(resolve => setTimeout(resolve, 500));

  const extractedSections = extractSectionPositions(firstPassPdf, content, opt.html2canvas.scale);
  // 调整页码(+1是因为要加上目录页的偏移)
  const updatedSections = extractedSections.map(section => ({
    ...section,
    page: section.page + 1
  }));

  // 更新DOM中的目录页码
  updatedSections.forEach(section => {
    const span = document.querySelector(`.toc-page[data-target="${section.title}"]`);
    if (span) {
      span.textContent = section.page;
    }
  });

  // 强制React更新DOM(等待渲染完成)
  setSections(updatedSections);
  await new Promise(resolve => setTimeout(resolve, 500));

  // 生成最终的PDF
  const finalContent = document.getElementById('pdf-content');
  const finalPdf = await html2pdf().set(opt).from(finalContent).toPdf().get('pdf');

  // 添加页脚和其他元素
  const totalPages = finalPdf.internal.getNumberOfPages();
  const pageWidth = finalPdf.internal.pageSize.width;
  const pageHeight = finalPdf.internal.pageSize.height;
  const footerHeight = 0.4;
  const footerYPosition = pageHeight - footerHeight;
  const centerYPosition = footerYPosition + footerHeight / 2;

  for (let i = 1; i <= totalPages; i++) {
    finalPdf.setPage(i);
    finalPdf.setFontSize(12);
    finalPdf.setFont('helvetica');

    if (i === 2) { // 目录页添加标题
      finalPdf.setFontSize(14);
      finalPdf.setTextColor(0, 0, 0);
      finalPdf.text("Table of Contents", 0.3, 0.5);
    }

    if (i > 1) { // 非封面页添加页脚
      finalPdf.setFillColor(51, 51, 51);
      finalPdf.rect(0, footerYPosition, pageWidth, footerHeight, 'F');
      finalPdf.setTextColor(255, 255, 255);
      finalPdf.text('WWW.ICORPSECURITY.COM.AU', 0.3, centerYPosition, { align: 'left', baseline: 'middle' });
      finalPdf.text(`Page ${i} of ${totalPages}`, pageWidth - 0.3, centerYPosition, { align: 'right', baseline: 'middle' });
    } else { // 封面页的页脚样式
      finalPdf.setFillColor(245, 245, 246);
      finalPdf.rect(0, footerYPosition, pageWidth, footerHeight, 'F');
    }
  }

  finalPdf.save(`Incident Analysis (${dayjs(formattedStartDateRange).format('DD/MM/YYYY')} - ${dayjs(formattedEndDateRange || formattedStartDateRange).format('DD/MM/YYYY')}).pdf`);
  if (onDownloadComplete) {
    onDownloadComplete();
  }
};

const extractSectionPositions = (pdf, content, scale) => {
  const sectionElements = content.querySelectorAll('[data-section]');
  const pixelsPerInch = 96;
  const pixelsPerPage = (pdf.internal.pageSize.height * pixelsPerInch) / scale;

  const sections = Array.from(sectionElements).map(el => {
    const top = el.offsetTop;
    const pageNum = Math.floor(top / pixelsPerPage) + 1;
    console.log(pageNum, "pageNum");
    return {
      title: el.getAttribute('data-section'),
      page: pageNum
    };
  }).sort((a, b) => a.page - b.page);

  return sections;
};

有没有大佬能帮我看看问题出在哪?或者有没有更可靠的方法来实现html2pdf.js的动态目录页码匹配?

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.09 03:03:10