介绍
在许多应用中,将网页内容转换为 PDF 文档是一项常见的需求。本文将展示如何使用 html2canvas 和 pdf-lib 两个库,将 HTML 元素转换为图像并嵌入到 PDF 文件中,最后实现文件的下载。这个解决方案非常适用于生成报告、发票或任何其他需要以 PDF 格式保存的内容。
环境准备
在开始之前,确保开发环境中已安装以下库:
- html2canvas:用于将 HTML 元素转换为 Canvas 图像。
- pdf-lib:用于创建和处理 PDF 文档。
可以通过 npm 安装这两个库:
npm install html2canvas pdf-lib
代码解析
下面的代码展示了一个类方法,包含了生成报告的完整过程。我们将逐行解释每个部分的功能。
1. 下载报告的方法
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 = []; // 存储每页的图像数据
解释:
- 当用户开始下载报告时,首先禁用下载按钮,避免重复点击。
- 然后重置页面的滚动位置,确保 PDF 的内容从顶部开始。
- 使用
querySelectorAll获取所有可见的报告页面,并计算页面的总数。 - 创建一个新的 PDF 文档,并初始化一个数组以存储图像数据。
2. 处理批次
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
解释:
- 设置每批处理的页面数为 5 页。
- 计算出需要的总批次数。
- 遍历每个批次,计算每个批次的开始和结束索引,并初始化一个数组用于存储 Promise。
3. 转换 HTML 为 Canvas
for (let i = start; i < end; i++) {
batchPromises.push(this.htmlToCanvas(docs[i], i)); // 将每页转换为 canvas
}
await Promise.all(batchPromises); // 等待所有页面转换完成
解释:
- 在每个批次中,将当前页面的 HTML 转换为 Canvas,存储 Promise 以便后续处理。
- 使用
Promise.all确保所有页面都已成功转换后再进行下一步。
4. 嵌入图像到 PDF
for (let i = start; i < end; i++) {
const imgBytes = await fetch(this.pageDataArr[i]).then((res) => res.arrayBuffer());
const img = await pdfDoc.embedJpg(imgBytes); // 嵌入 JPEG 图像
const page = pdfDoc.addPage([595.28, 841.89]); // A4 页面
const { width, height } = img.scaleToFit(595.28, 841.89); // 计算图像自适应宽高
page.drawImage(img, {
x: (page.getWidth() - width) / 2,
y: (page.getHeight() - height) / 2,
width,
height
});
}
解释:
- 将每个页面的图像字节数据读取为 ArrayBuffer,并嵌入到 PDF 文档中。
- 为每个页面添加一个新的 A4 页面,并计算图像的适应大小,使其居中显示。
5. 生成并下载 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.name) + '报告.pdf';
document.body.appendChild(a); // 将链接添加到文档
a.click(); // 触发下载
document.body.removeChild(a); // 下载后移除链接
解释:
- 生成 PDF 字节并创建 Blob 对象,形成可下载的链接。
- 创建一个临时的链接元素并触发下载。
- 下载完成后,恢复下载按钮的状态。
6. HTML 转 Canvas 的辅助函数
htmlToCanvas(doc, index) {
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(doc, {
scale: window.devicePixelRatio * 2,
width,
height,
scrollX: 0,
scrollY: 0
})
.then((canvas) => {
let pageData = canvas.toDataURL('image/jpeg', 1.0);
this.pageDataArr[index] = pageData; // 存储图像数据
resolve(canvas);
})
.catch((error) => {
console.error('Error generating canvas:', error);
reject(error);
});
});
}
解释:
- 将 HTML 元素转换为 Canvas,并返回一个 Promise 以便异步处理。
- 使用
html2canvas进行转换,并处理可能出现的错误。
使用方法
要使用上述代码,只需执行以下步骤:
-
确保已引入 html2canvas 和 pdf-lib:在 HTML 文件中或通过 JavaScript 模块引入这两个库。
-
创建报告页面:在 HTML 中创建具有类名为
reportPage的元素。确保这些元素是可见的,且符合报告格式。 -
调用
downloadReports方法:在用户点击下载按钮时调用此方法,开始生成并下载报告。
示例按钮代码:
<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>
最后展示整体代码
<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>
<script>
import html2canvas from 'html2canvas';
import { PDFDocument } from 'pdf-lib'; // 导入 pdf-lib
export default {
data(){
return{
pageDataArr: [], // 存储每一页的图像数据
totalPages: 15, // 总页数
pagesPerBatch: 5, // 每批处理的页数
disDownloadReport: false, // 按钮是否禁用
}
},
methods:{
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.name) + '报告.pdf';
document.body.appendChild(a); // 将链接添加到文档
a.click(); // 触发下载
document.body.removeChild(a); // 下载后移除链接
// 下载完成后恢复下载按钮状态
this.disDownloadReport = false;
},
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
});
});
},
}
}
</script>
结论
通过上述步骤,将网页内容转换为 PDF 文档,并实现下载功能。此方法不仅高效,而且易于实现,适合于各种需要生成 PDF 报告的场景。 如果在实现过程中遇到任何问题,请随时参考库的文档或寻求社区的帮助。 可以根据具体的项目需求和受众调整文章的内容和深度。如果有特定的格式要求或风格。