jsPDF结合html2canvas导出多页PDF时底部内容被截断问题
问题描述
开发页面导出功能时,需实现点击按钮将当前页面内HTML控件内容导出为PDF,选用jsPDF与html2canvas库完成开发后,单页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'仅对纯文本做切页判断,会直接从图片、表格、卡片等块级元素中间切割,同时未配置元素避断规则
修复方案
核心调整点
- 初始化PDF时提前计算页眉、页脚占用高度,给正文区域划定明确排版范围,禁止内容排版到页眉页脚区域
- 替换硬编码尺寸逻辑,直接从jsPDF实例读取A4实际宽高计算缩放比,避免版本差异导致的尺寸不匹配
- 将
autoPaging模式替换为'slice',配合CSSpage-break规则避免块级元素被从中间截断 - 调整页眉页脚绘制时机,在单页内容渲染完成后立刻绘制页眉页脚,避免内容覆盖;用jsPDF内置对齐参数替代手动位置计算,消除对齐偏移
- 开启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
相关产品推荐
相关产品推荐

