前一阵子我们内部系统改版,运营同事提了个需求:“能不能把订单明细页一键存成PDF发给客户?”后台页面上那一堆查询条件、表格、图表,总不能让人家手动Ctrl+P然后挑打印机。我第一反应就是用前端方案做,html2canvas加jspdf,这俩组合在前端导出PDF这个场景里几乎是标配。折腾了两天,踩了几个不大不小的坑,把思路和代码整理出来,给同样要做这个功能的朋友做个参考。
这套方案本质上是“先截图,再贴图”:用html2canvas把指定DOM区域渲染成一张canvas位图,再用jspdf新建一个pdf文档,把这张图按A4页面尺寸切分贴进去。它不需要后端参与,纯前端就能完成,适用于报表导出、工单保存、长图文存档这类场景。你可以直接拿去做订单、统计报表、文章内容、甚至简历预览的导出功能。如果你也在纠结怎么做“干净、清晰、不乱码”的前端导出PDF,这篇内容应该能帮你少走好几个弯路。
1. 项目背景:为什么需要把页面导出成PDF
1.1 前端导出PDF的主流方案对比
先说结论:前端导出PDF的方案挺多,但真正适合“把整个页面视觉还原出来”的,html2canvas加jspdf是最简单粗暴的一种。
我梳理一下常见的几条路:
- window.print() + 浏览器打印:这是最轻量的方案,直接把页面调成打印样式,让用户自己选“另存为PDF”。但它的缺点是样式不受控,不同浏览器打印出来的东西差别很大,而且用户必须手动操作,不适合“一键导出”这种动作。
- 纯jspdf写文本和表格:用jspdf的text、autoTable这些API逐行绘制内容。好处是输出的是矢量PDF,清晰度高、文件小,但代价是你得把页面里的DOM内容手动解析成坐标和行,复杂页面根本干不动,表格、图表、带样式的块状结构会写到怀疑人生。
- html2canvas + jspdf:把DOM先画成图片,再放进PDF。优点是不用管页面内部结构,最终看到什么就导出什么,还原度最高;缺点是输出的是位图,放大看会有轻微模糊,而且html2canvas对部分CSS属性支持不完整。
实际项目里,尤其是管理后台、报表系统,视觉还原度往往比“矢量放大不糊”更重要。我最终选了html2canvas + jspdf,理由很简单:快,稳,不用为每个页面单独写绘制逻辑。你要导出的内容只要在页面上能渲染出来,这套方案基本就能给你存下来。
1.2 这套组合的根本思路
把html2canvas和jspdf放在一起,核心思路就是“把页面当图画”。
html2canvas会把指定DOM节点“拍一张快照”,生成一个canvas元素。这个canvas就是一张位图,包含了节点内所有元素的视觉样式、布局、文字、图片、背景,基本上所见即所得。然后jsPDF负责创建PDF文件,提供一个叫addImage的方法,把canvas转成图片数据后贴到PDF页面上。
关键来了:PDF的页面尺寸是固定的(比如A4是210mm×297mm),但页面内容高度是任意的。所以你不能简单地把一整张长图直接塞进一页PDF里,否则内容会被缩放得小到看不清。正确做法是把长图按A4比例切成若干段,每段放一页,这叫“分页贴图”。具体怎么切,后面我会给完整代码。
这里必须提醒你一句:html2canvas不是浏览器原生截图,它是自己遍历DOM节点,逐个把样式绘制到canvas上。所以它注定不能100%还原CSS,尤其是filter、backdrop-filter、混合模式、部分flex/grid布局、较新的CSS特性,都可能在截图时丢失或错乱。提前做好这个心理预期,后面遇到样式问题就不会慌了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心原理:html2canvas和jspdf到底在做什么
2.1 html2canvas的截图机制
html2canvas的工作过程比很多同学想象得要复杂。它不是调用了什么浏览器底层截图API,而是从传入的DOM节点出发,递归遍历所有子节点,读取它们的样式信息(位置、大小、颜色、字体、边框、阴影等),再用canvas 2D API把这些信息逐项绘制出来。
这个机制决定了两个直接影响使用的结论:
第一,跨域图片默认画不上去。因为canvas绘制跨域图片会污染画布,html2canvas为了安全默认不允许加载跨域图片。解决办法是img标签上加上crossOrigin="anonymous",同时后端返回Access-Control-Allow-Origin头,或者直接转成base64。
第二,依赖样式计算结果的准确性。如果页面用rem、vw这类相对单位,或者依赖JS计算动态尺寸,截图时可能拿到的是未布局完成的状态。我还没见过哪个项目不需要针对导出场景做一点样式微调的。
html2canvas还提供了 onclone 回调。它会在内部克隆一份DOM用于截图,你可以在克隆文档里临时调整样式,比如隐藏筛选区、把过长内容展开、给目标容器加背景色。这些改动只影响截图,不影响真实页面,非常实用。
2.2 jsPDF的页面模型和addImage
jsPDF定义了一个文档对象,你可以理解成一张无限长的白纸,通过addPage新增页面,通过addImage把图片放上去。它的核心单位有pt、mm、px三套,默认是pt。A4纸在pt单位下是595.28 × 841.89,在mm单位下是210 × 297。
addImage的签名经常有人搞混,我重点说一下:
javascript复制pdf.addImage(imageData, format, x, y, width, height, alias, compression)
- imageData:可以是canvas对象、图片base64、或者图片的dataURL
- format:图片格式,传入canvas对象时填'PNG'即可
- x、y:图片在页面上的左上角坐标
- width、height:图片在页面上的宽高,单位跟随创建pdf时设定的unit
注意这两个width和height,它不是图片本身的像素宽高,而是你要把它“贴”在页面上的物理尺寸。如果原图很大,贴的时候width和height很小,图片会被压缩显示,等于在PDF里缩小了,但图片本身并没有变清晰。所以想要导出清晰,要从源头
