小王盯着屏幕,感觉头发又要掉几根。
"王哥,又在跟打印、导出 PDF 较劲呢?",刚入职的小李端着咖啡,幸灾乐祸地问。
"别提了!甲方爸爸这次要求更变态,不仅要指定区域打印、导出 PDF、在线预览,还要保证和页面上的 DOM 元素一模一样!",小王揉了揉太阳穴,"这简直是逼我用 html2canvas + jsPDF
搞像素魔法
啊!"
"王哥,这套路我都熟透了!",小李喝了口咖啡,慢悠悠地说,"先用 html2canvas 把指定的 DOM 元素转换成 canvas,然后用 jsPDF 的 addImage 方法把 canvas 生成的图片添加到 PDF 里,最后根据 type 参数判断是下载、打印还是预览,对吧?"
"没错,流程就是这么个流程",小王无奈地叹了口气,"但问题是,理想很丰满,现实很骨感!html2canvas 生成的 canvas,要么模糊得像打了马赛克,要么就是颜色失真;jsPDF 的 addImage 方法更坑,位置、大小、比例,稍微一不对,整个 PDF 就乱成一锅粥!这哪是像素魔法
,简直是像素灾难
!"
"王哥,你说的这些坑,我都踩过!",一直默默研究的小张突然抬起头,推了推眼镜,"我最近发现,想要用 html2canvas + jsPDF 完美'复刻' DOM,关键在于细节!"
html2canvas:配置是关键,细节决定成败
"首先,html2canvas 的配置至关重要!",小张开始讲解,"useCORS: true
必须加上,否则跨域图片直接 GG。scale
参数也不能乱用,要根据实际情况调整,才能保证 canvas 的清晰度。如果 DOM 元素里有复杂的 CSS 样式,比如 box-shadow
、border-radius
还要开启 foreignObjectRendering: true
,才能正确渲染。"
jsPDF:精雕细琢,方能成就完美
"然后,jsPDF的addImage 方法也要精雕细琢!",小张继续说道,"addImage
的坐标和宽高,必须和 canvas 的实际尺寸对应,否则就会出现拉伸、变形。如果 PDF 需要分页,还要计算好每页的高度,避免内容被截断。"
性能优化:让"像素魔法"飞起来
"最后,性能优化也不能忽视!",小张补充道,"html2canvas 转换 DOM 的时候,会遍历整个 DOM 树,非常耗时。可以尝试使用 ignoreElements
选项,忽略一些不必要的元素,或者使用 onclone
钩子,在转换之前对 DOM 进行一些预处理,减少转换的复杂度。"
"张哥,听君一席话,胜读十年书啊!",小李佩服得五体投地。
另辟蹊径:Print-JS,另一种选择
"其实,除了 html2canvas + jsPDF,还有一些其他的选择",小王沉吟道,"比如 Print-JS
,它可以直接打印指定的 DOM 元素,而且样式也比较接近原始页面。但是Print-JS 对于一些复杂的布局,可能无法完美支持。"
总结:没有银弹,只有最合适的方案
"所以,我们要根据实际情况,选择最合适的方案",小王总结道,"对于简单的页面,Print-JS 可能更便捷;对于需要高度还原的复杂页面,html2canvas + jsPDF 才是王道。关键是要掌握各种像素魔法
,才能将 DOM 完美复刻
到 PDF!"
相信各位看官已经对 html2canvas + jsPDF 这套方案有了更深入的了解。那么,useCORS
、scale
、foreignObjectRendering
这些 html2canvas 的配置参数该如何设置?jsPDF 的 addImage
方法又有哪些技巧?
接下来,就让我们一起深入研究这些问题,看看如何用 html2canvas + jsPDF 这套像素魔法
,将 DOM 完美"复刻"到 PDF,实现打印、导出、预览pdf的终极目标!
1.安装依赖
html2canvas:1.4.1
jspdf:2.5.1
print-js:1.6.0
js
npm install html2canvas jspdf print-js --save
2. 创建utils.js
文件
js
import print from 'print-js';
import html2Canvas from 'html2canvas';
import JsPDF from 'jspdf';
const htmlToPdf = ({
id, // 需要转为pdf的html容器的id 必填
title, // download下载时文件的名称 选填
type, // 类型 download 下载,print 打印,preview 预览
canvasParams, // 画布(html2Canvas)的相关参数
printParams, // 打印(print-js)的相关参数
pdfPageCallback // pdf的分页的自定义方法,处理完分页需要把pdf对象返回 选填
}) => {
return new Promise((resolve, reject) => {
html2Canvas(document.querySelector(`#${id}`),{
taintTest: false, // 在渲染前测试图片
background: "#fff",
useCORS: true, // 开启跨域配置
foreignObjectRendering: true, //
scale: window.devicePixelRatio
}, (canvasParams || {})).then((canvas) => {
let PDF = new JsPDF('', 'pt', 'a4');
if(typeof pdfPageCallback === 'function') {
PDF = pdfPageCallback(canvas, JsPDF);
} else {
// 内容的宽度
let contentWidth = canvas.width;
// 内容高度
let contentHeight = canvas.height;
// 一页pdf显示html页面生成的canvas高度,a4纸的尺寸[595.28,841.89];
let pageHeight = (contentWidth / 592.28) * 841.89;
// 未生成pdf的html页面高度
let leftHeight = contentHeight;
// 页面偏移
let position = 0;
//a4纸的尺寸[595.28,841.89],html页面生成的canvas在pdf中图片的宽高
let imgWidth = 595.28;
let imgHeight = (592.28 / contentWidth) * contentHeight;
// canvas转图片数据
let pageData = canvas.toDataURL('image/jpeg', 1.0);
// 判断是否需要分页
if (leftHeight < pageHeight) {
PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight);
} else {
while (leftHeight > 0) {
PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight);
leftHeight -= pageHeight;
position -= 841.89;
if (leftHeight > 0) {
// 新增一页
PDF.addPage();
}
}
}
}
const blob = PDF.output('blob');
const fileURL = URL.createObjectURL(blob);
switch (type) {
case 'download':
PDF.save(`${title || new Date().getTime()}.pdf`);
break;
case 'print':
printJS({
printable: fileURL,
type: 'pdf',
header: null,
},(printParams || {})));
break;
case 'preview':
window.open(fileURL, '_bank');
break;
default:
break;
}
resolve({ blob, fileURL });
}).catch(err => {
reject(err);
});
});
};
export default htmlToPdf;
3.具体使用
js
import htmlToPdf from '@/utils.js'
// 下载pdf
const data = {
id:'container',
type:'download',
title:'PDF下载'
}
// 打印pdf
const data = {
id:'container',
type:'print',
}
// 预览pdf
const data = {
id:'container',
type:'preview',
}
htmlToPdf(data);