目前市面上常用的前端導出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 idconst 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 文檔保存為 Uint8Arrayconst pdfBytes = await pdfDoc.save();// 生成下載鏈接并自動下載 PDFconst 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();});},