在许多应用中,将网页内容转换为 PDF 文档是一项常见的需求,特别是在生成报告、发票或任何其他需要以 PDF 格式保存的内容时。本文将展示如何结合使用 html2canvas 和 pdf-lib 两个库,将 HTML 元素转换为图像并嵌入到 PDF 文件中,最后实现文件的下载。与传统的解决方案相比,采用异步处理和分批处理方式,能有效解决大数据量下的卡顿问题,提高性能和用户体验。
解决方案概述
我们将通过以下步骤完成任务:
- html2canvas 用于将 HTML 元素转换为 Canvas 图像。
- 使用 pdf-lib 来生成 PDF 文档,将每页图像嵌入 PDF。
- 分批处理每一页的图像,以减少浏览器的内存占用,避免大文件下载时导致页面卡顿。
- 提供下载功能,用户点击按钮后,异步生成并下载 PDF 文件。
1. 示例 HTML 结构
首先,我们定义一个简单的 HTML 结构,其中包含一个报告内容的页面,并提供下载按钮。
<style>
.reportPage {
width: 595px; /* A4 宽度 */
height: auto; /* 自适应高度 */
border: 1px solid #000; /* 边框 */
padding: 20px; /* 内边距 */
margin: 20px; /* 外边距 */
}
.hidden {
display: none; /* 隐藏的元素不被包含 */
}
</style>
<template>
<div>
<div class="reportPage" ref="reportPage">
<h1>信息数据转换成报告形式</h1>
<p>这是数据转换报告的内容。</p>
<!-- 假设这里还有更多内容,通过遍出来 -->
<div v-for="n in 15" :key="n">
<h2>内容块 {{ n }}</h2>
<p>这是内容块 {{ n }} 的文本。</p>
</div>
</div>
<button :disabled="disDownloadReport" @click="downloadReports">下载报告</button>
</div>
</template>
2. 使用 html2canvas
和 jsPDF
生成 PDF
import html2canvas from 'html2canvas'
import jsPDF from 'jspdf'
export default {
data() {
return {
pageDataArr: [], // 存储每一页的图像数据
totalPages: 15, // 总页数
pagesPerBatch: 5, // 每批处理的页数
disDownloadReport: false, // 按钮是否禁用
};
},
methods: {
// 点击下载报告的方法
downloadReports() {
this.disDownloadReport = true; // 下载开始时禁用按钮
// 将页面滚动位置重置到顶部
document.documentElement.scrollLeft = 0;
document.documentElement.scrollTop = 0;
// 获取所有可见的报告页面
let docs = document.querySelectorAll('.reportPage:not(.hidden)');
let pdf = new jsPDF('p', 'pt', 'a4'); // 创建新的 PDF 文档
this.pageDataArr = []; // 清空图像数据数组
// 使用 Promise.all 处理所有页面转换
let proArr = Array.from(docs).map((doc, index) => {
return this.htmlToCanvas(doc, index); // 将每个页面转换为 Canvas
});
Promise.all(proArr).then(data => {
// 遍历每一页的图像数据,将其添加到 PDF 中
for (let i = 0; i < this.pageDataArr.length; i++) {
let contentWidth = data[0].width; // 获取内容宽度
let contentHeight = data[0].height; // 获取内容高度
// A4纸的尺寸[595.28, 841.89],计算图像的宽高
let imgWidth = 595; // 图像宽度
let imgHeight = (595 / contentWidth) * contentHeight; // 计算图像高度,保持宽高比
// 将图像添加到 PDF 页面
pdf.addImage(this.pageDataArr[i], 'JPEG', 0, 0, imgWidth, imgHeight);
// 如果不是最后一页,则添加新的一页
if (i < docs.length - 1) {
pdf.addPage();
}
}
// 延时 2 秒后保存 PDF 文件
setTimeout(() => {
pdf.save("xxx报告.pdf"); // 触发文件下载
this.disDownloadReport = false; // 生成后,恢复按钮状态
}, 2000);
});
},
// 将 HTML 元素转换为 Canvas 的方法
htmlToCanvas(doc, index) {
return new Promise((resolve, reject) => {
// 获取元素的计算样式
const box = window.getComputedStyle(doc);
// 获取 DOM 节点的宽高
const width = box.width.replace('px', ''); // 获取元素宽度
const height = box.height.replace('px', ''); // 获取元素高度
// 根据设备像素比进行缩放
const scaleBy = window.devicePixelRatio > 1 ? window.devicePixelRatio : 1;
// 使用 html2canvas 将 HTML 元素转换为 Canvas
html2canvas(doc, {
scale: window.devicePixelRatio * 2 || scaleBy, // 设置缩放比例
width, // 设置画布宽度
height, // 设置画布高度
scrollX: 0,
scrollY: 0
}).then((canvas) => {
// 将 Canvas 转换为 JPEG 格式的数据 URL
let pageData = canvas.toDataURL('image/jpeg', 1.0);
this.pageDataArr[index] = pageData; // 存储图像数据
resolve(canvas); // 解析 Promise
}).catch(error => {
console.error('Error generating canvas:', error); // 捕获并记录转换错误
reject(error); // 拒绝 Promise
});
});
},
},
};
3. 性能瓶颈问题:jsPDF
和内存占用
虽然 jsPDF
是一个非常流行的库,适用于生成 PDF 文件,但它也有一些限制:
- 内存占用:当页面内容过多时(比如长报告或多个页数的文档),
jsPDF
会占用大量内存,可能导致浏览器崩溃。 - 性能瓶颈:当需要处理大量页面时,
jsPDF
会变得非常缓慢,尤其是在处理多个图像时。
使用 pdf-lib
替代 jsPDF
处理大数据量的 PDF
pdf-lib
是另一个优秀的 JavaScript 库,提供了更加高效且内存友好的方法来生成和操作 PDF。pdf-lib
支持直接将图像嵌入到 PDF 中,并且具有更好的性能表现,尤其是在处理大量图像时。
4. 使用 pdf-lib
替换 jsPDF
进行优化
为了提高性能,避免 jsPDF
在处理大量内容时崩溃,可以使用 pdf-lib
来替代 jsPDF
。这里我们优化的主要思路是:
- 异步处理:使用异步方法逐步处理每个图像,避免一次性处理过多内容导致页面崩溃。
- 分批处理:避免一次性加载所有页面的图像数据,而是分批加载和合并,提高内存利用效率。
4.1 使用 pdf-lib
替代 jsPDF
import html2canvas from 'html2canvas'
import { PDFDocument } from 'pdf-lib'; // 导入 pdf-lib
async downloadReports() {
// 开始下载报告时禁用下载按钮
this.disDownloadReport = true;
// 将页面滚动位置重置到顶部
document.documentElement.scrollLeft = 0;
document.documentElement.scrollTop = 0;
// 获取所有非隐藏的报告页面
const docs = document.querySelectorAll('.reportPage:not(.hidden)');
const totalDocs = docs.length; // 计算报告页面总数
const pdfDoc = await PDFDocument.create(); // 创建新的 PDF 文档
this.pageDataArr = []; // 用于存储每页的图像数据
const batchSize = 5; // 每批处理 5 页
let batches = Math.ceil(totalDocs / batchSize); // 计算需要的总批次
// 遍历每个批次
for (let batch = 0; batch < batches; batch++) {
const start = batch * batchSize; // 当前批次开始的索引
const end = Math.min(start + batchSize, totalDocs); // 当前批次结束的索引
const batchPromises = []; // 用于存储当前批次的 Promise
// 遍历当前批次的页面
for (let i = start; i < end; i++) {
// 将每页转换为 canvas,并将 Promise 添加到数组中
batchPromises.push(this.htmlToCanvas(docs[i], i));
}
// 等待当前批次的所有页面转换完成
await Promise.all(batchPromises);
// 将当前批次的图像嵌入到 PDF 中
for (let i = start; i < end; i++) {
// 获取当前页面图像的字节数据
const imgBytes = await fetch(this.pageDataArr[i]).then((res) => res.arrayBuffer());
// 嵌入 JPEG 图像
const img = await pdfDoc.embedJpg(imgBytes);
// 创建新的一页,设置页面尺寸为 A4
const page = pdfDoc.addPage([595.28, 841.89]);
// 计算图像自适应宽高
const { width, height } = img.scaleToFit(595.28, 841.89);
// 将图像绘制到页面中心
page.drawImage(img, {
x: (page.getWidth() - width) / 2,
y: (page.getHeight() - height) / 2,
width,
height
});
}
}
// 生成 PDF 文件并触发下载
const pdfBytes = await pdfDoc.save(); // 保存 PDF 文档
const blob = new Blob([pdfBytes], { type: 'application/pdf' }); // 创建 Blob 对象
const url = URL.createObjectURL(blob); // 生成下载链接
const a = document.createElement('a'); // 创建一个临时链接
a.href = url; // 设置链接地址
// 下载文件的名称
a.download = (this.reportInfo.name || this.reportInfo.examinationName) + '报告.pdf';
document.body.appendChild(a); // 将链接添加到文档
a.click(); // 触发下载
document.body.removeChild(a); // 下载后移除链接
// 下载完成后恢复下载按钮状态
this.disDownloadReport = false;
},
downloadReports
这个方法会在点击下载按钮时触发,执行以下操作:
- 获取所有页面的 HTML 元素。
- 使用
html2canvas
转换 HTML 元素为 Canvas 图像。 - 使用
pdf-lib
将图像嵌入到 PDF 文档中。
4.2 htmlToCanvas
方法
htmlToCanvas(doc, index) {
// 将 HTML 元素转换为 Canvas 的函数
return new Promise((resolve, reject) => {
// 获取元素的计算样式
const box = window.getComputedStyle(doc);
const width = box.width.replace('px', ''); // 获取元素宽度
const height = box.height.replace('px', ''); // 获取元素高度
// 根据设备像素比进行缩放
const scaleBy = window.devicePixelRatio > 1 ? window.devicePixelRatio : 1;
// 使用 html2canvas 将 HTML 元素转换为 Canvas
html2canvas(doc, {
scale: window.devicePixelRatio * 2, // 设置缩放比例
width,
height,
scrollX: 0,
scrollY: 0
})
.then((canvas) => {
// 将 Canvas 转换为 JPEG 格式的数据 URL
let pageData = canvas.toDataURL('image/jpeg', 1.0);
this.pageDataArr[index] = pageData; // 存储图像数据
resolve(canvas); // 解析 Promise
})
.catch((error) => {
// 捕获并记录转换错误
console.error('Error generating canvas:', error);
reject(error); // 拒绝 Promise
});
});
},
该方法将 HTML 元素转换为 Canvas 图像。我们使用 html2canvas
来进行转换,并返回一个包含图像数据的 Promise。
5. 总结
通过结合使用 html2canvas
和 pdf-lib
,我们能够将网页内容转换为图像并嵌入 PDF 文件中。在此过程中,我们采用了按批处理和异步处理的方式,有效避免了卡顿和性能瓶颈。该方案非常适用于生成报告、发票或其他需要以 PDF 格式保存的内容。