前端导出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();
      });
    },
相关推荐
Acro6 分钟前
快给macOS 上的Grok Bot换一套皮肤
前端
flash俊杰8 分钟前
双引擎 OCR 编排:逐页判定、视觉优先、partial 语义与三层超时
前端
计算机魔术师9 分钟前
Reddit 每周被 Google 偷走 100 万次点击,CEO 摊牌了
前端
前端繁华如梦10 分钟前
用 React Three Fiber 做一栋能编辑的教学楼数字孪生
前端
前端繁华如梦15 分钟前
用 React Three Fiber 做一套智慧医院数字孪生:编辑器、大屏和 BIM 浏览器
前端
俊昭喜喜里2 小时前
理解全局跨域CORS配置
前端
hu55667984 小时前
如何使用Adobe Acrobat Pro DC将1页PDF切割为2页,A3变A4打印。
pdf
ComPDFKit4 小时前
PDF转Word后,每行都成一段?先分清断段、文本框和扫描页
pdf·word
锋行天下4 小时前
DeepSeek写Langgraph就是个大傻逼,一本正经胡说八道
前端·人工智能·后端
jktantan5 小时前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript