jspdf+html2canvas导出pdf遇到的问题
原理
需要导出的DOM元素,利用Html2canvas生成图片,然后用jspdf的addImage方法添加到pdf中。 但在这个过程中,我遇到了一些问题,记录下。
问题一. 图片跨域
解决方法1:图片增加crossOrigin属性
<img crossOrigin="anonymous"/>
这种方法在直接打开index.html文件时图片无法展示。
解决方法2:将图片换成svg,将其写入html文件中。
<div class="no-data">
<svg width="64" height="41" viewBox="0 0 64 41" xmlns="http://www.w3.org/2000/svg" class="ant-empty-img-simple">
<g transform="translate(0 1)" fill="none" fill-rule="evenodd">
<ellipse cx="32" cy="33" rx="32" ry="7" class="ant-empty-img-simple-ellipse"></ellipse>
<g fill-rule="nonzero" class="ant-empty-img-simple-g">
<path
d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z">
</path>
<path
d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
class="ant-empty-img-simple-path"></path>
</g>
</g>
</svg>
</div>
这种方法保证图片在哪种方式下都不跨域。但是在html中写入svg让代码的可阅读性大大下降。
解决方法3:将图片用base64加密。
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAjCAYAAAAkCQwqAAAAAXNSR0IArs4c6QAAAxpJREFUaEPtmYeK4zAQQJXeIL2H/P+HhfRNg/R6PHNzmF0lkWJl13ArMCnI45mnaZIjt9vtphyPzWajVquVSiQSTiSfTidVKBRULpdzIs8vJOIawGw2U9vtVmWzWZVOp50ovN/v/8msVCpOZIoQpwBQdDQaqWaz6cx4UfRdsp0BwE0xvlwuv8VVAbFer9VisfAAuwovJwCu16tnPDFKrL5zkFvIMUCIRqOBH+UEAManUilVKpUCK2QiAC84HA4ehKAjMICPjw9Ph2q1GlQXq/tdPTcQAFbieDyqRqNhpbyryePx2MsF5J1Xx8sAXMfiKwbQwgyHw0C55yUAJKH5fO40G78CgHuCVh9rALvdTuF6rVbLS3xhGNIjEIqZTMZKJSsAGE/Gp8t7Vofj8bjK5/NWytybTLhdLpeHsvAEOlAqgw0EKwCQHgwGqlgsPjUMhRhBE+RkMlH0Gclk8ukzl8ularfbVl2oNQDcn4eYDBIUAF4NFWo9AEzrPYvD82z2IG8FQK2mObJRyA8Wj6PUmvYYoQOAS5IrTEJG51HcTyiZ3h86ACg/nU69EDCJYT8EGixCoF6vKxKqyQgdAJQ+n8/e4YhtHgAAG6tYLGZiuzcnlACMtXcw8VsA0Ad0Oh0H6roX0e/3rQ9jrKoA7kxpMy1L7k18LJHFoUM1zRlI+wVgcyj66wH/ewjI1jPMOcD2wPRpDmAjIheNCa0piSaMgwTN6RBNFwemcj3S9S4Atp9cYjxC+B12AOw9pHkSAPy+11BpAeDqsp310xMP6Ha7YXQA1ev1vM2XruvEK3Tl8QsAVpk2VPfKEG+gt6cRikQioYKAvjRCtVpN+74AfYHw2RO+AKDUsfr33pnKS893vwCxpYte8hJVdy8A2Jl+9gKrEEAwYNim4ik2GxVbg2zmiy5sm+95JsbrjvG0ADASd5dEqPMGDivCBEB36AIMSYAkRB0c4zIoMGxW5ifmYiTGiuHPctVTAH4jxDN0nz9prBjNp3w31ccKwD2hAJGL0JFcIaEj/8n/DxsTX3URg+TT2739XWH/f6bG6ub9AYeQfbawholHAAAAAElFTkSuQmCC"/>
这种解决方法兼容性很好。
解决方案4:echarts中的image使用base64方式。
rich: {
first: {
width: 20,
height: 20,
borderWidth: 0,
color: 'rgba(0, 0, 0, 0)',
backgroundColor: {
image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAjCAYAAAAkCQwqAAAAAXNSR0IArs4c6QAAAxpJREFUaEPtmYeK4zAQQJXeIL2H/P+HhfRNg/R6PHNzmF0lkWJl13ArMCnI45mnaZIjt9vtphyPzWajVquVSiQSTiSfTidVKBRULpdzIs8vJOIawGw2U9vtVmWzWZVOp50ovN/v/8msVCpOZIoQpwBQdDQaqWaz6cx4UfRdsp0BwE0xvlwuv8VVAbFer9VisfAAuwovJwCu16tnPDFKrL5zkFvIMUCIRqOBH+UEAManUilVKpUCK2QiAC84HA4ehKAjMICPjw9Ph2q1GlQXq/tdPTcQAFbieDyqRqNhpbyryePx2MsF5J1Xx8sAXMfiKwbQwgyHw0C55yUAJKH5fO40G78CgHuCVh9rALvdTuF6rVbLS3xhGNIjEIqZTMZKJSsAGE/Gp8t7Vofj8bjK5/NWytybTLhdLpeHsvAEOlAqgw0EKwCQHgwGqlgsPjUMhRhBE+RkMlH0Gclk8ukzl8ularfbVl2oNQDcn4eYDBIUAF4NFWo9AEzrPYvD82z2IG8FQK2mObJRyA8Wj6PUmvYYoQOAS5IrTEJG51HcTyiZ3h86ACg/nU69EDCJYT8EGixCoF6vKxKqyQgdAJQ+n8/e4YhtHgAAG6tYLGZiuzcnlACMtXcw8VsA0Ad0Oh0H6roX0e/3rQ9jrKoA7kxpMy1L7k18LJHFoUM1zRlI+wVgcyj66wH/ewjI1jPMOcD2wPRpDmAjIheNCa0piSaMgwTN6RBNFwemcj3S9S4Atp9cYjxC+B12AOw9pHkSAPy+11BpAeDqsp310xMP6Ha7YXQA1ev1vM2XruvEK3Tl8QsAVpk2VPfKEG+gt6cRikQioYKAvjRCtVpN+74AfYHw2RO+AKDUsfr33pnKS893vwCxpYte8hJVdy8A2Jl+9gKrEEAwYNim4ik2GxVbg2zmiy5sm+95JsbrjvG0ADASd5dEqPMGDivCBEB36AIMSYAkRB0c4zIoMGxW5ifmYiTGiuHPctVTAH4jxDN0nz9prBjNp3w31ccKwD2hAJGL0JFcIaEj/8n/DxsTX3URg+TT2739XWH/f6bG6ub9AYeQfbawholHAAAAAElFTkSuQmCC',
},
},
title: {
color: '#fff',
backgroundColor: '#68B92E',
borderWidth: 0,
fontSize: 14,
lineHeight: 20,
width: 20,
height: 20,
align: 'center',
borderRadius: 4,
},
},
在echarts中添加图片时,建议使用base64加密的图片。
问题二:echarts在用canvas渲染时导出时不显示
这个问题的本质和问题一是一样的,最简单的解决办法是用SVGRenderer渲染echarts图形,或者是自己再按照上述的方式对每一个echarts图形进行base64加密也可以的。
问题三:导出的文件挤在一起或者是进行了裁减
这个问题最主要的是在生成pdf的时候需要动态配置pdf是横向还是纵向的,如果要生成pdf的DOM元素的高度大于DOM元素的宽度,那应该配置成横向的,相反应该是纵向的。
if (size[0] > size[1]) {
pdf = new jsPDF('l', 'pt', size); // 横向
} else {
pdf = new jsPDF('p', 'pt', size); //纵向
}
问题四:生成的文件模糊
解决这个问题就是放大canvas图片。具体代码见最后一节。
关于分页
如果没有分页和打印需求,建议不要做分页。
如果非要分页,建议后端做,这种方法非常容易出现截断或者空白的问题。
angular中使用jspdf和html2canvas生成pdf文件
代码如下:
import { Injectable } from '@angular/core';
import jsPDF from 'jspdf';
import { Subject } from 'rxjs';
const html2Canvas: any = (window as any).html2canvas;
@Injectable({
providedIn: 'root',
})
export class JspdfService {
private _pdfLoadingSource = new Subject<boolean>();
pdfLoading$ = this._pdfLoadingSource.asObservable();
constructor() {}
loadingPdf(mission: boolean) {
this._pdfLoadingSource.next(mission);
}
async pdfHandler(targetDom: any, scale = 2) {
this.loadingPdf(true);
setTimeout(async () => {
// 将克隆节点动态追加到body后面。
const cloneDom = targetDom.cloneNode(true);
// 设置克隆节点的css属性,因为之前的层级为0,我们只需要比被克隆的节点层级低即可。
cloneDom.style.position = 'absolute';
cloneDom.style.top = '0';
cloneDom.style.index = '-1';
cloneDom.style.height = targetDom.height;
// 将克隆节点动态追加到body后面。
window.document.getElementById('pdf-con')!.appendChild(cloneDom);
const canvas = await html2Canvas(cloneDom, {
// 截取标签转换为canvas
canvas: this.createCanvas(cloneDom),
useCORS: true,
background: '#FFFFFF',
});
this.downloadPdf(canvas, scale);
cloneDom.hidden = true; // 隐藏之前的元素,更好对比
}, 1000);
}
downloadPdf(canvas: any, scale = 2) {
// 将canvas变成PDF并下载
const size = [canvas.width / scale, canvas.height / scale]; // pdf真实宽高
let pdf;
if (size[0] > size[1]) {
pdf = new jsPDF('l', 'pt', size); // 横向
} else {
pdf = new jsPDF('p', 'pt', size); // 纵向
}
const ctx = canvas.getContext('2d'),
pdfw = size[0],
pdfh = size[1],
imgHeight = Math.floor((pdfh * canvas.width) / pdfw); // 按实际界面可视区显示比例换算一页图像的像素高度
const page = document.createElement('canvas');
page.width = canvas.width;
page.height = canvas.height;
// 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
page!
.getContext('2d')!
.putImageData(
ctx.getImageData(
0,
0,
canvas.width,
Math.min(imgHeight, canvas.height)
),
0,
0
);
pdf.addImage(
page.toDataURL('image/jpeg', 1.0),
'JPEG',
0,
0,
pdfw,
Math.min(pdfh, (pdfw * page.height) / page.width)
); // 添加图像到页面
const name = '测试文件名';
pdf.save(`${name}.pdf`); // 保存PDF
this.loadingPdf(false);
}
createCanvas(target: any, scale = 2) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = target.offsetWidth * scale; // 画布实际宽度
canvas.height = target.offsetHeight * scale; // 画布实际高度
canvas.style.width = target.offsetWidth + 'px'; // 浏览器上显示的宽度
canvas.style.height = target.offsetHeight + 'px'; // 浏览器上显示的高度
context!.scale(scale, scale); // 等比缩放
return canvas;
}
}
调用方法:
this.jspdfService.pdfHandler(document.getElementById('需要导出的dom元素ID'));
转载自:https://juejin.cn/post/7379047318518399014