使用html2canvas与jsPDF遇分页数据重复及iOS/Safari下载失败问题
问题解决:PDF生成分页重复、Safari下载失败及html2pdf适配问题
一、html2canvas+jsPDF分页内容重复问题
原代码的分页逻辑是直接将完整canvas偏移位置添加到新页面,导致内容重复。正确做法是截取canvas对应页面的区域,而非整图偏移。
修正代码:
const accordionTable = document.createElement('div'); ReactDOM.render(<ReportMainPDF repdata={response.data} />, accordionTable); document.body.appendChild(accordionTable); // 等待组件渲染完成,确保DOM完全加载 setTimeout(() => { html2canvas(accordionTable, { logging: true, scale: 2, allowTaint: true, useCORS: true }).then((canvas) => { const imgWidth = 208; const pageHeight = 295; const imgHeight = (canvas.height * imgWidth) / canvas.width; const pageCount = Math.ceil(imgHeight / pageHeight); const doc = new jsPDF('p', 'mm', [210, 297]); // 用标准A4尺寸减少适配问题 for (let i = 0; i < pageCount; i++) { // 创建临时canvas截取当前页内容 const tempCanvas = document.createElement('canvas'); tempCanvas.width = canvas.width; tempCanvas.height = pageHeight * (canvas.width / imgWidth); // 按比例计算每页高度 const ctx = tempCanvas.getContext('2d'); // 截取对应区域:y轴偏移为当前页的起始位置 const yOffset = i * tempCanvas.height; ctx.drawImage(canvas, 0, yOffset, canvas.width, tempCanvas.height, 0, 0, tempCanvas.width, tempCanvas.height); if (i > 0) doc.addPage(); doc.addImage(tempCanvas.toDataURL('image/png'), 'PNG', 0, 0, imgWidth, pageHeight); } // 兼容Safari的下载逻辑 const blob = doc.output('blob'); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'test.pdf'; document.body.appendChild(a); // Safari特殊处理:无法直接触发a标签下载,改用打开新窗口让用户手动保存 if (navigator.userAgent.includes('Safari') && !navigator.userAgent.includes('Chrome')) { window.open(url); } else { a.click(); } setTimeout(() => { URL.revokeObjectURL(url); a.remove(); }, 100); ReactDOM.unmountComponentAtNode(accordionTable); accordionTable.remove(); setIsLoading(false); }); }, 500); // 等待时间可根据组件渲染复杂度调整
二、iPhone Safari无法下载PDF问题
Safari对doc.save()的支持存在兼容性问题,改用Blob生成下载链接做兼容:
- 先将PDF转为Blob对象,创建临时URL
- 非Safari环境用a标签触发下载,Safari环境打开新窗口让用户手动保存PDF
三、html2pdf内容放大适配+空白页问题
改用html2pdf时,需调整配置确保渲染正确:
修正配置示例:
const accordionTable = document.createElement('div'); ReactDOM.render(<ReportMainPDF repdata={response.data} />, accordionTable); document.body.appendChild(accordionTable); setTimeout(() => { const opt = { margin: 10, filename: 'test.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 1, logging: true, allowTaint: true, useCORS: true }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }, pagebreak: { mode: ['css', 'legacy'] } // 启用CSS分页规则 }; // 确保生成PDF的元素在DOM中可见,避免移动端渲染空白 accordionTable.style.display = 'block'; accordionTable.style.position = 'relative'; accordionTable.style.zIndex = '9999'; html2pdf().set(opt).from(accordionTable).save().then(() => { ReactDOM.unmountComponentAtNode(accordionTable); accordionTable.remove(); setIsLoading(false); }); }, 800);
关键注意点:
- 等待React组件完全渲染后再调用html2pdf,避免未加载完成导致空白
- 设置
html2canvas.scale:1避免内容放大,配合jsPDF.format:'a4'适配页面 - 启用
pagebreak规则,或在React组件中用CSSpage-break-after: always;手动设置分页 - 确保生成PDF的元素在DOM中可见,避免移动端因隐藏元素导致空白
内容的提问来源于stack exchange,提问作者Abdur Rehman
相关产品推荐
相关产品推荐

