HomeAuthorContactSearch

纯前端导出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样式,控制起来比较方便。

缺点是无法兼容老旧的浏览器;导出多页文档时页面长度很难控制,尤其是在一些动态页面中,往往会出现截断的情况。

附上demo:https://mywsq.github.io/pdf-generate-demo