前端导出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 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();});},

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/diannao/86832.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

physicsnemo开源程序是开源深度学习框架,用于使用最先进的 Physics-ML 方法构建、训练和微调深度学习模型

​一、软件介绍 文末提供程序和源码下载 NVIDIA PhysicsNeMo 是一个开源深度学习框架&#xff0c;用于使用最先进的 SciML 方法构建、训练、微调和推理物理 AI 模型&#xff0c;以实现 AI4 科学和工程。PhysicsNeMo 提供 python 模块来构建可扩展和优化的训练和推理管道&#…

JDBC接口开发指南

1.简介 JDBC&#xff08;Java Data Base Connectivity,java数据库连接&#xff09;是一种用于执行SQL语句的Java API&#xff0c;可以为多种关系数据库提供统一访问&#xff0c;它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准&#xff0c;据此可以构建更高级的工具…

Shell 脚本:系统管理与任务自动化的利器

在开发者忙碌的日常工作中&#xff0c;效率就是生命线。当面对大量重复、繁琐的系统管理任务与开发流程时&#xff0c;一款得力的编程工具能让工作事半功倍。Shell 脚本&#xff0c;这把在 Linux 和 Unix 系统环境下闪耀着光芒的利器&#xff0c;凭借其强大的自动化能力&#x…

关于mybatis插入大批量数据效率问题

一、即便分批次用mybatis插入数据&#xff0c;效率依旧不高&#xff0c;原因&#xff1a; MyBatis一次性批量插入几千条数据&#xff0c;为什么性能很差&#xff1f;-腾讯云开发者社区-腾讯云 文中提出&#xff1a; 默认执行器类型为Simple&#xff0c;会为每个语句创建一个新…

在 JavaScript中编写 Appium 测试(入门)

1.编写一个测试 (JS) 要在 JavaScript&#xff08;Node.js&#xff09;中编写 Appium 测试&#xff0c;我们需要选择一个与 Appium 兼容的客户端 库。维护最好的库和 Appium 团队推荐使用的库是 WebdriverIO, 所有就让我们使用它吧。既然我们已经安装了 Appium&#xff0c;我们…

【android bluetooth 框架分析 04】【bt-framework 层详解 6】【Properties介绍】

DeviceProperties、AdapterProperties、StorageModule、以及 bt_config.conf 是 AOSP Bluetooth 栈中 设备属性管理与持久化系统 的核心组成部分&#xff0c;它们之间关系紧密&#xff0c;但职责各有不同。 下面我将依次讲解它们的区别与联系. 注意: 在代码里面 还有 Blueto…

@Resource vs @Autowired 在Spring中的使用和区别

Resource vs Autowired 在Spring中的使用和区别 在Spring开发中&#xff0c;我们常会接触两个用于实现引用模块注入的注解&#xff1a;Resource 和 Autowired。它们在使用上有些相似之处&#xff0c;但本质上来看&#xff0c;有所区别。本文将给出两者的详细介绍和对比&#x…

Mac M4 芯片运行大模型指南,包括模型微调与推理

Mac M4 芯片运行大模型指南&#xff0c;模型微调与推理 背景模型推理 Ollama&#x1f50d; 举例说明&#xff1a;踩坑 模型微调 unsloth 背景 在国补、教育优惠、京东会员500优惠券等众多优惠之下。 我拿下了Macmini M4 16G 内存万兆网卡。在机器到手的第一时间&#xff0c;马…

微信小程序中安装vant

以下是微信小程序中安装 Vant 的详细步骤&#xff1a; 1. 初始化项目 在微信小程序项目目录下&#xff0c;打开终端&#xff0c;执行以下命令进行项目初始化&#xff1a; npm init -y该命令会快速生成一个默认的package.json文件&#xff0c;-y参数表示直接使用默认配置&…

今天做的力扣SQL

我本地markdown的东西直接复制出来了。 多说一嘴&#xff0c;今天早上六点醒了&#xff0c;然后被外面吵&#xff0c;心里也担心找实习就一直睡不着了。索性直接来实验室&#xff0c;这一上午感觉好快啊。幸运的是&#xff0c;自己也没有浪费时间&#xff0c;还行吧。SQL欠的账…

【开发常用命令】:docker常用命令

docker常用命令 基础命令 # 启动docker systemctl start docker # 关闭docker systemctl stop docker # 重启docker systemctl restart docker # 设置开机自启动 systemctl enable docker # 查看docker运行状态 systemctl status docker # 查看docker版本号信息 docker versi…

安装配置以太链钱包工具

安装go语言环境 1、官网下载go安装包并上传到指定机器 https://golang.google.cn/dl/ 2、解压缩至指定位置&#xff1a; tar -C /usr/local -xzf go1.21.0.linux-amd64.tar.gz 3、将 /usr/local/go/bin 目录添加至 PATH 环境变量&#xff1a; export PATH$PATH:/usr/local/g…

论文阅读:speculative decoding

Fast Inference from Transformers via Speculative Decoding 论文地址&#xff1a;https://arxiv.org/pdf/2211.17192 speculative sampling 为了从分布 p ( x ) p(x) p(x) 中采样&#xff0c;我们实际上是从分布 q ( x ) q(x) q(x) 中采样 x x x&#xff0c;如果 q ( …

java操作word里的表格

依赖&#xff1a; <dependency><groupId>com.techCoLtd</groupId><artifactId>aspose-words-16.4.0-jdk16</artifactId><classifier>jdk16</classifier> </dependency>/*** 删除表格及表格的行* throws Exception*/ private s…

单链表经典算法题之分割链表

给定一个头结点和一个值x&#xff0c;是链表中所有小于x的值都在x前面 typedef struct ListNode ListNode; struct ListNode* partition(struct ListNode* head, int x) { //思路一&#xff1a;在原链表上进行修改 //思路二&#xff1a;创建新链表&#xff0c;使用哨兵位&…

Modbus TCP转DeviceNet网关连接ABB变频器配置案例

某工厂需要将支持Modbus TCP协议的上位机控制系统&#xff08;如PLC或SCADA&#xff09;与支持DeviceNet协议的变频器&#xff08;如ABB ACS880、施耐德ATV320等&#xff09;进行通信。为实现协议转换&#xff0c;采用开疆智能Modbus TCP转DeviceNet网关KJ-DVCZ-MTCPS作为中间设…

【力扣 简单 C++】206. 反转链表

目录 题目 解法一&#xff1a;迭代 解法二&#xff1a;递归 题目 待添加 解法一&#xff1a;迭代 class Solution { private:ListNode* reverse(ListNode* head){ListNode* newHead {};while (head){ListNode* nextNode {head->next};head->next newHead;newHead …

计算机视觉之三维重建(深入浅出SfM与SLAM核心算法)—— 1. 摄像机几何

文章目录 1. 针孔相机1.1. 针孔成像1.2. 光圈对成像的影响 2. 透视投影相机2.1. 透镜成像2.2. 失焦2.3. 径向畸变2.4. 透视投影的性质 3. 世界坐标系到像素坐标系的变换4. 其它相机模型4.1. 弱透视投影摄像机4.2. 正交投影摄像机4.3. 各种摄像机模型的应用场合 课程视频链接&am…

第十三节:第七部分:Stream流的中间方法、Stream流的终结方法

Stream流常见的中间方法 Stream流常见的终结方法 代码 学生类&#xff08;代码一与代码二共涉及到的类&#xff09; package com.itheima.day28_Stream;import java.util.Objects;public class Student implements Comparable<Student> {private String name;private i…

深入理解 Go 中的字节序(Endianness)检测代码

深入理解 Go 中的字节序&#xff08;大小端&#xff09;检测代码 在计算机系统中&#xff0c;字节序&#xff08;Endianness&#xff09; 是指多字节数据类型&#xff08;如 int16、int32 等&#xff09;在内存中的存储顺序。Go 语言标准库提供了对大端&#xff08;Big-endian&…