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

jsPDF结合html2canvas导出多页PDF时底部内容被截断问题

问题描述

开发页面导出功能时,需实现点击按钮将当前页面内HTML控件内容导出为PDF,选用jsPDF与html2canvas库完成开发后,单页PDF可正常生成,但多页场景下存在两类异常:

  • 页面底部内容被截断
  • 跨页后内容对齐错位

问题效果

多页PDF内容对齐异常、底部截断问题示例

现有实现代码

const input = document.getElementById('print');
  const pdf = new jsPDF({
    orientation: 'portrait',
    unit: 'pt',
    format: 'a4',
    compress:true,
  });

  //check for Screen dimensions
  let srcwidth = document.getElementById('print').scrollWidth;

  pdf
    .html(input, {
      margin: [55, 0, 40, -2],
      autoPaging: 'text',
      html2canvas: {
        scale: 595.26 / srcwidth, //595.26 is the width of A4 page
        scrollY: 0,
      },
    })
    .then(() => {
      const pageCount = pdf.internal.getNumberOfPages();
      const pageWidth = pdf.internal.pageSize.getWidth();
      var pageHeight = pdf.internal.pageSize.getHeight();

      //loop to set header/ footer, style, alignment, text style to the page
      for (let i = 1; i <= pageCount; i++) {
        pdf.rect(
          5,
          5,
          pdf.internal.pageSize.width - 13,
          pdf.internal.pageSize.height - 13,
          'S',
        );
        pdf.setPage(i);
        pdf.viewerPreferences({"FitWindow":true,"ViewArea":"MediaBox","ViewClip":"MediaBox" });
        var header = 'CSMM 5A - Vessel Cyber Security Self Assessment';
        var headerLine =
          '_________________________________________________________________________________';
        const footer = `Page ${i} of ${pageCount}`;
        var submitedDate =
          'Date Last Edited: ' +
          this.formatDateTime(this.state.vesselAssesementData.editedOn);

        pdf
          .setFont('sans-serif', '', 'bold')
          .setFontSize(14)
          .text(header, 20, 25, { baseline: 'top' });
        pdf.addImage(kline, 'PNG', 530, 12, 35, 35);
        pdf.text(headerLine, 10, 38, { baseline: 'top' });

        pdf
          .setFont('Arial, sans-serif', '', 'normal')
          .setFontSize(9)
          .text(
            submitedDate,
            pageWidth / 6.5 - pdf.getTextWidth(submitedDate) / 2,
            pageHeight - 18,
            { baseline: 'bottom' },
          );

        pdf
          .setFont('Arial, sans-serif', '', 'normal')
          .setFontSize(9)
          .text(
            footer,
            pageWidth / 1.08 - pdf.getTextWidth(footer) / 2,
            pageHeight - 18,

            { baseline: 'bottom|right' },
          );
      }
      pdf.save(
        `${
          'VesselCyberSecuritySelfAssessmentReport_' +
          this.state.vesselAssesementData.uniqueId
        }.pdf`,
      );

问题根因

现有实现存在3个核心逻辑缺陷,直接导致截断和对齐问题:

  • 切页逻辑未预留页眉、页脚占位空间:autoPaging按全A4页高度切割内容,渲染完成后手动添加的页眉页脚会直接覆盖对应位置的内容,造成底部截断
  • 缩放参数硬编码A4宽度值,仅做宽度适配未做DPI和Y轴位置校准,跨页时水平偏移量累积造成对齐异常
  • autoPaging: 'text'仅对纯文本做切页判断,会直接从图片、表格、卡片等块级元素中间切割,同时未配置元素避断规则

修复方案

核心调整点

  1. 初始化PDF时提前计算页眉、页脚占用高度,给正文区域划定明确排版范围,禁止内容排版到页眉页脚区域
  2. 替换硬编码尺寸逻辑,直接从jsPDF实例读取A4实际宽高计算缩放比,避免版本差异导致的尺寸不匹配
  3. 将autoPaging模式替换为'slice',配合CSSpage-break规则避免块级元素被从中间截断
  4. 调整页眉页脚绘制时机,在单页内容渲染完成后立刻绘制页眉页脚,避免内容覆盖;用jsPDF内置对齐参数替代手动位置计算,消除对齐偏移
  5. 开启html2canvas跨域支持,避免图片资源加载失败造成的布局偏移

修正后完整代码

const input = document.getElementById('print');
// 提前定义页眉、页脚占用高度(单位pt,与jsPDF单位保持一致)
const HEADER_HEIGHT = 55;
const FOOTER_HEIGHT = 40;
const pdf = new jsPDF({
  orientation: 'portrait',
  unit: 'pt',
  format: 'a4',
  compress: true,
});

const a4Width = pdf.internal.pageSize.getWidth();
const a4Height = pdf.internal.pageSize.getHeight();
const srcwidth = input.scrollWidth;

pdf
  .html(input, {
    // 页边距:上=页眉高度,下=页脚高度,左右留统一边距
    margin: [HEADER_HEIGHT, 10, FOOTER_HEIGHT, 10],
    autoPaging: 'slice',
    html2canvas: {
      scale: a4Width / srcwidth, // 按A4实际宽度等比缩放
      scrollY: 0,
      useCORS: true, // 开启跨域图片支持,避免图片渲染偏移
      logging: false
    },
    // 单页渲染完成回调,立刻绘制页眉页脚避免被内容覆盖
    callback: (doc) => {
      const pageCount = doc.internal.getNumberOfPages();
      for (let i = 1; i <= pageCount; i++) {
        doc.setPage(i);
        // 绘制页面边框
        doc.rect(5, 5, a4Width - 10, a4Height - 10, 'S');
        doc.viewerPreferences({"FitWindow":true,"ViewArea":"MediaBox","ViewClip":"MediaBox" });

        const header = 'CSMM 5A - Vessel Cyber Security Self Assessment';
        const headerLine = '_________________________________________________________________________________';
        const footer = `Page ${i} of ${pageCount}`;
        const submitedDate = 'Date Last Edited: ' + this.formatDateTime(this.state.vesselAssesementData.editedOn);

        // 绘制页眉
        doc.setFont('sans-serif', '', 'bold')
          .setFontSize(14)
          .text(header, 20, 25, { baseline: 'top' });
        doc.addImage(kline, 'PNG', a4Width - 65, 12, 35, 35); // 动态计算logo位置,避免硬编码偏移
        doc.text(headerLine, 10, 38, { baseline: 'top' });

        // 绘制页脚
        doc.setFont('Arial, sans-serif', '', 'normal')
          .setFontSize(9)
          .text(
            submitedDate,
            20,
            a4Height - 18,
            { baseline: 'bottom' },
          );
        doc.text(
          footer,
          a4Width - 20,
          a4Height - 18,
          { baseline: 'bottom', align: 'right' }, // 用内置对齐参数,避免手动计算偏移
        );
      }
    }
  })
  .then(() => {
    pdf.save(
      `VesselCyberSecuritySelfAssessmentReport_${this.state.vesselAssesementData.uniqueId}.pdf`
    );
  });

配套CSS配置

给导出区域元素添加断行规则,避免块级内容被跨页截断:

#print * {
  page-break-inside: avoid;
}
/* 表格单独配置,允许行级断行,避免整表被挤到下一页产生大面积空白 */
#print tr {
  page-break-inside: avoid;
  page-break-after: auto;
}

注意事项

  • 导出前确认#print容器没有transform、fixed定位属性,这类属性会造成html2canvas渲染偏移
  • 长表格场景可单独给表格标签设置page-break-inside: auto,仅保证单行不被截断即可

内容的提问来源于stack exchange,提问作者Dante

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 08:39:15