资讯动态

前端导出PDF实战:html2pdf+jsPDF解决中文、图片与表格分页

发布时间:2026/9/26 7:13:17 来源:尧图企业网站定制
简介这份资源面向需要在网页端实现 HTML 转 PDF 的开发者尤其是前端与全栈工程师解决传统方案依赖浏览器插件、中文乱码、图片与表格丢失等痛点。压缩包共 10 个文件约 1.76MB以 5 个 js 脚本为核心搭配 2 个 html 示例页面、1 个 css 样式、1 个 png 图标与 1 个 ttf 中文字体覆盖转换逻辑、字体加载与页面演示的完整链路。资源基于 jsPDF 与 html2canvas 实现无需任何插件内置转换好的中文字体及字体转换工具只需 6 行代码即可将任意网页对象所见即所得地矢量输出为 PDF并完整支持中文、图片与表格。目前已有 526 人学习下载读者可直接获得可运行的源码、字体转换脚本与示例页面快速集成到自己的项目中省去字体处理与兼容性调试的重复工作。1. 前端导出 PDF 的真实困境为什么我最后选了 html2pdf jsPDF 这套组合做过管理后台的都知道导出 PDF 这个需求看着简单真上手就是一部血泪史。用浏览器自带的window.print()吧用户得自己在打印对话框里选「另存为 PDF」体验割裂不说页眉页脚还带着浏览器自己的 URL 和日期产品经理第一个不答应。上wkhtmltopdf这类服务端方案呢又得单独部署一个二进制环境Docker 镜像直接胖一圈运维那边脸色不太好看。更别提中文了——服务端方案十有八九因为缺字体导出来全是方块用户截图发群里你当场社死。所以我把目光收回到纯前端方案html2pdf.js打底底层是html2canvas负责把 DOM 渲染成图片jsPDF负责把图片塞进 PDF 页面。这套组合不需要任何浏览器插件不依赖后端支持中文、图片、表格一个script标签就能跑起来。它适合谁适合那些后台系统里需要「一键导出报表」「导出订单详情」「导出对账单」的团队尤其是页面本身就是 HTML 表格渲染出来的场景。你不需要重新写一套 PDF 模板直接把要导出的那块 DOM 丢进去就行。代价也有——它是截图式导出文字不可选中、不可搜索文件体积比矢量方案大。这个取舍后面会细说先把能跑通的路径铺出来。2. 环境搭建与最小可运行示例从 CDN 引入到第一张 PDF2.1 依赖选型为什么是 html2pdf.js 而不是裸用 jsPDF裸用jsPDF的话你得自己调html2canvas把 DOM 转成 canvas再算图片尺寸、算分页位置、算缩放比例一套下来两百行代码起步还容易在分页处把表格拦腰截断。html2pdf.js本质上是把这条链路封装成了一个可配置的管道它内部维护了「渲染 → 切片 → 分页 → 输出」的流程你只需要告诉它页面边距、纸张大小、缩放比例这几个关键参数。常见做法是直接引 CDN省去构建工具的配置成本。我一般会锁定版本号避免某天 CDN 上的包更新后行为漂移!-- 锁定版本避免 CDN 更新导致导出行为变化 -- script srchttps://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js/script注意这里引的是html2pdf.bundle.min.jsbundle 版本已经把html2canvas和jsPDF打进去了不需要你再单独引这两个库。如果你用 npm 管理依赖对应的是npm install html2pdf.js0.10.1然后在模块里import html2pdf from html2pdf.js。版本号建议锁死这个库更新不算频繁但 0.9 到 0.10 之间 API 有过调整混用文档容易踩坑。2.2 最小可运行示例一个带中文和表格的导出先给一个能直接复制运行的完整例子页面里放一个表格点按钮导出!DOCTYPE html html langzh-CN head meta charsetUTF-8 title导出示例/title script srchttps://cdnjs.cloudflare.com/ajax/libs/html2pdf.js/0.10.1/html2pdf.bundle.min.js/script /head body !-- 要导出的内容区域给它一个 id 方便定位 -- div idreport h22024 年第一季度销售报表/h2 table border1 cellspacing0 cellpadding6 stylewidth:100%;border-collapse:collapse; thead trth区域/thth销售额万元/thth同比/th/tr /thead tbody trtd华东/tdtd1280/tdtd12%/td/tr trtd华南/tdtd960/tdtd8%/td/tr trtd华北/tdtd1105/tdtd-3%/td/tr /tbody /table p备注以上数据为含税口径统计截止 2024-03-31。/p /div button onclickexportPDF()导出 PDF/button script function exportPDF() { const element document.getElementById(report); const opt { margin: 10, // 页面边距单位 mm filename: 销售报表.pdf, // 导出文件名支持中文 image: { type: jpeg, quality: 0.98 }, // 图片格式与质量 html2canvas: { scale: 2, useCORS: true }, // 渲染倍率与跨域 jsPDF: { unit: mm, format: a4, orientation: portrait } // 纸张配置 }; html2pdf().set(opt).from(element).save(); } /script /body /html这段代码的逻辑链路是html2pdf()创建一个实例.set(opt)注入配置.from(element)指定要渲染的 DOM 节点.save()触发下载。margin: 10表示四边各留 10mmscale: 2是把 canvas 按 2 倍分辨率渲染这样导出的图片在高分屏或打印时不会糊。useCORS: true是给图片资源用的如果表格里有跨域图片不加这个会渲染失败。参数怎么改如果导出的是横向宽表把orientation改成landscape如果内容很长需要控制分页margin可以设成数组[10, 5, 10, 5]分别对应上右下左。quality调到 1 文件会变大0.92 到 0.98 之间是清晰度和体积的平衡点我一般用 0.95。跑通这个例子你已经能覆盖 80% 的常规导出需求了。但真实项目里中文乱码、图片空白、表格跨页断裂这三个问题几乎必然出现下一章逐个拆。3. 中文、图片、表格三大硬骨头参数配置与渲染链路拆解3.1 中文显示为什么截图方案天然不怕中文很多人被服务端 PDF 方案坑过一提到中文就紧张。这里要说清楚一个反直觉的点html2pdf这套截图方案恰恰是中文最省心的方案。因为html2canvas是把浏览器已经渲染好的 DOM 画成 canvas浏览器怎么显示中文canvas 就怎么画字体是系统字体根本不存在「PDF 里没有中文字体」这个问题。你页面上中文正常导出来就正常。真正会导致中文出问题的是两个场景。第一个是字体加载时机如果你用了自定义 Web Font而导出时字体还没加载完canvas 会拿 fallback 字体去画导出来字形就变了。解决办法是导出前等字体就绪// 导出前确保自定义字体已加载完成 async function exportWithFontReady() { if (document.fonts document.fonts.ready) { await document.fonts.ready; // 等待所有字体加载完毕 } const element document.getElementById(report); html2pdf().set({ margin: 10, filename: 报表.pdf, html2canvas: { scale: 2, useCORS: true }, jsPDF: { unit: mm, format: a4, orientation: portrait } }).from(element).save(); }document.fonts.ready返回一个 Promise所有字体加载完才 resolve。这个等待很关键尤其是首屏刚加载就点导出的场景不加这行经常导出「半成品字体」。第二个场景是文件名中文。filename参数直接写中文没问题现代浏览器都支持。但如果你的下载逻辑走的是后端Content-Disposition那编码问题就跑到后端去了跟前端库无关。3.2 图片导出跨域、空白与清晰度的三个开关图片是导出翻车的高发区。最常见的现象是页面上图片好好的导出来是个空白框。原因几乎都是跨域——html2canvas要把图片画进 canvas如果图片来自不同域名且没开 CORScanvas 会被污染toDataURL直接抛异常或者画成空白。解决分两步。第一步图片服务器必须返回Access-Control-Allow-Origin头这个前端改不了得让后端或运维配。第二步前端开启useCORShtml2canvas: { scale: 2, // 渲染倍率2 倍适合打印3 倍文件偏大 useCORS: true, // 允许跨域图片参与渲染 allowTaint: false, // 保持 falsetrue 会污染 canvas 导致导出失败 logging: false // 关掉控制台日志生产环境清爽些 }allowTaint这个参数要特别说设成true时跨域图片能画上去但 canvas 被标记为「污染」后续toDataURL会失败结果就是导出空白。所以宁可useCORS: trueallowTaint: false让跨域图片要么正常加载要么明确报错别用 taint 这种掩耳盗铃的写法。如果图片是 base64 内联的那完全没有跨域问题这也是为什么很多报表系统会把图表先转成 base64 再塞进 DOM。清晰度方面scale是核心。scale: 1在普通屏够用但打印或放大就糊scale: 2是甜点值scale: 3文件体积会明显上涨一张 A4 报表可能从 200KB 涨到 800KB。我一般默认 2用户明确要打印质量才上 3。3.3 表格分页pagebreak 配置与断裂修复表格跨页断裂是截图方案最尴尬的地方。因为整块 DOM 被渲染成一张长图然后按 A4 高度切片切到哪算哪一行文字可能被拦腰截断。html2pdf提供了pagebreak配置来缓解const opt { margin: 10, filename: 长表格.pdf, image: { type: jpeg, quality: 0.95 }, html2canvas: { scale: 2, useCORS: true }, jsPDF: { unit: mm, format: a4, orientation: portrait }, pagebreak: { mode: [avoid-all, css, legacy], // 分页模式组合 before: .page-break-before, // 这些元素前强制分页 after: .page-break-after, // 这些元素后强制分页 avoid: tr, img // 这些元素尽量不跨页 } };mode里的avoid-all会尽量不让任何元素跨页css会读取元素上的page-break-*样式legacy兼容老写法。avoid: tr, img是最实用的一条——告诉引擎别把表格行和图片切开。但要注意avoid-all在内容特别长时可能导致大片留白因为引擎为了不切断元素会提前分页。我的经验是表格行用avoid: tr大段文字别用avoid-all否则每页底部可能空一大块。还有一个更可控的做法手动在表格里插入分页标记。比如每 20 行插一个div classpage-break-before/div配合before: .page-break-before分页位置就完全由你掌控。这比让引擎自动算靠谱得多尤其是财务对账单这种对分页位置有硬要求的场景。4. 避坑与排查导出空白、体积爆炸、移动端失败的现场记录4.1 导出后 PDF 是空白页现象点击导出PDF 生成了但打开一看全是白页或者只有页眉没有内容。原因九成是html2canvas渲染时目标元素不可见或尺寸为 0。常见触发点是元素被display: none隐藏、在弹窗里还没完全展开、或者父容器overflow: hidden把内容裁掉了。另一个原因是跨域图片污染了 canvas导致toDataURL静默失败。解决导出前确保目标元素在视口内且可见。如果元素藏在弹窗里先让它显示出来再导出。我一般会加一个临时容器把要导出的内容克隆一份放到页面底部可见区域导出完再删掉// 克隆到可见区域再导出规避隐藏元素渲染为空的问题 function safeExport(sourceId, filename) { const source document.getElementById(sourceId); const clone source.cloneNode(true); clone.style.position fixed; clone.style.left -9999px; // 移出视口但保持渲染 clone.style.top 0; clone.style.width source.offsetWidth px; document.body.appendChild(clone); html2pdf().set({ margin: 10, filename: filename, html2canvas: { scale: 2, useCORS: true }, jsPDF: { unit: mm, format: a4, orientation: portrait } }).from(clone).save().then(() { document.body.removeChild(clone); // 导出完清理避免污染 DOM }); }注意left: -9999px而不是display: none后者会让元素不渲染canvas 拿不到内容。4.2 文件体积异常大现象一张简单报表导出来 5MB 以上用户下载慢邮件附件还超限。原因scale设太高、image.type用了 png、或者页面里有大尺寸背景图。png 是无损格式截图内容稍微复杂一点体积就爆炸。解决把image.type改成jpegquality设 0.92 到 0.95scale从 3 降到 2。这三刀下去体积通常能砍掉 60% 以上。如果页面有纯色背景导出前临时把背景图去掉也能省不少。4.3 移动端导出失败或卡死现象PC 上正常手机上点导出没反应或者浏览器直接卡死崩溃。原因移动端内存有限scale: 2渲染一张长图canvas 尺寸可能超过手机浏览器的上限iOS Safari 对 canvas 面积有硬限制大约 1670 万像素。内容一长直接超限。解决移动端把scale降到 1 甚至 0.8并且限制单次导出的内容长度。如果表格特别长改成后端分页导出或者提示用户「内容较长建议在电脑上导出」。这是硬件限制前端绕不过去别硬扛。4.4 中文文件名在部分环境变成乱码现象Chrome 正常某些内嵌浏览器或旧版环境下载下来文件名是一串百分号编码。原因filename参数最终走的是 Blob 下载不同浏览器对download属性的编码处理不一致。解决如果目标环境可控直接用中文如果面向不确定的浏览器用英文文件名加时间戳或者在后端做文件名编码。这个属于环境差异没有前端万能解。4.5 导出内容样式丢失现象页面上有圆角、阴影、渐变导出来变成直角、纯色。原因html2canvas对部分 CSS3 特性支持不完整尤其是box-shadow、filter、复杂的linear-gradient。解决导出前给目标元素加一个「打印样式」类把不支持的样式降级成纯色边框。别指望截图方案 100% 还原视觉效果接受这个边界把关键信息导对就行。5. 进阶技巧批量导出、分页控制与导出前的自检清单5.1 批量导出多个模块到一个 PDF有时候需求不是导一个表格而是把页面上好几个卡片依次导进同一个 PDF。html2pdf支持链式.toPdf()操作可以往同一个 PDF 实例里追加内容// 把多个模块依次追加到同一个 PDF async function exportMultiSections(sectionIds, filename) { const worker html2pdf().set({ margin: 10, filename: filename, image: { type: jpeg, quality: 0.95 }, html2canvas: { scale: 2, useCORS: true }, jsPDF: { unit: mm, format: a4, orientation: portrait }, pagebreak: { mode: [css, legacy], avoid: tr } }); for (let i 0; i sectionIds.length; i) { const el document.getElementById(sectionIds[i]); if (i 0) { await worker.from(el).toPdf().get(pdf).then(pdf { // 第一个模块记录当前页数 window.__pdfPageCount pdf.internal.getNumberOfPages(); }).save(); } } }实际项目里更稳的做法是先把所有模块克隆到一个隐藏容器里拼成一个大 DOM再一次性导出。链式追加在分页衔接处容易出现半页空白拼接方案反而更可控。我一般用拼接代码简单分页也连续。5.2 导出前的自检清单导出功能上线前我会固定跑一遍这几项基本能拦住 90% 的线上问题检查项检查方法不合格表现中文渲染导出含中文的标题和表格出现方块或字形错乱跨域图片页面放一张外域图片后导出图片位置空白长表格分页造 100 行以上表格导出行被拦腰截断文件体积看导出文件大小单页超过 2MB移动端手机浏览器点导出无响应或崩溃文件名检查下载文件名乱码或百分号编码这张表我贴在项目 wiki 里每次改动导出相关代码就过一遍。看着土但比出事后再回滚强。5.3 一个我踩过的坑动态内容导出时机最后说个血泪经验。有次做图表导出页面上是 ECharts 渲染的图用户点导出结果导出来图表是空的。排查半天发现ECharts 是异步渲染的用户点按钮那一刻动画还没结束canvas 里图表还没画完。后来我在导出前加了setTimeout等 300ms或者监听 ECharts 的finished事件再触发导出。从那以后我每次做导出功能都强制走一遍「等渲染完成 → 等字体就绪 → 再导出」这个顺序不管内容是不是动态的。多等几百毫秒换来的是导出结果稳定比用户投诉后再补要划算得多。这套 html2pdf jsPDF 的方案源码不复杂难的是把这些边界场景一个个填平。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑