【PDF】html/dom生成pdf

news2024/11/26 22:33:13

1、简要描述

上一篇博客主要讲的是pdf文件转换成canvas,然后进行相关的操作。本篇博客主要讲html中dom如何生成pdf文件(前端生成pdf),后端生成pdf当然也可以,原理也是将html网页通过后端服务导出成pdf,不深入讲,这里着重讲前端生成pdf。

2、相关插件及知识

还是使用的老朋友html2canvas和jspdf插件

1、jspdf

"jspdf": "^2.5.1"

 使用方法:

import JsPDF from 'jspdf';

const PDF = new jsPDF({
  unit: "mm", // 单位,本示例为mm
  format: "a4", // 页面大小
  orientation: "portrait", // 页面方向,portrait: 纵向,landscape: 横向
  putOnlyUsedFonts: true, // 只包含使用的字体
  compress: true, // 压缩文档
  precision: 16, // 浮点数的精度
});

// 或者

const PDF = new JsPDF('p', 'mm', [210, 297]);

<!-- 常用方法 -->
// 添加图片
PDF.addImage(
  imageData, // 此值可以为下面这些类型 string | HTMLImageElement | HTMLCanvasElement | Uint8Array | RGBAData
  'JPEG', // 转换后的格式
  x, // 被切割的imageData的横坐标
  y, // 被切割的imageData的纵坐标
  w, // 当前图片的宽度
  h, // 当前图片的高度
);
// 添加新的一页
PDF.addPage();
// 输出格式
PDF.output(type: "arraybuffer"): ArrayBuffer;
PDF.output(type: "blob"): Blob;
PDF.output(type: "bloburi" | "bloburl"): URL;
// 本地保存为pdf文件
PDF.save('lindadayo.pdf')

2、html2canvas

"html2canvas": "^1.4.1"
// 实例方法   
html2canvas(dom, config).then(function(canvas) {})

 config相关配置参考下图:

3、源码

1、dom结构

 

2、核心逻辑

这里为什么要将dom进行分区处理呢?请看第四点疑难解答

    /**
     * 生成pdf
     * @param CommonPage 需要转换的dom节点
     * @param i 分区索引
     * @returns
     */
    async generatePdf(CommonPage?: Element, childLen?: number) {
      PDF = new JsPDF('p', 'mm', [210, 297]); // pdf实例
      for (let i = 0; i < childLen; i++) {
        await asyncSingleAreaControl(CommonPage, i)
      }
      generateUploadPdf();
    },
    /**
     * 上传pdf文件
     */
    async generateUploadPdf() {
      // 文件重命名
      const pdfName = pdfNameHandle()
      const uri = PDF.output('blob')
      const file = await blobUriToFile(uri, pdfName)
      // 此时的file是File类对象,你可以选择上传到服务器噢~当然你也可以选择直接导出到前端
      // PDF.output('lindadayo.pdf');
    },
    /**
     * 单个分区生成pdf操作
     * @param CommonPage 父节点dom
     * @param i 分区索引
     * @returns
     */
    async asyncSingleAreaControl(CommonPage, i) {
      const canvas = await singleHandle(CommonPage, i)
      await areaPage(canvas, i)
    },
    /**
     * 分区pdf处理
     * @param canvas 各个分区dom转换后的canvas
     * @param areaNo 分区索引
     */
    areaPage(canvas, areaNo) {
      // 是否是第一个分区(作用于是否开始就addPage)
      const isFirstArea = areaNo === 0
      return new Promise((resolve, _reject) => {
        // a4纸宽高
        const A4Origin = {
          width: PDF.internal.pageSize.getWidth(),
          height: PDF.internal.pageSize.getHeight()
        }
        const contentWidth = canvas.width;
        /**
         * html2canvas放大3.125倍时精度丢失导致多了2像素
         * 3368: 高度285mm纸张html2canvas放大300dpi后像素
         * 3366:正常实际高度
         */
        const contentHeight = canvas.height <= 3368 ? 3366 : canvas.height;
        const pageHeight = Math.round(contentWidth / A4Origin.width * A4Origin.height);
        let leftHeight = contentHeight;
        let position = 0;
        const imgWidth = A4Origin.width;
        const imgHeight = Math.ceil(A4Origin.width / contentWidth * contentHeight);
        const pageData = canvas.toDataURL('image/jpeg', 1);
        // 非首个分区,得先addPage,因为不然会少一页 && 大于某个范围才新增一页,避免因为浮点数计算精度造成多增一页
        if (!isFirstArea && leftHeight > 0) {
          PDF.addPage()
        }
        while (leftHeight > 0) {
          PDF.addImage(pageData, 'JPEG', 0, position, imgWidth + (isBrower() ? 0.62 : 0), imgHeight + (isBrower() ? 0.32 : 0));
          position -= A4Origin.height;
          leftHeight -= pageHeight
          // 大于某个范围才新增一页,避免因为浮点数计算精度造成多增一页
          if (leftHeight > 0) {
            PDF.addPage()
          }
        }
        resolve(true)
      })
    },
    /**
     * 单页pdf处理
    //  * @param root 总节点
     * @param index 分区索引
     */
    async singleHandle(CommonPage, index) {
      // 报错Unable to find element in cloned iframe解决方法
      // getDiv在外部声明, 内部赋值
      try {
        getDiv = CommonPage.querySelector(`#CommonPageItemArea-${index}`)
        const res = await html2canvas(getDiv, {
          useCORS: true,
          allowTaint: true,
          scale: 3.125
        }).then(function(canvas) {
          return canvas
        })
        return res
      } catch (e) {
        console.log(e)
      }
    }

4、疑难解答

1、为什么要对dom进行分区操作?

其实如果你不使用html2canvas的参数scale,就没必要进行分区,但是在很多时候,你不放大canvas的话,会导致pdf中的图片很模糊,还有锯齿,所以要对canvas进行方法,但是放大后,会导致一些问题:生成pdf后,超过15000px以后的dom会有样式丢失,所以得对dom进行分区操作,让每个分区的dom高度 * 放大倍数不超过15000px。我们一般都会导出a4纸大小,a4纸宽高是210mm*297mm,换算成像素是793.29px * 1122.52px,如果你选择放大两倍,那么,单页高度就是2245px,结论为一个分区能够放六个a4纸高度的dom,所以你在开发页面时,就要做好这种页面结构噢~

2、html2canvas仍然报图片出错/跨域的问题,即使后端oss已经解决跨域了

这个涉及知识点:img标签获取属于非跨域操作,Image类实例化属于跨域操作,所以得再html2canvas依赖中打补丁

/dist/html2canvas.js

 

3、报错Unable to find element in cloned iframe解决方法

在分区中循环处理dom生成canvas时会报出这种错误,原因是html2canvas第一参数的变量应该设置为全局变量而不应该是局部变量

    try {
        getDiv = CommonPage.querySelector(`#CommonPageItemArea-${index}`)
        const res = await html2canvas(getDiv, {
          useCORS: true,
          allowTaint: true,
          scale: 3.125
        }).then(function(canvas) {
          return canvas
        })
        return res
      } catch (e) {
        console.log(e)
      }

--- 有问题可以随时评论噢~ ---

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

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

相关文章

LaTex使用bib文件引用参考文献(最简单有效!!!)

引言 在写文章&#xff0c;尤其是科技类文章的时候&#xff0c;需要引用大量参考文献。如果按照本身给的模板一个个填入是十分浪费时间的&#xff0c;并且后面引用格式或者顺序有改变的话改动很大。所以我一般习惯于用Bib文件直接导入&#xff0c;接下来就是介绍使用Bib文件导…

密码学学习笔记(十一):压缩函数 - Davies–Meyer结构

密码学中压缩函数是指将输入的任意长度消息压缩为固定长度输出的函数。压缩函数以两个特定长度的数据为输入&#xff0c;产生与其中一个输入大小相同的输出。简单来说就是它接受一些较长的数据&#xff0c;输出更短的数据。 压缩函数接收长度为X和Y的两个不同输入&#xff0c;并…

如何退出telnet

例如&#xff0c;我已经成功telnet到服务器了&#xff0c;可以输入信息&#xff1a; 此时&#xff0c;同时按 ctrl 和 ] 键&#xff0c;可以到telnet提示符窗口&#xff0c;如下&#xff1a; 在telent提示符窗口&#xff0c;输入quit&#xff0c;然后回车&#xff0c;就回到了…

性能测试的一二三

目录 前言&#xff1a; 性能测试流程 性能工具 如何选择性能测试工具 性能测试需要掌握哪些技能 总结 前言&#xff1a; 性能测试是评估系统在不同负载条件下的性能和稳定性的过程。 在我们日常生活和工作中&#xff0c;软件已经成为不可或缺的一部分。然而&#xff0c;…

高并发的哲学原理(五)-- 拆分网络单点(上):应用网关、负载均衡和路由器(网关)

上一篇文章的末尾&#xff0c;我们提到了一个假想出来的五万 QPS 的系统&#xff0c;以及这种规模的系统架构中必然存在的负载均衡器&#xff0c;那本篇文章我们就来一起利用负载均衡搭建一个能够支撑五万 QPS 的系统。 “监听 HTTPS 443 端口的进程”这个单点 之前&#xff0…

Linux DMA 简介

文章目录 1. 前言2. 背景3. DMA 硬件基础3.1 什么是 DMA&#xff1f;3.2 为什么需要 DMA&#xff1f;3.3 DMA 传送模式3.4 DMA 常见硬件拓扑3.4.1 ARM 架构常见 DMA 硬件拓扑3.4.2 其它架构 DMA 硬件拓扑 4. Linux 下的 DMA4.1 DMA 内存地址和区域4.1.1 DMA 内存涉及的3种地址4…

Matplotlib是什么

Matplotlib 是一款用于数据可视化的 Python 软件包&#xff0c;支持跨平台运行&#xff0c;它能够根据 NumPy ndarray 数组来绘制 2D 图像&#xff0c;它使用简单、代码清晰易懂&#xff0c;深受广大技术爱好者喜爱。 NumPy 是 Python 科学计算的软件包&#xff0c;ndarray 则…

NAT转换网关实现IP地址转换,保障数据采集

NAT转换网关&#xff08;Network Address Translation Gateway&#xff09;是物通博联推出的一款物联网设备&#xff0c;用于在不同网络之间进行网络地址转换&#xff08;Network Address Translation&#xff09;&#xff0c;以实现IP地址的转换和映射。 物通博联NAT转换网关…

再谈操作系统

文章目录 ⭐1. 什么是操作系统&#x1f31f;2. 为什么要有操作系统&#x1f320;3. 操作系统如何管理 ⭐1. 什么是操作系统 先入为主&#xff0c;操作系统是一款管理软件 操作系统分为两部分 操作系统本身&#xff0c;主要做一些进程管理、内存管理、文件管理、驱动管理等工…

【AGC】删除控制台应用重建报错url is null问题

【关键字】 AGC、agconnect-services.json、InvalidParameterException 【问题描述】 有开发者反馈在删除了AppGallery Connect中某个应用及其对应的项目&#xff0c;再重新创建相同的应用&#xff08;包名一致&#xff09;&#xff0c;并且重新下载agconnect-services.json到…

【c++修行之路】IO流架构及使用

文章目录 前言输入输出库文件读写序列化与反序列化结语 前言 大家好久不见&#xff0c;今天一起来学习c中的IO流。 输入输出库 这两张架构图略显复杂&#xff0c;这里给出一张比较清楚的IO流架构图&#xff1a; 也就是说&#xff0c;我们平时使用的诸如cin、cout、cerr、cl…

2023华为产品测评官-开发者之声 | 华为云CodeArts征文活动,多重好礼邀您发声!

"2023华为产品测评官&#xff0d;开发者之声"活动激发了众多开发者和技术爱好者的热情&#xff0c;他们纷纷递交了精心编写的产品测评报告。活动社群充满活力&#xff0c;参与者们热衷于交流讨论&#xff0c;互相帮助解决问题&#xff0c;一起探索云技术的无限可能。…

C# Nullable学习

在C#1.x&#xff0c;一个值类型变量是不可以被赋予null值的&#xff0c;否则会产生异常。 在C#2.0中&#xff0c;提供了Nullable类型&#xff0c;允许用它定义包含null值&#xff08;即空值&#xff09;的数据类型&#xff0c;这对处理数据库中包含可选字段以及很多方面都有帮…

【云原生】Docker网络Overlay搭建Consul实现跨主机通信

目录 1.overlay网络是什么&#xff1f; 实现overlay环境 1.overlay网络是什么&#xff1f; 在Docker中&#xff0c;Overlay网络是一种容器网络驱动程序&#xff0c;它允许在多个Docker主机上创建一个虚拟网络&#xff0c;使得容器可以通过这个网络相互通信。 Overlay网络使用…

linux - bc 命令安装

一.引言 迁移新机器后发现没有 bc 命令&#xff0c;之前 shell 脚本的一些计算逻辑会出错&#xff0c;下面快速安装一下。 二.安装 bc 请确保在 root 权限下执行该命令&#xff1a; sudo yum install bc 出现下述界面即安装成功&#xff1a; 三.测试 bc 测试下脚本里 % 的…

Nautilus Chain 更换全新测试网,主网即将在不久上线

目前&#xff0c;Nautilus Chain 正在为主网上线前的最后阶段做准备&#xff0c;据悉该链更新了全新的测试网&#xff0c;在此前版本的测试网的基础上进行了全新的技术升级&#xff0c;最新测试网版本与生态发展的技术规划更为贴近。本次测试网升级将会是最后一次测试网版本的迭…

ylb-接口8手机号注册

总览&#xff1a; 在web模块下的service包&#xff0c;补充短信接口&#xff08;SmsService&#xff09;&#xff1a;检查用户发送的验证码是否正确 package com.bjpowernode.front.service;public interface SmsService {/*** param phone 手机号* return true&#xff1a;发…

聚集十二罗汉,探索宇宙本质,马斯克神秘的xAI

作者 | 德新编辑 | 王博 马斯克组团入局通用人工智能。 7月12日&#xff0c;马斯克发推官宣成立新的公司xAI。据官网介绍&#xff0c;这是一家试图「探索理解宇宙本质」的公司。 新公司公布了12名首批成员&#xff0c;除了马斯克外&#xff0c;他们曾经在「AlphaStar、AlphaCod…

springboot线上买菜系统

开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven…

【Linux进阶之路】冯诺依曼体系结构与操作系统

前言 存储器与CPU 在正式介绍今天的话题之前&#xff0c;博主先来带大家搞清楚一些硬件的概念&#xff0c;方便接下来的理解。 存储器&#xff0c;顾名思义就是用来存储的设备&#xff0c;那在计算机里&#xff0c;存储器包含什么呢&#xff1f; 答&#xff1a;内存 外存&a…