目前市面上常用的前端导出PDF库组合一般为:
1. html2canvas + js-pdf
2. html2canvas+pdf-lib
3. domtoimage+pdf-lib
因本人项目中导出pdf需求为导出30页及以上的多页pdf,考虑性能问题,选择了 html2canvas+pdf-lib 及domtoimage+pdf-lib两种方式尝试实现
html2canvas+pdf-lib(个人推荐,因为适配ios Safari浏览器)
本人是先尝试使用的domtoimage+pdf-lib方案,但实测中发现H5在ios Safari浏览器端倒不出来故有个html2canvas+pdf-lib方案,经实战测试该方案能够适配ios Safari浏览器导出!!!
代码如下:
首先引入必要插件:
yarn add pdf-lib
yarn add html2canvas
yarn add file-saver
file-saver 插件很重要,使用a.click方案导出的pdf在Safari中不是直接下载,而是打开一个类似预览页的页面查看pdf,需用户分享导出,比较麻烦。
async downloadPDF() {
// 创建一个新的 PDF 文档
const pdfDoc = await PDFDocument.create();
// 处理需转pdf的dom的id数组
const pdfDoms = await this.handlePDFPageDom();
this.allNum = pdfDoms.length;
for (let i = 0; i < pdfDoms.length; i++) {
this.loadText = `文件生成中${i + 1}/${this.allNum}`;
const doc = document.querySelector("#" + pdfDoms[i]);
const canvas = await html2canvas(doc, {
scale: 2, // 提高清晰度,控制内存
useCORS: true,
});
const imgDataUrl = canvas.toDataURL("image/jpeg", 0.95); // 压缩图像
const imgBytes = await fetch(imgDataUrl).then((res) =>
res.arrayBuffer()
);
const img = await pdfDoc.embedJpg(imgBytes);
// const { width, height } = img.scaleToFit(595.28, 841.89);
const A4_WIDTH = 595.28; // A4 宽度
const A4_HEIGHT = 841.89; // A4 高度
const scale = Math.min(A4_WIDTH / img.width, A4_HEIGHT / img.height);
const scaledWidth = img.width * scale;
const scaledHeight = img.height * scale;
const xOffset = (A4_WIDTH - scaledWidth) / 2;
const yOffset = (A4_HEIGHT - scaledHeight) / 2;
const page = pdfDoc.addPage([595.28, 841.89]);
page.drawImage(img, {
x: xOffset,
y: yOffset,
width: scaledWidth,
height: scaledHeight,
});
canvas.remove();
await new Promise((resolve) => setTimeout(resolve, 100)); // 防止卡死
}
const pdfBytes = await pdfDoc.save();
const blob = new Blob([pdfBytes], {
type: "application/octet-stream",
});
FileSaver.saveAs(blob, `导出的PDF.pdf`);
uni.hideLoading();
this.loadText = "文件生成成功!";
},
domtoimage+pdf-lib
async downloadPDF() {
this.loadText = "文件生成中...";
// 创建一个新的 PDF 文档
const pdfDoc = await PDFDocument.create();
// 处理需转pdf的dom id
const pdfDoms = await this.handlePDFPageDom();
let pdfPage = [];
let base64Arr = [];
for (let i = 0; i < pdfDoms.length; i++) {
const element = document.getElementById(pdfDoms[i]);
const url = await domtoimage.toPng(element, {
quality: 0.95,
skipFonts: true,
});
base64Arr.push({ base64: url });
}
await base64Arr.map((item, index) => {
pdfDoc.addPage([595.28, 841.89]);
pdfPage.push(this.handleReportView(item.base64, index, pdfDoc));
});
await Promise.all(pdfPage)
.then(async (res) => {
// 将 PDF 文档保存为 Uint8Array
const pdfBytes = await pdfDoc.save();
// 生成下载链接并自动下载 PDF
const blob = new Blob([pdfBytes], { type: "application/pdf" });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = `${this.studentName}.pdf`;
link.click();
URL.revokeObjectURL(link.href);
uni.hideLoading();
this.loadText = "文件生成成功!";
setTimeout(() => {
window.parent.postMessage({
cmd: "success",
});
}, 1000);
})
.catch((err) => {
// PDF = null;
console.log("生成失败", err);
});
},
async handleReportView(imgBase64, index, pdfDoc) {
const A4_WIDTH = 595.28; // A4 宽度
const A4_HEIGHT = 841.89; // A4 高度
// 获取所有页面
const pages = pdfDoc.getPages();
// 修改第index页(索引从0开始)
const pageNow = pages[index];
return await new Promise(async (resolve, reject) => {
const pageData = imgBase64;
// setTimeout(() => {
let img = new Image();
img.crossOrigin = "Anonymous";
img.onload = async () => {
const imgBytes = await fetch(pageData).then((res) =>
res.arrayBuffer()
);
// 嵌入 PNG 图片
const pngImage = await pdfDoc.embedPng(imgBytes);
const { width: imgWidth, height: imgHeight } = img;
// 计算缩放比例,确保图片适应 A4 页面并保持宽高比
const scale = Math.min(A4_WIDTH / imgWidth, A4_HEIGHT / imgHeight);
const scaledWidth = imgWidth * scale;
const scaledHeight = imgHeight * scale;
img.width = scaledWidth;
img.height = scaledHeight;
// 计算图片的偏移量,使其居中显示在页面上
const xOffset = (A4_WIDTH - scaledWidth) / 2;
const yOffset = (A4_HEIGHT - scaledHeight) / 2;
// 将内容设置到第几页
await pageNow.drawImage(pngImage, {
x: xOffset,
y: yOffset,
width: scaledWidth,
height: scaledHeight,
});
resolve();
};
img.onerror = () => {
alert("资源加载失败");
resolve();
};
img.src = pageData;
// }, 500);
}).catch((err) => {
return Promise.resolve();
});
},