目录
第一章、解析需求
第二章、工具说明以及注意事项
2.1 生成pdf文件用到的组件库
2.2 注意事项
第三章、代码思路(pdf单页+分页)
3.1 工具导入
3.2 难点:通过html2canvas和jspdf生成pdf文件
3.2.1 涉及到函数参数解释
3.2.2 实现思路
3.2.3 输出pdf遇到的问题
3.2.4 提供解决2.2多页截断问题的思路
3.2.5 最终效果
第四章、多个文件实现压缩包解压
4.1.1 实现
4.1.2 实现效果
第五章、 window.print()使用
5.1 window.print()说明
5.2 代码使用
第六章、总结
6.1 盖章的pdf效果图
6.2 完整代码
第一章、解析需求
- 模板——页面,数据肯定就得从后端渲染得到,然后就是动态渲染页面
- 盖章——也就是一个章图片,至于位置呢,开发者通过样式决定
- pdf文件——这里我分成了两种情况,用window打印下载生成的pdf文件以及其他方法
- 难点——如何生成多个pdf文件,并压缩下载;如果一个pdf多页如何处理
第二章、工具说明以及注意事项
2.1 生成pdf文件用到的组件库
- html2canvas: 将Dom转化为图片
- jspdf:将图片转化为pdf
- jszip: 将多个文件打压成一个zip的压缩包
- file-saver:将文件导出
2.2 注意事项
使用上面这几个组件库的时候需要注意的是html2canvas这个插件是先将我们的dom/html页面先转化成了一个canvas的画布图片,如果我们的pdf只有一页,我们使用这套插件的时候都没有问题,百度上大部分的解决方案我们都可以使用,但是当我们想输出多页pdf的时候,问题就出现了,jspdf插件会直接针对图片进行截断,不能识别每一个dom元素,就会造成在分页的过程中,html中的图片、表格、文字…出现截断的情况,同时打印出来的pdf排版也不美观
第三章、代码思路(pdf单页+分页)
3.1 工具导入
javascript">npm install html2canvas
npm install jspdf
npm install jszip
npm install file-saver
3.2 难点:通过html2canvas和jspdf生成pdf文件
3.2.1 涉及到函数参数解释
-
const PDFres = await downloadPDF(dom, name, a4width, a4height)
dom:需要生成pdf的html的dom元素
name: 生成pdf的文件名称
a4width:pdf的宽度
a4height: pdf的高度 (注意这里小编这么命名是要用到的宽高是a4纸的宽高)
- html2canvas(dom,{scale,dpi})
html2canvas(dom, { // 截图所需要的dom元素的名称id/class…
// 可配置项放在这里
scale: 4, // 提高渲染的比例
dpi: window.devicePixelRatio * 4 // 设备像素比
})
- new JsPDF('', 'pt', 'a4') 三个参数:new JsPDF('参数1','参数2','参数3')
- 参数1: l/p l:横向 p:纵向 ,默认纵向
- 参数2:测量单位("pt","mm", "cm", "m", "in" or "px") ; 建议用pt 1(pt) = 1/72(inch-英寸) 1(inch) = 2.54(cm)
- 参数3:pdf的格式,默认'a4',这里有很多格式可选:a0 - a10、b0 - b10、c0 - c10、dl、letter、government-letter、legal、junior-legal、ledger、tabloid、credit-card我们主要知道的为:a3:[841.896,1190.55],a4 :[595.28,841.89],以及自定义格式,自定义格式参数为:[pdfX, pdfY] ——理解:pdfX:pdf的宽度 pdfY:pdf的高度
- pdf.addImage(image, format, x, y, width, height, alias, ***pression, rotation)
- image:表示要插入的图片资源,可以是图片文件的路径或者base64编码字符串。
- format:表示要插入的图片格式,包括:'JPEG','PNG', 'GIF','BMP', 'TIFF', 'RAW', 'JPEG2000'。 注:正常pdf的a4纸距离四边 2.54cm(上下) 3.14cm(左右)
- x:图片在PDF中的x轴坐标,单位为pt(点) 1(pt) = 1/72(inch) 1(inch) = 2.54(cm)
- y:图片在PDF中的y轴坐标,单位为pt(点)
- width:图片在PDF中的宽度,单位为pt(点)
- height:图片在PDF中的高度,单位为pt(点)
- alias(可选):指定图片资源的别名。
- ***pression(可选):指定图片的压缩质量,取值为0-1之间的浮点数。
- rotation(可选):指定图片的旋转角度,取值范围为0-360之间的整数。 - pdf.addPage() 添加空白页
- pdf.deletePage(targetPage) 删除目标页,参数为要删除的页
-
pdf.save(`${name}.pdf`) 保存为pdf文档,参数为自定义pdf文件名称
3.2.2 实现思路
- 有了以上了解后:我们需要得到一个pdf需要有4个基本值——需要输出pdf的html页面、生成pdf的名字(当然代码写的过程中可以写死就不需要了)、生成pdf纸的宽、高
- 值准备好了,我们利用html2canvas将dom页面转换成图片,然后对比pdf纸的宽高等比缩放内容的宽高,从而在pdf上不失真的显示
- pdf生成:单页与分页,两个高度对比,html页面的实际高度以及pdf的页面高度,如果html页面高度小于pdf一页的高度,那么直接渲染输出,否则pdf页面添加,同时html页面的高度递减(可理解成偏移量也要跟着变化)
- 最后生成pdf文件
- 代码如下——(具体还有不清楚的可看小编的注释)
downloadPDF = (dom, name, a4width, a4height) => {
return new Promise((resolve, reject) => {
// 打印纸的宽高赋值
const domWidth = dom.offsetWidth
const domHeight = dom.offsetHeight
setTimeout(() => {
// 需要答应的dom元素赋值
const _downDOM = dom
// 导出之前先将滚动条置顶,不然会出现数据不全的现象
window.pageYoffset = 0
document.documentElement.scrollTop = 0
document.body.scrollTop = 0
html2canvas(_downDOM, { // 两个参数:所需要截图的元素id,截图后要执行的函数, canvas为截图后返回的最后一个canvas
scale: 4, // 按比例增加分辨率 (2=双倍).
dpi: window.devicePixelRatio * 4 // 设备像素比
}).then((canvas) => {
const contentWidth = domWidth
const contentHeight = domHeight
// 一页pdf显示html页面生成的canvas高度;
const pageHeight = (contentWidth / a4width) * a4height
// 未生成pdf的html页面高度
let leftHeight = contentHeight
// 页面偏移
let position = 0
// a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
const imgWidth = a4width
const imgHeight = (a4width / contentWidth) * contentHeight
// const pageData = new Image()
// 设置图片跨域访问
// pageData.setAttribute('crossOrigin', 'Anonymous')
const pageData = canvas.toDataURL('image/jpeg', 1.0)
const pdf = new JsPDF('', 'pt', 'a4')
/*
new JsPDF('', 'pt', 'a4') 三个参数:new JsPDF('参数1','参数2','参数3')
· 参数1:'l'/'p' l:横向 p:纵向
· 参数2:测量单位("pt","mm", "cm", "m", "in" or "px") -- 建议用pt 1(pt) = 1/72(inch) 1(inch) = 2.54(cm)
· 参数3:pdf的格式,默认'a4',
---- 这里有很多格式可选:
a0 - a10
b0 - b10
c0 - c10
dl
letter
government-letter
legal
junior-legal
ledger
tabloid
credit-card
---- 我们主要知道的为:a3 --> [841.896,1190.55],a4 --> [595.28,841.89],以及自定义格式
---- 自定义格式参数为:[pdfX, pdfY] --理解:pdfX:pdf的宽度 pdfY:pdf的高度
*/
// 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
// 当内容未超过pdf一页显示的范围,无需分页
if (leftHeight < pageHeight) {
pdf.addImage(pageData, 'JPEG', 1.25, 0, imgWidth, imgHeight)
/*
pdf.addImage(image, format, x, y, width, height, alias, ***pression, rotation)
1、image:表示要插入的图片资源,可以是图片文件的路径或者base64编码字符串。
2、format:表示要插入的图片格式,包括:‘JPEG’, ‘PNG’, ‘GIF’, ‘BMP’, ‘TIFF’, ‘RAW’, ‘JPEG2000’。
注:正常pdf的a4纸距离四边 2.54cm(上下) 3.14cm(左右)
3、x:图片在PDF中的x轴坐标,单位为pt(点)。 1(pt) = 1/72(inch) 1(inch) = 2.54(cm)
4、y:图片在PDF中的y轴坐标,单位为pt(点)。
5、width:图片在PDF中的宽度,单位为pt(点)。
6、height:图片在PDF中的高度,单位为pt(点)。
7、alias(可选):指定图片资源的别名。
8、***pression(可选):指定图片的压缩质量,取值为0-1之间的浮点数。
9、rotation(可选):指定图片的旋转角度,取值范围为0-360之间的整数。
*/
} else {
while (leftHeight > 0) {
pdf.addImage(pageData, 'JPEG', 1.25, position, imgWidth, imgHeight)
leftHeight -= pageHeight
position -= a4height // 841.89
// 避免添加空白页
if (leftHeight > 0) {
pdf.addPage()
}
}
}
// 这里返回文件 用来处理多个下载 打包zip
resolve({ PDF: pdf, name: name })
// 直接单个pdf可直接调用下面方法
// pdf.save(name)
})
}, 10)
})
}
3.2.3 输出pdf遇到的问题
可以注意的是,小编在代码中添加这么一段代码:
// 导出之前先将滚动条置顶,不然会出现数据不全的现象
window.pageYoffset = 0 // 相对于窗口显示区左上角的位置为0
document.documentElement.scrollTop = 0 // 滚动条垂直位置为0
document.body.scrollTop = 0 // 让页面置顶
原因:小编在将下载pdf的按钮放在页面最下方的时候,输出pdf,因为位置问题,导致pdf的输出不全,小编发现置顶之后肯定不会出错,所以放入置顶的一段代码
3.2.4 提供解决2.2多页截断问题的思路
- 确定问题点:首先上面的函数是直接将html页面转换成了一张图片,对该图片处理的时候我们并不能知道这里面的元素:图片、表格、文字的位置,需要解决完整的内容不被截断而且还能完美定位布局就有些困难,所以我们需要在html转换成图片之前就对元素开始做定位的处理,从而在pdf多页输出的时候正常输出也不会有影响
- 需要添加的点我们已经找到了,那么实现的方法呢?小编的思路提供参考:
- 小编将html的页面进行了处理,对一些可能出现截断的父元素、子元素盒子添加了类名 'page_re',从而通过计算每一个盒子高度的累计和来判断是否超出pdf页面,超出了则在下一个盒子计算它距离第一页最后一个盒子的距离,公式:当前容器移动到下一页需要的距离:当前容器的高度减去超出当前pdf页的高度,超出当前pdf页的高度:到当前盒子的累计和减去pdf高度
// 处理页面分页问题
deal (dom) {
const list = document.querySelectorAll('.page_re') // 获取每一个page_re盒子
/*
备注:
要对每一个转成pdf可分割不可分割去元素添加page_re的类标签,
目的:
为了计算转成pdf后的距离处理
缺点:写样式时需要自己留意哪些盒子需要加一个类标签,从而计算高度时是一个独立的盒子
*/
let margin = 0 // 计算每一页 最后一个容器距离底部的距离
let pdHeight = 0 // 容器高度的累加值
const pageHeight = A4_HEIGHT / A4_WIDTH * dom.offsetWidth// 设置a4纸转成html时的的高度,从而计算html转图片后放入a4纸时的格式
list[0].style.marginTop = MARGIN_TOP // 给第一个盒子一个高度
// i 从1开始, 0 为 封面---->我写的内容没有封面,从第一个盒子开始计算高度
for (let i = 0; i < list.length; i++) {
const height = parseFloat(window.get***putedStyle(list[i]).height) // 每一个page_re盒子的高度
pdHeight += height // 累加每个容器的高度
// 当容器的高度累加大于pdf一页的高度时,说明这个容器需要放到下一页。
if (pdHeight > pageHeight) {
margin = height - (pdHeight - pageHeight) // 计算距离下一页的高度 当前容器的高度减去超出当前pdf页的高度就是当前容器移动到下一页需要的距离
list[i].style.marginTop = margin + 'px' // 下一页的第一个盒子距离上一页的高度
i -= 1 // list[i] 表示当前容器应显示在下一页,所以当下一页计算容器高度累加时应加上list[i]的高度, 这里可以试试用while
pdHeight = 10 // 进入下一页重新计算累加高度,这里每个page_re
// 元素下的第一个子元素都会有10px的paddingTop,所以初始高度为10 这里也可以动态获取
}
}
},
html部分:
3.2.5 最终效果
分页成功:(这里图片表示章,其余表示内容排版,盖章的pdf文件具体效果看最后一章节的效果图)
第四章、多个文件实现压缩包解压
4.1 生成zip实现
4.1.1 实现
该内容模块就不难了,将生成的每一个pdf的promise放到promises数组中,如果数组中只有一个值,只需要输出一个pdf,否则将多个pdf整合生成zip,代码如下:
// 生成zip
zipChange (promises) {
Promise.all(promises).then(async (pdfs) => {
const zip = new JSZip()
promises.forEach(async (item, index) => {
const { PDF, name } = item
if (promises.length === 1) {
PDF.save(`${name}.pdf`)
} else {
await zip.file(`${name}.pdf`, PDF.output('blob'))
}
})
if (promises.length > 1) {
zip.generateAsync({ type: 'blob' }).then((content) => {
FileSaver.saveAs(content, '内容' + '.zip')
})
}
})
}
4.1.2 实现效果
第五章、 window.print()使用
5.1 window.print()说明
window.print()打印是浏览器自带的打印,实现原理是将html转换为pdf可以在线预览打印或者导出pdf,在任何网页上可通过Ctil+p快捷键调出浏览器打印程序,它可将整个网页打印出来,在我们开发中,其实并不需要将所有页面打印出来,或者只需要局部的页面做打印,那我我们就自己实现window.print()打印功能,具体可以看小编接手的项目的实现如下
5.2 代码使用
第六章、总结
6.1 盖章的pdf效果图
6.2 完整代码
demo_pdf: 这是一个纯前端生成盖章pdf模板的demo
路径:/pdf
如果代码运行不了、有不同的思路评论区可以留言小编,及时给予回复!!!有用就点个赞吧!!!