最近在做后台系统的月报导出功能,需求很简单:把报表页面的图表和表格导出成 PDF,发给管理层看。
功能不大,但真到选型的时候我纠结了。导出 PDF 这件事,前端能做、后端也能做,光我扒过的方案就有三四种,每种的体积、清晰度、用户体验都不一样。踩了一圈之后,把主流的三种方案梳理一遍,给同样在选型的朋友一个参考。
后面对比的,就是这三类主流做法:
• 浏览器原生打印:调 window.print(),靠浏览器的"另存为 PDF"完成• 前端截图渲染:用 html2canvas 把页面截成图片,再塞进 jsPDF 生成 PDF • 后端无头浏览器渲染:后端用 Puppeteer 启个无头 Chrome,直接输出 PDF
三种方案没有绝对的好坏,关键看你更在意文件体积、用户体验还是渲染保真度。
方案一:浏览器原生打印
这是最传统的做法,也是我现在月报在用的方案。核心就一行代码:
// 触发浏览器打印对话框,用户选"另存为 PDF"window.print();剩下的工作全交给浏览器的打印引擎和 CSS。配合 @media print 写一套打印样式,控制哪些元素打印、哪些隐藏:
@media print { .no-print { display: none; } /* 隐藏按钮、菜单 */ table { page-break-inside: avoid; } /* 表格不被切断 */ .chart { break-inside: avoid; } /* 图表保持完整 */}优点:
• 文件体积小。PDF 里存的是矢量文字和图形,一份月报通常几十 KB • 文字清晰、可搜索、能复制,管理层直接贴到 PPT 里没问题 • 图表是浏览器原生渲染,不糊 • 实现成本低,不用引第三方库
缺点:
• 用户体验差一截。点"导出"弹出来的是打印对话框,用户要自己选"另存为 PDF",对不熟的人有学习成本 • 分页控制靠 CSS,跨浏览器表现不一致,调试是个耐心活 • 复杂布局(比如精确的封面、页眉页脚)做起来费劲
适合内部后台、报表类场景——体积小、文字可检索这两个优势太实在了。
方案二:前端截图渲染
如果"用户必须自己选另存为 PDF"这点体验不能忍,截图方案能解决。典型组合是 html2canvas + jsPDF:
// 把目标节点截图,转成图片塞进 PDFconst canvas = await html2canvas(document.querySelector('#report'));const pdf = new jsPDF({ unit: 'px', format: [canvas.width, canvas.height] });pdf.addImage(canvas.toDataURL('image/jpeg', 0.8), 'JPEG', 0, 0);pdf.save('月报.pdf');点一下直接下载,不用走打印对话框,体验上了一个台阶。
优点:
• 用户点"导出"直接拿到 PDF,流程顺 • 所见即所得,页面长啥样 PDF 就长啥样,不用调打印样式 • 纯前端实现,不依赖后端
缺点:
• 文件偏大。PDF 里存的是位图,一份月报轻松几 MB,图表越多越大 • 文字是图片,不能选、不能搜、复制不了 • 跨页大切大合容易切断图表,得手动按元素边界分页处理 • 分辨率不够时文字发虚,调高分辨率体积又暴涨
适合对体验要求高、对体积不敏感、内容以视觉为主的场景,比如海报、宣传页。报表类不太合适。
方案三:后端无头浏览器渲染
把渲染活儿全搬到后端,用 Puppeteer 启个无头 Chrome,加载页面直接输出 PDF:
// 后端启动无头浏览器,访问页面并导出 PDFconst browser = await puppeteer.launch();const page = await browser.newPage();await page.goto('https://app.example.com/report', { waitUntil: 'networkidle0' // 等图表加载完});await page.pdf({ path: '月报.pdf', format: 'A4', printBackground: true });await browser.close();前端只管发个请求拿文件,体验最干净。
优点:
• 用户点一下直接下载,体验最好 • 渲染保真度高,和原生打印一样是矢量文字,体积小、可搜索 • 分页、页眉页脚、水印都能精确控制 • 不受用户浏览器环境影响,输出一致
缺点:
• 服务端要装无头 Chrome,依赖大、体积几百 MB • Linux 部署有坑:缺中文字体时导出的 PDF 全是方块,得手动装 fonts-noto-cjk这类字体包• 渲染要排队,并发高时需要做任务队列 • 运维成本高,Chrome 版本、依赖、安全更新都得跟
适合 to C 产品、对体验和保真度都要求高的场景,但要掂量下运维成本。
三种方案怎么选
把上面的对比压成一张表:
我的选择思路很简单:
• 内部后台、报表:选原生打印。体积小、文字可搜,体验差那一点能接受 • to C、视觉为主:选截图渲染。所见即所得,体积大就大 • to C、要求高、有运维:选后端渲染。体验和保真都顶配
我现在这个月报导出用的就是原生打印。管理层看的报表,文字要能复制贴到 PPT 里,文件发邮件不能太大,这两点原生打印都满足。用户多点一步"另存为 PDF",换来几十 KB 的体积和可搜索文字,值。
写在最后
导出 PDF 没有银弹,三种方案各自踩在不同的痛点上。想清楚你最在意的是体积、体验还是保真度,答案就出来了。
选了原生打印之后,月报导出体积大、文字不能复制的问题就再也没出现过。你在用什么方案?评论区聊聊。
夜雨聆风