【实战】通过 JS 将 HTML 导出为 PDF 文档

2023-11-07

背景介绍

某老人院信息管理系统项目,甲方要求将财务模块的各种报表导出为PDF文档,方便打印。

之前的解决方案,是将报表生成专门的打印 HTML 页面,然后按 Ctrl+P 调用浏览器本身打印功能去打印。 这种方式存在的问题是不同分辨率的显示器,页面效果不一,需要专门设定打印尺寸,使用起来不够方便,功能上线后,一直遭到甲方吐槽...

轮子工具选择

目标很明确明确,将 HTML 内容导出为PDF。 时间有限,先找轮子,一通谷歌后选定了前端工具 jspdf。具体使用方式比较简单,参考下列两个链接:

github.com/linwalker/r…
github.com/MrRio/jsPDF

解决方案解析

先上代码:

html2canvas(document.body, {
  onrendered:function(canvas) {
    // 要输出的 PDF 每页的宽高尺寸,单位是 pt
    let pageWidth = 841.89
    let pageHeight = 592.28

    // 要打印内容,转换成 canvas 图片后的宽高尺寸
    let contentWidth =  canvas.width*3/4
    let contentHeight = canvas.height*3/4

    // 将要打印内容的图片,等比例缩放至宽度等于输出时 PDF 每页的宽度,此时的图片宽
    let imgWidth = pageWidth
    // 将要打印内容的图片,等比例缩放至宽度等于输出时 PDF 每页的宽度,此时的图片高
    let imgHeight = pageWidth / contentWidth * contentHeight

    // 起始内容截取位置
    let position = 0
    // 剩余未打印内容的高度
    let leftHeight = imgHeight

    // 获取打印内容 canvas 图片元素
    let pageData = canvas.toDataURL('image/jpeg', 1.0)
    
    // 初始化 pdf 容器,三个参数分别是:纸张方向(填'',则是横向)、打印单位、纸张尺寸
    let PDF = new JsPDF('landscape', 'pt', 'a4')
    
    // 循环截取打印内容并添加进容器
    if (leftHeight < pageHeight) {
      PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
    } else {
      while (leftHeight > 0) {
        PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
        leftHeight -= pageHeight
        position -= pageHeight
        if (leftHeight > 0) {
          PDF.addPage()
        }
      }
    }
    
    // 将容器中的内容输出为 PDF 文档
    pdf.save('content.pdf');
  }
})
复制代码

导出 pdf 的函数我参考了这条github链接,做了部分修改。函数逻辑比较简单,不做过多解释。主要提两点:

  1. 修复了一个小bug,原函数忽略了单位转换问题(px 要转 pt),存在导出的 PDF 最后会有空白页。
  2. 原函数中 leftHeight 用的是 contentHeight,也就是 canvas 图片的未缩放换算前的高。这就导致 pageHeight 需要再换算才能得到,这增加了函数逻辑复杂度。其实 leftHeight 可以设为 imgHeight,即缩放换算后的高,而pageHeight 就设为 PDF 单页的高,这样代码逻辑更清晰。

这函数核心逻辑就三步:

  1. 获取要打印内容区域的宽高,并等比缩放直至其宽度等于输出 PDF 的页面的宽度,以此获得缩放后的打印内容图片宽高(imgWidth, imageHeight)
  2. 按单页 PDF 的宽高 (pageWidth, pageHeight),循环截取缩放后的打印内容图片,并将每次截取的内容添加至 PDF 对象容器。(每截取一次,就是一页 pdf)
  3. 将 PDF 对象容器中的内容,输出为 PDF 文档。

问题与补救思路

实践中遇到的问题是竖直方向上图片被随机截断,如下图示:

针对这个项目的业务场景,我采取的补救方案是“设定打印内容高度”。 具体思路如下:

  1. 确定输出单页纸张的尺寸比例
    例如: A4纸宽高比 = 841.89 / 592.28 (横向);
  2. 保持比例不变,通过简单换算确定单个打印的页面宽高
    例如:页面宽 1920px ,高 1360px;
  3. 通过 CSS,精确控制打印页面中各元素的高度,使得超出单页高度的内容,合理过度。
    例如:我想每页打印表格不超过 34 行,那么单行高度就应该设定为 1360/34 = 40px;
    第一页由于有标题、表头等元素,所以只打 30 行,标题和表头合计高度为 160px (这个可根据实际需求,只需保证标题、表头的高度都是单行高的整数倍即可)

最终成功解决竖直方向不规则截断问题。

总结

做的好的三个点:

  1. 快速寻找轮子,思路正确
  2. 知其所以然的态度,促使深入思考实现原理,因此才有可能优化解决方案,使得最终交付的结果更优质
  3. 实在想不到...

不足的两点:

  1. 整个任务完成花费了一个工作日,效率太低。在项目本地部署环节浪费很多时间(主要因为项目本身技术栈选型不好,也没有相应的部署说明文档)。另外,一开始未看懂函数就开始瞎改,浪费了不少时间。
  2. 功能没有合理封装,最终手动复制黏贴到所有页面,浪费大量时间。

进一步研究和思考:

  1. 研究 jspdf 源码,更进一步了解导出 PDF 的实现原理
  2. 研究通用打印页面方案,看能否将导出 PDF 文档功能封装为 vue 组件(先针对打印内容为表格的)
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【实战】通过 JS 将 HTML 导出为 PDF 文档 的相关文章

随机推荐

  • Transformer(四)--实现验证:transformer 机器翻译实践

    转载请注明出处 https blog csdn net nocml article details 125711025 本系列传送门 Transformer 一 论文翻译 Attention Is All You Need 中文版 Tran
  • HBase与MapReduce整合:TableMapper与TableReducer部分源码分析

    目录 关于TableMapper和TableReducer TableMapReduceUtil initTableMapperJob initTableReducerJob TableInputFormat TableRecordRead
  • 基于Hybris平台的电商个性化服务实践

    个性化服务是什么 下面例举几个典型的电商网站的个性化服务案例 对于浏览过新品推荐的客户 电商网站主动为此类客户推荐一款新上市的商品 对于单笔订单总金额达到1000 并且该订单中包含化妆品的客户 此用户将被升级为金牌客户 后续电商网站定期为金
  • RAID介绍及RAID5配置实例(超详细)

    一 RAID磁盘阵列介绍 磁盘阵列的全名 Redundant Arrays of Inexpensive Disk RAID 中文简称是独立冗余磁盘阵列 RAID可以通过技术 软件或者硬件 将多个独立的物理硬盘整合成为一个较大的硬盘组 逻辑
  • MATLAB实现基本的遗传算法(写成函数形式,可调用),优化目标函数,并举例展示

    遗传算法 其本质上是一种进化算法 相比其他的算法应用范围比较广泛 特别是对于一些非线性 多模型 多目标的函数优化问题 用其他的优化方法较难求解 而遗传算法可以方便的得到较好的结果 不过正如我在PSO粒子群算法的文章中说道 每种算法的应用场景
  • Ubuntu: 安装最新版本的 Nginx

    Ubuntu 默认 apt 源中的 Nginx 版本比较旧 今天介绍下如何在 Ubuntu 中安装最新版本的 Nginx 要安装较新版本的 Nginx 可以使用 Nginx 的 APT 源 执行如下脚本来添加 Nginx APT 源 bin
  • gitlab项目的备份与迁移

    gitlab项目的备份与迁移 最近工作中需要用到gitlab项目的备份与迁移工作 因此做了一个简单的部署配置 这里小小记录一下 一 gitlab的安装 1 安装准备 1 本人由于在centos7上部署gitlab 因此使用的gitlab的版
  • unity3d 启动项目卡在load script assembly

    最近突然碰到unity启动项目 卡在load script assembly的情况 删除了library重开也还是如此 日志打印里面打印到 刷新script开始然后就戛然而止了 后来发现是 项目里某个文件夹被一个命令行占用了 unity在导
  • 安装nrm报错

    internal validators js 124 throw new ERR INVALID ARG TYPE name string value TypeError ERR INVALID ARG TYPE The path argu
  • 2023华为OD机试真题【最接近中位数的索引】

    前言 本题使用Java解答 如果需要python版本答案 请查看以下链接 Python版本答案 题目内容 给定一个数组X和正整数K 请找出使表达式X i x i 1 X i K 1 结果最接近于数组中位数的下标i 如果有多个i满足条件 请返
  • [Java]两个有理数的加减乘除

    public class Rational private long num 0 分子 private long den 1 分母 Begin long i public Rational long num long den this nu
  • Blender 的 操作与快捷键

    Blender是一款开源的免费的3D建模软件 目前来看还不错哦 Blender的快捷键 鼠标右键选中物体 左键是确认 选中下 按住滑轮拖动鼠标可以环视 选中下 按住shift正反键可以上下 选中下 按住shift按住滑轮拖动鼠标可以左右 小
  • javaweb-HTTP协议(服务器数据的接收、处理、响应流程)详解

    HTTP协议 HTTP请求 两种HTTP请求方式 在服务器接收信息 HttpServletRequest 请求行 请求头 请求体 处理 响应 HTTP协议 什么是HTTP协议 超文本传输协议 Hypertext Transfer Proto
  • https请求跨域问题的解决

    原http服务升级为https服务后 发现同级域名下的单点登录不可用 报错 The page at https aaa xxx com was loaded over HTTPS but requested an insecure fram
  • CTFweb篇——upload-labs

    0x00 前言 本次以 upload labs的Pass 00和pass 01为例 0x01 进入靶场 pass 00 首先查看Pass 00题目 任务要求上传一个webshell到服务器 编写一句话木马 然后上传 右键复制图像地址 连接菜
  • Windows系统克隆***与防范

    随着电脑技术的发展和电脑的普及 还有大大小小的 骇客 网站和越来越简单的工具 使得目前 变得日趋频繁 被植入 的电脑或 服务器也越来越多 与此同时系统管理员的 安全意识也在不断提高 加上杀毒软件的发展 网络 的生命周期也越来越短 所以 者在
  • sql函数创建和调用

    create FUNCTION Fun GetDeptID 传入参数 或参数类型 UserID VARCHAR 100 IDepGrade INT 返回值类型 RETURNS VARCHAR 100 AS BEGIN 定义返回值 DECLA
  • 揭开“视频超分”黑科技的神秘面纱

    在看电影时 有一幕大家应该都非常熟悉 警察从证据图片中选取一块区域放大 再放大 直到一个很小的目标变得清晰可见 从而发现重要的线索 现实中是不是真的有这样的技术 可以把模糊的小图变得清晰 答案是 一定程度上可以 这项黑科技就是超分辨率技术
  • 【教程】PyTorch Timer计时器

    转载请注明出处 小锋学长生活大爆炸 xfxuezhang cn OpenCV的Timer计时器可以看这篇 Python Timer和TimerFPS计时工具类 Timer作用说明 统计某一段代码的运行耗时 直接上代码 开箱即用 import
  • 【实战】通过 JS 将 HTML 导出为 PDF 文档

    背景介绍 某老人院信息管理系统项目 甲方要求将财务模块的各种报表导出为PDF文档 方便打印 之前的解决方案 是将报表生成专门的打印 HTML 页面 然后按 Ctrl P 调用浏览器本身打印功能去打印 这种方式存在的问题是不同分辨率的显示器