首页/新闻资讯/正文详情

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

发布时间:2026/9/26 7:13:13 来源:云帆数科 栏目:资讯中心
前端导出PDF实战:html2pdf+jsPDF解决中文、图片与表格分页
简介这份资源面向需要在网页端实现 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 的方案源码不复杂难的是把这些边界场景一个个填平。希望帮到你。本文还有配套的精品资源点击获取

相关推荐

不用拆安全锁,用提示词和LoRA调教出好用的大模型
不用拆安全锁,用提示词和LoRA调教出好用的大模型

最近社区里“Abliteration”这个词被反复提起,配套的关键词是“解除大模型禁制”。好多人跑来问我能不能用这个思路,把模型那些“这个问题我暂时无法回答”的边边框框拆掉,让模型想说什么就说什么。作为一个常年泡在本地部署、微调、模型评测… · 2026/9/26 7:13:13

AI论文网站实测:8款工具在选题、查文献、写作降重中的表现与用法
AI论文网站实测:8款工具在选题、查文献、写作降重中的表现与用法

毕业季又快到了,朋友圈里陆续有人晒出论文进度,焦虑型选手已经通宵改了三稿。作为一个前前后后辅导过几十名本科生写毕业论文的人,我想认真聊一个被问了无数次的话题:市面上那么多AI论文网站,到底哪些真能帮上忙&#… · 2026/9/26 7:13:07

LeetCode 329 最长递增路径:C++ DFS+记忆化搜索详解
LeetCode 329 最长递增路径:C++ DFS+记忆化搜索详解

1. 先把这个题看透:题意、本质和常见误区LeetCode 329 这道题,我在面试里见过,也在刷题群里看人争论过。题目输入是一个二维整数矩阵,要求返回矩阵中最长递增路径的长度,C 接口签名是int longestIncreasingPath(vector… · 2026/9/26 7:13:07

UE5建模工具链实战:Modeling Mode与Geometry Script程序化生成指南
UE5建模工具链实战:Modeling Mode与Geometry Script程序化生成指南

1. 项目缘起与整体设计思路1.1 为什么要在 UE5 里折腾建模工具链第一次在 UE5 里看到 Modeling Mode 的时候,我其实没太当回事——毕竟做了这么多年场景,Max、Blender、Maya 哪个不比引擎里那套半成品顺手?直到有个项目要求做一套程序化生成的… · 2026/9/26 7:58:19

Windows 下 OpenClaw 接入飞书机器人:部署避坑与并发调优实战
Windows 下 OpenClaw 接入飞书机器人:部署避坑与并发调优实战

老实说,把 OpenClaw 和飞书打通这件事,我在 Windows 上整整折腾了一个周末。如果你也在搜 Windows 部署 OpenClaw、飞书机器人、AI 助手这类关键词,那这篇记录应该能帮你省下至少一个通宵。我尽量不说废话,把每一步踩过的坑、查过… · 2026/9/26 7:58:19

压图别再开PS了:Squoosh与Caesium让图片压缩三秒高效搞定
压图别再开PS了:Squoosh与Caesium让图片压缩三秒高效搞定

回想一下你第一次打开Photoshop是为了什么?我猜超过一半的人会回答:把图片变小。我自己也是这样,大学那会儿要传作业到课程平台,单张图片不能超过2MB,花了一晚上学会人生第一个"PS技能"——图像大小调整&… · 2026/9/26 7:58:19

测试工程师KPI怎么定?一套可落地的指标体系与绩效复盘指南
测试工程师KPI怎么定?一套可落地的指标体系与绩效复盘指南

干测试这一行,聊到KPI几乎人人都有话说。有人觉得测出来的bug越多功劳越大,有人觉得自己天天忙得要死最后绩效却一般,还有人被“线上出故障一票否决”压得喘不过气。我在测试行业待了十多年,从一线测试做到测试负责人,… · 2026/9/26 7:58:19

TensorSharp 支持 Jev 模式了:一次去噪,直接读出决策
TensorSharp 支持 Jev 模式了:一次去噪,直接读出决策

目录 先说 Jev 是什么 TensorSharp 里是怎么落地的 怎么调 HTTP 原生 .NET 接口能干什么 为什么快 4–5 倍 哪些事它明确不做 相关链接 2026年9月22日 vLLM 合并了 PR #57250,给 DiffusionGemma 加了一种 Jev 风格的结构化读取模式。我们跟得很快&#xff… · 2026/9/26 7:58:13

2026梦幻防红系统源码解析:抖音圆码跳转拦截与域名轮换实战
2026梦幻防红系统源码解析:抖音圆码跳转拦截与域名轮换实战

简介:这是一套面向社群运营、私域推广及小程序开发者的防红跳转系统源码,针对链接易被平台拦截、域名频繁被封的痛点,提供多域名池智能切换方案,官方宣称防拦截率可达99%以上。资源包共152个文件,约21.72MB&#xff0c… · 2026/9/26 7:58:13

数据库课后习题答案别硬背:当测试用例集刷,效率翻倍
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍

简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21

OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40

向下兼容与向上兼容:接口设计中的兼容性策略与工程实践
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践

一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码