前端导出PDF(适配ios Safari浏览器)

目前市面上常用的前端导出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();
      });
    },
相关推荐
2601_962885725 分钟前
AlphaFeed 支持哪些 K 线周期?period参数怎么传?
前端·数据库·python
企业数字化笔记18 分钟前
AI工具的文件和参数怎么设计?上传校验、配置版本与可复现任务
前端·人工智能
铁皮饭盒26 分钟前
还是网页端, 46mb模型, 抠图功能升级了, 抠任意主体, 还是不要显卡, 不要python, 满意吗?
前端·javascript·后端
怕浪猫27 分钟前
Agent 怎么做规划?这道面试题淘汰了 80% 的候选人
前端·面试·agent
YQteamdyq1 小时前
为什么 { ...state } 会悄悄杀掉响应式?yq-sanyi v0.4.2 的三处修复
前端·javascript
零基础1231 小时前
PDF 处理工具全攻略:pdf24、PDFgear、Adobe Acrobat DC 与 Stirling-PDF 深度对比
java·开发语言·pdf
Java后端的Ai之路1 小时前
01-React基础教程
开发语言·前端·python·react.js·前端框架
颜进强1 小时前
25 · NestJs DurableProviders 持久化 Provider:把 ContextId 当缓存键
前端·后端·ai编程
爱丶不疚1 小时前
Electron: Sentry 都采集了什么?你知道吗
前端·electron
恋猫de小郭1 小时前
Dart 4.0 要彻底移除 dart:mirrors,Augmentations 应该要来了
android·前端·flutter