【PDF】html/dom生成pdf

news/2024/11/17 2:30:12/

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", // 单位,本示例为mmformat: "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 === 0return 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.ppmy.cn/news/886282.html

相关文章

索尼影像及传感解决方案业务第一财季营收146亿元 同比增14%

【TechWeb】7月31日消息&#xff0c;索尼公司昨日发布了2019财年第一财季&#xff08;2019年4月1日-2019年6月30日&#xff09;财报。财报显示&#xff0c;索尼影像及传感解决方案业务第一财季营收达2,307亿日元&#xff08;约合人民币146亿元&#xff09;&#xff0c;同比增长…

索尼等惊爆在华巨额亏损 实为做账避税

听起来简直令人难以置信:无论是三星、LG,还是索尼、夏普 , 无论是冰箱、空调,还是彩电、数码相机,任是名声显赫、市场份额无限扩张 ,它们在中国的销售业绩却至今是 “赔钱的吆喝 ,不赚钱的买卖 ”。 洋品牌在华长期亏损,不仅是上世纪的事实,也是今天的事实。当然也有专家出来质…

【历史上的今天】7 月 24 日:Caldera 诉微软案;AMD 宣布收购 ATI;谷歌推出 Chromecast

整理 | 王启隆 透过「历史上的今天」&#xff0c;从过去看未来&#xff0c;从现在亦可以改变未来。 今天是 2022 年 7 月 24 日&#xff0c;在 1951 年的今天&#xff0c;晶体管发明家 John Bardeen 通知 AT&T 贝尔实验室&#xff0c;他将离开公司&#xff0c;与 Walter B…

索尼第三财季净利润超预期 股价暴涨16%

索尼 北京时间2月1日消息&#xff0c;据外媒报道&#xff0c;在电影、音乐以及游戏业务强劲表现的推动下&#xff0c;索尼第三财季净利润超出预期&#xff0c;推动周一股价创下一年来的最大涨幅。 截至东京时间周一9:26分(北京时间8:26分)&#xff0c;索尼股价在东京交易所盘中…

索尼战略转型:合作中求发展

自2007年11月&#xff0c;日本索尼公司开始有计划的战略转型&#xff0c;以放弃半导体业务换取蓝光DVD的一枝独大&#xff0c;加大与三星在第八代液晶面板的合作&#xff0c;参股夏普第十代液晶面板工厂&#xff0c;投入巨资开发OLED面板技术开发&#xff0c;半年的业务调整&am…

微软发布 2023 财年一季报:营收501亿美元,同比增11%!

大家好&#xff01;我是韩老师。 今天&#xff0c;微软发布了 2023 财年第一财季财报。 &#xff08;注&#xff1a;微软的 2023 财年指的是 2022 年 7 月 - 2023 年 6 月&#xff09; 财报显示&#xff0c;微软第一财季营收为 501.22 亿美元&#xff0c;同比增长 11%&#xff…

汤晓鸥徐立等自愿锁定商汤股票2年/ 索尼新动捕全套48g/ 百度欲扩大RoboTaxi运营范围…今日更多新鲜事在此...

日报君 发自 凹非寺量子位 | 公众号 QbitAI 大家好&#xff0c;又到周三&#xff01; 这两天寒潮来袭&#xff0c;注意保暖哇&#xff01; 今天科技圈都在关注什么新鲜事&#xff1f;一起来看点热乎的。 神舟十五发射成功&#xff0c;6名航天员已在空间站会师 神舟十五号载人飞…

索尼第一财季净利润2.05亿美元 同比锐减74%

据国外媒体报道&#xff0c;索尼今日发布了最新财季财报&#xff0c;公司第一财季净利润为212亿日元&#xff08;约合2.05亿美元&#xff09;&#xff0c;同比锐减74%。这主要是因为日元汇率走强和智能手机销量下滑对公司利润造成了较大的负面影响&#xff0c;日本西南地区发生…