当前位置: 首页 > news >正文

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导出就让后端来吧!别问,问就是前端和后端的相爱相杀~

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

相关文章:

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

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

服务器数据恢复—Raid磁盘阵列故障类型和常见故障原因

出于尽可能避免数据灾难的设计初衷&#xff0c;RAID解决了3个问题&#xff1a;容量问题、IO性能问题、存储安全(冗余)问题。从数据恢复的角度讨论RAID的存储安全问题。 常见的起到存储安全作用的RAID方案有RAID1、RAID5及其变形。基本设计思路是相似的&#xff1a;当部分数据异…...

C++字符串中的string类操作

愿我如星君如月&#xff0c;夜夜流光相皎洁。 ——《车逍遥篇》【宋】范成大 目录 正文&#xff1a; 主要特点&#xff1a; 基本操作&#xff1a; 代码演示&#xff1a; 总结&#xff1a; 今天我们接着上次的章节继续&#xff0c;这次我们来说一个为解决上个方法的缺陷而诞…...

axios设置responseType: ‘blob‘,获取接口返回的错误信息

在axios的请求中当后端接口返回的是文件流的情况下&#xff0c;我们需要在请求参数里面设置responseType: blob&#xff0c;如果接口报错&#xff0c;默认前端无法获取后端返回的错误信息。 解决方法&#xff1a;通过FileReader获取错误信息 async handleFetch() {const res aw…...

【C++】:模板初阶—函数模板|类模板

✨ Blog’s 主页: 白乐天_ξ( ✿&#xff1e;◡❛) &#x1f308; 个人Motto&#xff1a;他强任他强&#xff0c;清风拂山岗&#xff01; &#x1f4ab; 欢迎来到我的学习笔记&#xff01; 本文参考博客&#xff1a;一同感受C模版的所带来的魅力 一、泛型编程思想 首先…...

Java 远程执行服务器上的命令

在Java中使用JSch库执行远程服务器上的命令是一种常见的做法&#xff0c;特别是在需要自动化运维任务或者进行远程文件操作时。以下是基于Codekru网站提供的示例&#xff0c;展示如何使用JSch库在远程服务器上执行单个或多个命令。 准备工作 首先&#xff0c;确保您的项目中已…...

3DMax基础- 创建基础模型

目录 零.软件简介 一. 标准基本型 长方体 圆锥体 球体 圆柱体 管状体 圆环 四棱锥 茶壶 平面​编辑 加强型文本 二. 扩展基本体 三.复合对象 变形 散布 一致 连接 图形合并 布尔 并集 合并 交集 差集 四.门和窗 门 窗 植物,栏杆,墙 零.软件简介 3…...

JavaScript 知识点(从基础到进阶)

&#x1f30f;个人博客主页&#xff1a;心.c ​ 前言&#xff1a;JavaScript已经学完了&#xff0c;和大家分享一下我的笔记&#xff0c;希望大家可以有所收获&#xff0c;花不多说&#xff0c;开干&#xff01;&#xff01;&#xff01; &#x1f525;&#x1f525;&#x1f5…...

计算机网络知识点复习——TCP协议的三次握手与四次挥手(连接与释放)

TCP协议的三次握手与四次挥手&#xff08;连接与释放&#xff09; 一、前言二、简单的知识准备1. TCP协议的主要特点2. TCP报文段 三、TCP连接的建立&#xff08;三次握手&#xff09;四、TCP连接的释放&#xff08;四次挥手&#xff09;五、TCP连接与释放的总结六、结束语 一、…...

SpringDataJPA系列(7)Jackson注解在实体中应用

SpringDataJPA系列(7)Jackson注解在实体中应用 常用的Jackson注解 Springboot中默认集成的是Jackson&#xff0c;我们可以在jackson依赖包下看到Jackson有多个注解 一般常用的有下面这些&#xff1a; 一个实体的示例 测试方法如下&#xff1a; 按照上述图片中的序号做个简…...

【Spring Boot 3】【Web】统一封装 HTTP 响应体

【Spring Boot 3】【Web】统一封装 HTTP 响应体 背景介绍开发环境开发步骤及源码工程目录结构总结背景 软件开发是一门实践性科学,对大多数人来说,学习一种新技术不是一开始就去深究其原理,而是先从做出一个可工作的DEMO入手。但在我个人学习和工作经历中,每次学习新技术总…...

Linux如何做ssh反向代理

SSH反向代理是一种通过SSH协议实现的安全远程访问方式&#xff0c;它允许客户端通过SSH连接到一台具有公网IP的代理服务器&#xff0c;然后这台代理服务器再将请求转发给内部网络中的目标主机。以下是实现SSH反向代理的步骤&#xff1a; 一、准备工作 确保服务器配置&#xff…...

Verilog语法+:和-:有什么用?

Verilog语法:和-:主要用于位选择&#xff0c;可以让代码更简洁。 一、位选择基础 在Verilog中&#xff0c;位选择可以通过直接索引来实现&#xff0c;例如&#xff1a; reg [7:0] data; wire select_a; wire [2:0] select_b; assign select_a data[3]; assign select_b …...

stm32F103 串口2 中断 无法接收指定字符串 [已解决]

stm32F103 串口2中断接收指定字符串 USART 初始化和中断配置示例中断处理函数示例关键点总结 确保在串口配置中正确使能空闲中断 ( USART_IT_IDLE) 是关键。这个中断可以帮助你在串口接收一帧数据完成后&#xff0c;进行相应的处理和分析。 为了确保你在串口配置时能避免类似问…...

Matlab/Simulink和AMEsim联合仿真(以PSO-PID算法为例)

目录 安装软件和配置环境变量 Matlab/Simulink和AMEsim联合仿真详细流程 非常重要的一点 Simulink模型和AMEsim模型用S-Function建立连接 从AMEsim软件打开Matlab Matlab里的设置 Matlab的.m文件修改&#xff08;对于PSO-PID算法&#xff09; 运行程序 我印象中好像做过…...

超声波测距模块HC-SR04(基于STM32F103C8T6HAL库)

超声波测距模块参考资料 1.电路连接及引脚配置 触发信号PA3只需要输出10us的高电平&#xff0c;所以直接设置成 普通的GPIO端口即可&#xff1b;回响信号使用外部中断&#xff0c;上升沿信号产生外部中断&#xff0c;打开定时器&#xff0c;下降沿再产生一次中断&#xff0c;读…...

Go语言结构体和元组全面解析

Go语言中的复合类型与其应用 在编程中&#xff0c;标准类型虽然方便&#xff0c;但无法满足所有需求。Go通过支持结构体和元组类型&#xff0c;为开发者提供了自定义数据类型的能力。本文将介绍如何定义结构体、如何使用指针操作结构体、如何通过元组返回多个值等内容&#xf…...

集成电路学习:什么是SDK软件开发工具包

SDK&#xff1a;软件开发工具包 SDK&#xff0c;即Software Development Kit&#xff08;软件开发工具包&#xff09;&#xff0c;是一套由软件提供商或其他组织提供的开发工具集合。这些工具旨在帮助开发者更快速、更便捷地创建、测试和部署软件应用程序。以下是对SDK的详细解…...

java后端如何发送http请求

用java后端发送请求需要用到的一个工具包为HttpClient。HttpClient是Apache的一个子项目&#xff0c;是高效的、功能丰富的支持HTTP协议的客户端编程工具包。 引入依赖 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId&…...

装WebVideoCreator记录

背景&#xff0c;需要在docker容器内配置WebVideoCreator环境&#xff0c;配置npm、node.js WebVideoCreator地址&#xff1a;https://github.com/Vinlic/WebVideoCreator 配置环境&#xff0c;使用这个教程&#xff1a; linux下安装node和npm_linux离线安装npm-CSDN博客 1…...

【杂谈】-递归进化:人工智能的自我改进与监管挑战

递归进化&#xff1a;人工智能的自我改进与监管挑战 文章目录 递归进化&#xff1a;人工智能的自我改进与监管挑战1、自我改进型人工智能的崛起2、人工智能如何挑战人类监管&#xff1f;3、确保人工智能受控的策略4、人类在人工智能发展中的角色5、平衡自主性与控制力6、总结与…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

Zustand 状态管理库:极简而强大的解决方案

Zustand 是一个轻量级、快速和可扩展的状态管理库&#xff0c;特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...

阿里云ACP云计算备考笔记 (5)——弹性伸缩

目录 第一章 概述 第二章 弹性伸缩简介 1、弹性伸缩 2、垂直伸缩 3、优势 4、应用场景 ① 无规律的业务量波动 ② 有规律的业务量波动 ③ 无明显业务量波动 ④ 混合型业务 ⑤ 消息通知 ⑥ 生命周期挂钩 ⑦ 自定义方式 ⑧ 滚的升级 5、使用限制 第三章 主要定义 …...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

生成 Git SSH 证书

&#x1f511; 1. ​​生成 SSH 密钥对​​ 在终端&#xff08;Windows 使用 Git Bash&#xff0c;Mac/Linux 使用 Terminal&#xff09;执行命令&#xff1a; ssh-keygen -t rsa -b 4096 -C "your_emailexample.com" ​​参数说明​​&#xff1a; -t rsa&#x…...

VTK如何让部分单位不可见

最近遇到一个需求&#xff0c;需要让一个vtkDataSet中的部分单元不可见&#xff0c;查阅了一些资料大概有以下几种方式 1.通过颜色映射表来进行&#xff0c;是最正规的做法 vtkNew<vtkLookupTable> lut; //值为0不显示&#xff0c;主要是最后一个参数&#xff0c;透明度…...

vue3 定时器-定义全局方法 vue+ts

1.创建ts文件 路径&#xff1a;src/utils/timer.ts 完整代码&#xff1a; import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...

三体问题详解

从物理学角度&#xff0c;三体问题之所以不稳定&#xff0c;是因为三个天体在万有引力作用下相互作用&#xff0c;形成一个非线性耦合系统。我们可以从牛顿经典力学出发&#xff0c;列出具体的运动方程&#xff0c;并说明为何这个系统本质上是混沌的&#xff0c;无法得到一般解…...