jsPDF将html页面生成pdf文件的前端解决方案及html2canvas如何真正解决跨域图片的问题
jsPdf是一个可以把html转成pdf的插件,有人多人在用。
但是老外做的很多东西没考虑过英文之外的语言,这个也不例外,不支持中文,而且页面还原度也不是很好。
网上实例导出的是 A4 大小,用到的是 addImage,以及需要 html2canvas 库。其实就是把需要导出的网页,利用 html2canvas 生成图片,在 addImage 至 PDF 中。至于为什么需要先转成图片呢,直接网页转不是很好吗,还能文字复制。因为不支中文,而且,样式还原度也不高。当然,执意于文本复制,网上也有解决方式(搜索关键字:jsPDF 中文)。
所以常用的解决方案就是:曲线救国: html2canvas + jsPdf
既然你不支持中文,那我把页面转成图片,怕不怕,图片再导出PDF照样中!这种方式很常见、很省事,问题也很多图片拉伸、模糊,最重要这样导出的PDF是没有灵魂的,因为他里面的内容都是图片,不能复制。对于pdf操作要求比较高的,这种方案就不大适合。如果你喜欢这种可以参照这篇文章Javascript 将html转成pdf,下载,支持多页哦(html2canvas 和 jsPDF),写的很详细。
采用这种方案也有一些问题需要处理,这里记录下,给后来人避坑。
1、html2canvas生成图片模糊的问题
这个问题大多数都会遇到,可以看我之前的博客处理:html转为图片插件:html2canvas保存图片模糊问题解决
2、html2canvas偶尔生成大量空白漏掉一些元素问题
在使用过程我发现有时候当滚动到底部时,生成的图片就会存在大量空白,漏掉很多元素
解决方案就是在生成图片前滚动至顶部使生成图片的元素进入渲染区域内。添加如下代码即可:
document.documentElement.scrollTop = 0
3、html2canvas生成图片里包含跨域图片的处理
这个问题也比较奇怪,在很多同版本浏览器上均可以生成跨域图片,但是在个别电脑上同版本浏览器也不行。会报跨域的错误,因为我们图片都是存储在阿里云的oss上
Access to image at 'https://......oss-cn-...aliyuncs.com/image/base/be6f..f.jpeg'
from origin 'http://...:3001' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
网上很多各种解决方案也不靠谱。所以最后还是自己看源码,在html2canvas官网:http://html2canvas.hertzen.com/,下载源码,这里先看一下之前写的博客,涉及到在线图片跨域转base64格式的问题:将在线图片转换成base64踩坑记录及静态资源跨域及缓存的处理
通过之前这篇博客就可以看到问题,那么其实我们只要改下面2段代码即可
if (isInlineBase64Image(src) || useCORS) {
// img.crossOrigin = 'anonymous';
img.crossOrigin = '*';
}
// img.src = src;
img.src = src + '?v=' + Math.random();
注释的为原代码,下面的为修改后的代码即可。
4、jsPdf分页问题
页面过长,就涉及分页问题。因为 A4 是有固定页面大小的(宽:595.28, 高:841.89),保存的内容高度(缩放至A4宽度)超过该长度,则会出现截断问题。
解决方式其实不多,分情况而定。其实最简单的方式是不分页。不是吗?不分页,啥问题都没。这个问题就是,为什么要分页?讲真,没有分页需求,也没有打印需求,没必要分页,不是吗?整个页面导成图片就行。
不分页就是将元素宽高作为pdf文件的宽高就行
const pdf = new jsPDF('', 'pt', [contentWidth, contentHeight])
如果确实要分页的话,如果是纯静态页面,还比较简单,根据 A4 的尺寸比例,预留好间隔,这样,裁剪的时候,便不会出现文本或者其他内容被裁成两半。
如果是动态页面的话就比较复杂,前端实现方案就并不太合适了,建议采用后端实现方案。
5、实例代码
downPdf () {
let _this = this
document.documentElement.scrollTop = 0
let canvas = document.createElement("canvas")
let context = canvas.getContext("2d")
let _articleHtml = document.getElementById('article-content')
let _w = _articleHtml.clientWidth
let _h = _articleHtml.clientHeight
let scale = 3
canvas.width = _w * scale
canvas.height = _h * scale
context.scale(scale, scale)
let opts = {
scale: 1,
width: _w,
height: _h,
canvas: canvas,
useCORS: true
}
html2canvas(_articleHtml, opts).then(canvas => {
_this.createPdfAll(canvas, scale)
})
},
createPdfAll (canvas, scale) {
let contentWidth = canvas.width / scale
let contentHeight = canvas.height / scale
let pdf = new jsPDF('', 'pt', [contentWidth, contentHeight])
let pageData = canvas.toDataURL('image/jpeg', 1.0)
pdf.addImage(pageData, 'JPEG', -9, 0, contentWidth, contentHeight)
pdf.save(`${this.detail.title}.pdf`)
},
此种方案纯前端实现,无需后端配合,并且页面还原度比较高,对于pdf操作要求不高的需求,还是比较合适的解决方案。
缺点就是无法复制,对pdf操作不大兼容,对于分页也不大友好,容易出现分割。其实后端实现方案也有蛮多,并且对于分页更友好,可以分情况选择不同方案。