vue通过html2canvas+jspdf生成PDF问题全解(水印,分页,截断,多页,黑屏,空白,附源码)

前端导出PDF的方法不多,常见的就是利用canvas画布渲染,再结合jspdf导出PDF文件,代码也不复杂,网上的代码基本都可以拿来即用。 如果不是特别追求完美的情况下,或者导出PDF内容单页的话,那么基本上也就满足业务需求了。但是,如果你需要导出PDF的内容又多又复杂呢?

目录

1.PDF常规导出 

2.问题1:水印

3.问题2:导出黑屏与空白

4.问题3:分页与截断

5.结束语


1.PDF常规导出 

        刚接触这个需求的时候,lz想的是能实现将当前网页内容导出PDF下载即可。因为这也符合常规业务需求逻辑,也没有考虑其他的,因此参考了几篇网上的博文,很快就选定了vue项目中利用html2canvas+jspdf来实现导出PDF。

        所以,很快就实现了。在此基础上,还满足了可以动态进行PDF分页dom节点的划分 ,并且觉得就这?就这?蜜汁自信(不自量力)的lz还马上写了一篇博客,下面附上博客地址,内附源码。

vue2利用html2canvas+jspdf动态生成多页PDF_html2pdf 多页-CSDN博客

 如果诸位的导出pdf内容很简单,那上面的博文应该大概或许能助尔等一臂之力。导出过程中有其他疑难杂症的咱们接着往下看。

2.问题1:水印

        给导出的PDF加上水印,这个需求很合理吧?毕竟版权和文件安全意识还是要有的。对于这个需求,想要解决也很简单。对应的依赖如watermark-dom,但是lz在用的时候发现这个水印一旦加上就甩都甩不掉,全局都附带上了,而且在导出的时候,PDF文件上竟然也没带上水印?wtf?

不信邪的可以试试,也有可能是项目的差异性呢,呵呵哒...

依赖安装

npm install watermark-dom --save

在你要用到水印的页面引入:

import watermark from "watermark-dom";export default {name:'PDF',data() {return {compony:"多页PDF导出"}},mounted() {//该水印依赖可用,但是导出文件后不会带上this.$nextTick(() => { var waterdom = document.getElementById('pdfinsurancepdf'); var height = waterdom.offsetHeight;console.log(waterdom,height)watermark.load({watermark_id: 'wm_div_id',watermark_parent_node:'pdfinsurancepdf',   //水印插件挂载的父元素element,不输入则默认挂在body上watermark_txt: "PDF导出",                  //水印的内容watermark_fontsize:'24px',                  //水印字体大小watermark_x_space:100,              //水印x轴间隔watermark_y_space:100,      })})},destroyed() {watermark.remove();},
}

 如果上面的方法不好用,你可以试试这个推荐方法:

通过js操作dom的方式,创建vue自定义指令,来动态的给dom元素加上水印。

优点创建后全局可用,精准性高,指哪打哪,而且导出的PDF精准的附带了水印,由于是通过js来实现的,可塑性强,可通过直接改js文件来二次调整适应需求变化

在untils目录下创建watermark.js文件:

const globalCanvas = null
const globalWaterMark = null// watermark 样式
let style = `
display: block;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-repeat: repeat;
pointer-events: none;`const getDataUrl = ({ font, fillStyle, textAlign, textBaseline, text, rotate = -20 }) => {font = font || '25px normal'fillStyle = fillStyle || 'rgba(180, 180, 180, 0.2)'text = text || ''const canvas = globalCanvas || document.createElement('canvas')const ctx = canvas.getContext('2d') // 获取画布上下文ctx.rotate((rotate * Math.PI) / 180)ctx.font = fontctx.fillStyle = fillStylectx.textAlign = textAlign || 'left'ctx.textBaseline = textBaseline || 'middle'ctx.fillText(text, canvas.width / 10, canvas.height / 2)return canvas.toDataURL('image/png', 1) // 第二个参数为质量
}const setWaterMark = (el, binding) => {//const parentElement = el.parentElementconst parentElement = el// 获取对应的 canvas 画布相关的 base64 urlconst url = getDataUrl(binding)// 创建 waterMark 父元素const waterMark = globalWaterMark || document.createElement('div')waterMark.className = 'water-mark' // 方便自定义展示结果style = `${style}background-image: url(${url});`waterMark.setAttribute('style', style)// 将对应图片的父容器作为定位元素parentElement.setAttribute('style', 'position: relative;')// 将图片元素移动到 waterMark 中parentElement.appendChild(waterMark)
}// 监听 DOM 变化
const createObserver = (el, binding) => {const waterMarkEl = el.parentElement.querySelector('.water-mark')const observer = new MutationObserver((mutationsList) => {if (mutationsList.length) {const { removedNodes, type, target } = mutationsList[0]const currStyle = waterMarkEl?waterMarkEl.getAttribute('style'):'';// 证明被删除了if (removedNodes[0] === waterMarkEl) {observer.disconnect()// 重新添加水印,dom监听init(el, { value: binding })} else if (type === 'attributes' && target === waterMarkEl && currStyle !== style) {waterMarkEl.setAttribute('style', style)}}})observer.observe(el.parentElement, {childList: true,attributes: true,subtree: true})
}// 初始化
const init = (el, binding) => {// 设置水印setWaterMark(el, binding.value)// 启动监控createObserver(el, binding.value)
}// 定义指令配置项
const directives = {inserted(el, binding) {init(el, binding)}
}export default {name: 'watermark',directives
}

main.js进行全局指令注入:

import waterMark from '@/utils/watermark.js'
Vue.directive('watermark', waterMark.directives)

 后面就可以在你需要导出PDF的dom上附带指令添加水印即可:

<div id="page1" v-watermark="{ text: '这是水印' }"><img src="@/assets/pdfhead.png" style="width:100%;height:auto;" alt="封面" />
</div>

3.问题2:导出黑屏与空白

        原因分析:关于这个问题,lz是在苹果移动端遇到的,谷歌浏览器和安卓环境下都能正常导出,但是在ios移动端导出时就出现了黑屏的情况,而且出现黑屏的这一段pdf刚好涉及到多页pdf,单页的pdf却是正常的。网上说各种原因的都有,看的很头痛。直到lz无意中看到了一个说法:

tips:这里PDF大小指常规a4纸大小,A4大小,210mm x 297mm

不同主流浏览器以及移动终端针对canvas画布的大小有对应的限制,而导出PDF原理恰好就是通过将当前网页内容通过canva渲染成图片再导出pdf的。

安卓端绘制canvas大小转化成PDF,大概是10几页

苹果端绘制canvas大小转化成PDF,大概5~6页

而lz黑屏的地方,正好是要一次性生成9页pdf,按照这个思路一测,果然真相大白

解决办法:就是分页节点细化,避免绘制canvas画布大小超出限制。

4.问题3:分页与截断

        对于将网页内容导出pdf,出现截断问题算是老生常谈的问题了。对于固定的内容,我们可以指定分页节点,再合并导出一个PDF文件,不用考虑分页出现截取的情况。但是对于动态的dom内容,无法指定分页节点,我们在一股脑导出,让其自动分页的情况下,就很容易出现文字被截取,表格被截取的情况。

网上的解决办法也五花八门,什么限制高度啊,动态计算文字高度啊,动态计算分页节点啊。一看就头大。直到lz看到了一篇这样的示例:

vue-pdf2: 纯前端导出版本2(回炉重制版)已解决分页截断,页眉,页脚,页码,页边距,模糊等情况

根据封装的参数来看,也算是能满足很多常规业务需求了。 

/*** 生成pdf(处理多页pdf截断问题)* @param {Object} param* @param {HTMLElement} param.element - 需要转换的dom根节点* @param {number} [param.contentWidth=550] - 一页pdf的内容宽度,0-595* @param {number} [param.contentHeight=800] - 一页pdf的内容高度,0-842* @param {string} [param.outputType='save'] - 生成pdf的数据类型,添加了'file'类型,其他支持的类型见http://raw.githack.com/MrRio/jsPDF/master/docs/jsPDF.html#output* @param {number} [param.scale=window.devicePixelRatio * 2] - 清晰度控制,canvas放大倍数,默认像素比*2* @param {string} [param.direction='p'] - 纸张方向,l横向,p竖向,默认A4纸张* @param {string} [param.fileName='document.pdf'] - pdf文件名,当outputType='file'时候,需要加上.pdf后缀* @param {number} param.baseX - pdf页内容距页面左边的高度,默认居中显示,为(A4宽度 - contentWidth) / 2)* @param {number} param.baseY - pdf页内容距页面上边的高度,默认 15px* @param {HTMLElement} param.header - 页眉dom元素* @param {HTMLElement} param.footer - 页脚dom元素* @param {HTMLElement} param.headerFirst - 第一页的页眉dom元素(如果需要指定第一页不同页眉时候再传这个,高度可以和其他页眉不一样)* @param {HTMLElement} param.footerFirst - 第一页页脚dom元素* @param {string} [param.groupName='pdf-group'] - 给dom添加组标识的名字,分组代表要进行分页判断,当前组大于一页则新起一页,否则接着上一页* @param {string} [param.itemName='pdf-group-item'] - 给dom添加元素标识的名字,设置了itemName代表此元素内容小于一页并且不希望被拆分,子元素也不需要遍历,即手动指定深度终点,优化性能* @param {string} [param.editorName='pdf-editor'] - 富文本标识类* @param {string} [param.tableSplitName='el-table__row'] - 表格组件内部的深度节点* @param {string} [param.splitName='pdf-split-page'] - 强制分页,某些情况下可能想不同元素单独起一页,可以设置这个类名* @param {string} [param.isPageMessage=false] - 是否显示当前生成页数状态* @param {string} [param.isTransformBaseY=false] - 是否将baseY按照比例缩小(一般固定A4页边距时候可以用上)* @param {Array} [param.potionGroup=[]] - 需要计算位置的元素属性,格式是 data-position='xxx',需要同时在节点上加上param.itemName,如<p data-position='p-position' class='pdf-group-item'></p>* @returns {Promise} 根据outputType返回不同的数据类型,是一个对象*/export class PdfLoader {...
}

测试用例效果:

5.结束语

        如果你要问最终lz选择了什么方法来解决?我会告诉你,我选择了交给后端。不可否认,前端是万能的,确实能实现将网页内容导出PDF,但是话又说回来,经过和产品沟通发现,后端Java通过依赖工具包生成的PDF竟然效果更好?

        所以,需要复杂的PDF导出就让后端来吧!别问,问就是前端和后端的相爱相杀~

        别遇到什么事都自己抗,沟通最重要~

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

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

相关文章

HOW DO VISION TRANSFORMERS WORK

HOW DO VISION TRANSFORMERS WORK Namuk Park1,2, Songkuk Kim1 1Yonsei University, 2NAVER AI Lab{namuk.park,songkuk}yonsei.ac.kr 总结 MSA 改善模型泛化能力&#xff1a; MSA 不仅提高了模型的准确性&#xff0c;还通过平滑损失景观来提高泛化能力。损失景观的平坦化使…

ArkUI-动画

ArkUI-动画 系统能力属性动画显式动画 关键帧动画转场动画路径动画粒子动画 资源调用GIF动画帧动画 三方库LottieSVG 提升动画的流畅度使用renderGroup概述使用约束 系统能力 属性动画 通过更改组件的属性值实现渐变过渡效果&#xff0c;例如缩放、旋转、平移等。支持的属性包…

乐鑫 ESP32-P4 无线连接解决方案

ESP32-P4 是乐鑫信息科技推出的一款功能强大的芯片 (SoC) &#xff0c;专为高性能的应用打造。尽管 ESP32-P4 集成了一系列先进特性&#xff0c;但它并未设置无线连接功能&#xff0c;因此需要额外的连接方案来满足嵌入式系统的多样化需求。乐鑫为 ESP32-P4 提供了三种主要的连…

2分钟学会使用createrepo制作本地yum仓库

华子目录 createrepo介绍实验主题实验前提实验前的准备1.server端配置yum网络源&#xff08;这里以阿里云为主&#xff09;2.server端配置本地yum源&#xff08;需要挂载本地镜像&#xff09;3.关闭selinux和firewalld4.安装createrepo包 server端实验步骤1.只下载&#xff0c;…

检测SSRF漏洞的工具

免责声明此文档仅限于学习讨论与技术知识的分享&#xff0c;不得违反当地国家的法律法规。对于传播、利用文章中提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;本文作者不为此承担任何责任&#xff0c;一旦造成后果请自行承担&…

Self-study Python Fish-C Note19 P62to63

类和对象 (part 2) 本节主要介绍 类和对象的构造函数、重写、钻石继承、Mixin及案例源码剖析&#xff08;原视频P62-63)\ 构造函数 之前我们在函数章节里说&#xff0c;函数是可以通过参数来进行个性化定制的。类在实例化的时候其实也是支持个性化定制对象的。 定义类的时候…

idea的springboot里面的resources是什么

在IDEA&#xff08;IntelliJ IDEA&#xff09;中的Spring Boot项目中&#xff0c;resources目录扮演着非常重要的角色。这个目录主要用于存放项目的非代码资源&#xff0c;包括但不限于配置文件、静态资源文件&#xff08;如图片、CSS、JavaScript等&#xff09;、模板文件&…

如何用c++判断一个类型是vector

如何用c判断一个类型是vector 我们使用模板元编程来搞定 这里我们可以定义一个模板结构体 is_std_vector&#xff0c;并对其进行特化&#xff0c;以便专门处理 std::vector 类型。 . 下面是详细的实现和使用示例。 实现 is_std_vector 类型, 继承自false_type 首先&#xff…

EvoSuite使用总结

1.安装EvoSuite插件 以IDEA为例&#xff0c;在Plugins栏搜索EvoSuite后点击install&#xff0c;安装完成后重启IDEA 2.使用EvoSuite 选中文件右键选择Run EvoSuite 生成成功可以看到如下提示&#xff1a; 注意事项&#xff1a; 生成路径&#xff1a;src/test/java 使用juni…

数智时代敲门砖:华为云MaaS服务让中小企业AI应用如此简单

文&#xff5c;白 鸽 编&#xff5c;王一粟 19.9元的数字人、29.9元的云数据库、195元10卡时包的MaaS大模型服务...... 堪称企业级“双十一”的华为云828营销季&#xff0c;带着一众AI应用产品和云服务&#xff0c;杀疯了。 云计算叠加大模型的价格战&#xff0c;也正式进入…

HTTPS理论(SSL/TLS)

SSL安全套接层协议 为互联网通信提供加密和身份认证SSL3.0有漏洞&#xff0c;被TLS取代基于TCP的协议工作原理 握手&#xff1a;客户端hello&#xff1b;服务器hello&#xff08;发送数字证书&#xff09;&#xff08;协商ssl版本&#xff0c;加密算法&#xff09;数据传输连接…

JS笔记

9.3 1.数据类型 1.1.Object 对象 对象&#xff1a;一切皆对象 面向过程&#xff1a;按照时间的发生顺序&#xff0c;从上往下依次执行 对象对象&#xff1a;指挥对象做某件事 1.2.数据类型的检测 1.3.数据类型转换 1.自动转换&#xff1a;js会通过关系运算符&#xff0c…

ai聊天软件哪个好用?分享5款实用的智能聊天软件

从文字到语音&#xff0c;再到现在的智能AI聊天软件&#xff0c;我们见证了沟通方式的不断演进。 每天&#xff0c;我们都需要与家人、朋友、同事进行交流&#xff0c;而AI聊天软件的出现&#xff0c;无疑为我们的对话增添了一抹智能色彩。 那么&#xff0c;ai聊天软件下载哪…

Kettle--发送邮件

目录 新建转换 执行成功 新建demo 作业job 发送邮件配置 邮件服务器&#xff1a; 授权码获得 以163邮箱为例 新建转换 执行成功 新建demo 作业job 发送邮件配置 确定发件人和收件人邮箱 服务器设置 邮件服务器&#xff1a; 邮件服务商SMTP服务器地址SMTP端口&#xff08;…

图像边缘检测技术详解:利用OpenCV实现Sobel算子

图像边缘检测技术详解&#xff1a;利用OpenCV实现Sobel算子 前言Sobel算子的原理代码演示结果展示结语 前言 在数字图像处理的广阔领域中&#xff0c;边缘检测技术扮演着至关重要的角色。无论是在科学研究、工业自动化&#xff0c;还是在日常生活中的智能设备中&#xff0c;我们…

我的大模型岗位面试总结!太卷了!!!—我面试了24家大模型岗位 只拿了9个offer!

这段时间面试了很多家&#xff08;共24家&#xff0c;9个offer&#xff0c;简历拒了4家&#xff0c;剩下是面试后拒的&#xff09;&#xff0c;也学到了超级多东西。 大模型这方向真的卷&#xff0c;面试时好多新模型&#xff0c;新paper疯狂出&#xff0c;东西出的比我读的快…

HiGPT:异构图语言模型的突破

人工智能咨询培训老师叶梓 转载标明出处 人工智能领域的一大挑战是如何从海量复杂的数据中提取有价值的信息&#xff0c;特别是在处理异构图数据时。异构图由多种类型的节点和边组成&#xff0c;它们之间的相互关系丰富而复杂。传统的图神经网络&#xff08;GNNs&#xff09;在…

年薪80万,成功入职字节跳动!

前言&#xff1a; 最近AI相关就业岗位爆了。。。无论是**华为、**百度、阿里、字节等互联网巨头&#xff0c;还是中小型的科技公司都在高薪挖 AI 人才。 上周找 字节的面试官朋友&#xff08;职级3-1&#xff09;要来了几套高质量AI****内部资料和2024吴恩达机器学习资料。既…

DeepMind MuJoCo——生成动态场景中的物体运动视频,模拟物理现象和动作生成

一、DeepMind MuJoCo介绍 DeepMind MuJoCo&#xff08;Multi-Joint dynamics with Contact&#xff09;是一个由 DeepMind 开发的高效物理仿真引擎&#xff0c;专门用于模拟具有复杂物理交互的机器人和物理系统。MuJoCo 能够在实时和非实时环境下进行高精度的物理仿真&#xf…

护眼台灯哪个牌子最好?五款央视公认最好的护眼灯分享

小时候&#xff0c;对正确用眼知识一无所知&#xff0c;也不明白何种光线环境对眼睛最为友善&#xff0c;结果如今的近视度数已濒临千度大关。虽然早已习惯佩戴眼镜的生活&#xff0c;但近视所带来的诸多不便仍旧在日常生活中无处不在。因此&#xff0c;对于家中孩子的视力健康…