纯前端导出PDF
绘制PDF是一个复杂的工作,类似于图像处理。一般由服务端调用某些工具绘制,生成的PDF文字可复制。在少数的情况下(后端太懒),需要前端生成PDF。
首先面临的问题是,一般绘制PDF的库并不支持中文,需要导入庞大的字体文件,正常的绘制方法行不通。目前比较可行的办法是使用canvas将元素绘制出来再导出图像,最后将图像放入PDF中导出。这样生成的PDF基本上可以还原页面。
处理页面
为了得到最佳导出效果,应该为元素确定一个最佳导出宽度。如果导出元素中含有一些要隐藏的元素,应该将元素克隆并隐藏这些元素后再导出。使用html2canvas可以方便地使用canvas绘制元素。
注意:元素必须插入至dom树内渲染才能正常绘制,克隆的新元素可以绝对定位到视口并且将z-index设置为负数。html2canvas接收参数windowWidth设置最佳绘制宽度。
const canvas = await html2canvas(element, {
scrollX: 0, // 横向滚动距离
scrollY: 0, // 纵向滚动距离
windowWidth: 1200 // 以此窗口宽度渲染
});
生成的canvas的高度是随着元素在windowWidth下的高度而决定的。
通常情况下,我们生成的PDF文件具有绝对宽度,以A4纸宽度为例,需要计算出将图片经过缩放后的高度再插入,避免图片被拉伸。
// 标准A4纸宽度为595pt
const FILE_WIDTH = 595;
// 计算图片高度
const imageHeight = (canvas.height / canvas.width) * FILE_WIDTH;
需要特别注意的一点是,jspdf并不会根据插入元素的大小判断纸张规格。需要我们手动指定。创建PDF文档时,需要计算插入元素的高度是否大于宽度,如果是则使用p
模式(photo)表示纵向,反之则使用l
模式(landscape)表示横向。如果不指定会出现“正方形”文档的情况。
// 创建pdf文档
const doc = new jsPdf(imageHeight > FILE_WIDTH ? "p" : "l", "pt", [
imageHeight,
FILE_WIDTH
]);
// 插入图片
doc.addImage(
canvas.toDataURL("image/jpeg", 1.0), // canvas转图片
"JPEG",
0, // 横向定位
0, // 纵向定位
FILE_WIDTH,
imageHeight
);
// 导出pdf文档
doc.save(filename);
canvas转图片时,可以适当增加倍率以提升图片的清晰度。
canvas.toDataURL("image/jpeg", 3.0)
结论
纯前端导出PDF文档是行得通的。与后端导出相比,导出的文档主体为图片格式,文字无法复制;能显示绝大部分CSS样式,控制起来比较方便。
缺点是无法兼容老旧的浏览器;导出多页文档时页面长度很难控制,尤其是在一些动态页面中,往往会出现截断的情况。