前端 HTML 转 PDF 的两种主流方案深度解析(2026 年版)
目前前端生成 PDF 最主流的两种方案是:
- 客户端方案:
html2canvas + jsPDF(或其封装库html2pdf.js) - 服务端方案:
Puppeteer / Playwright(Node.js 无头浏览器)
这两种方案几乎占据了 90% 以上的实际项目。下面从原理、优缺点、适用场景、核心代码、坑点与优化等维度进行深度对比。
一、核心对比表(快速决策)
| 维度 | 客户端方案(html2canvas + jsPDF) | 服务端方案(Puppeteer / Playwright) | 胜出方 |
|---|---|---|---|
| 实现难度 | ★☆☆☆☆(最简单) | ★★★☆☆(需后端) | 客户端 |
| 生成质量 | 中等(样式丢失常见) | 极高(接近浏览器打印效果) | 服务端 |
| 中文/字体支持 | 较差(需特殊处理) | 优秀(可加载本地字体) | 服务端 |
| 大文件 / 长页面 | 容易卡顿、崩溃 | 稳定 | 服务端 |
| 分页控制 | 弱(需 hack) | 强(支持 @page、页眉页脚) | 服务端 |
| 部署复杂度 | 零(纯前端) | 中等(需 Node 服务) | 客户端 |
| 性能压力 | 前端浏览器承担 | 后端服务器承担 | 看场景 |
| 安全性 | 高(客户端) | 中(后端需注意 HTML 注入) | 客户端 |
| 2026 年推荐场景 | 简单报表、导出预览、H5 小程序 | 正式合同、发票、复杂报告、打印级 PDF | — |
二、方案一:客户端 —— html2canvas + jsPDF(最常用)
原理
html2canvas把 DOM 元素渲染成 Canvas(像素级截图)jsPDF把 Canvas 转为 PDF 文件并下载
推荐库
- 直接用
html2pdf.js(封装版,最推荐) - 或手动组合
html2canvas + jsPDF
核心代码(Vue 3 + html2pdf.js)
<template>
<div ref="content">
<!-- 你的 HTML 内容 -->
<h1>发票标题</h1>
<table>...</table>
</div>
<button @click="exportPDF">导出 PDF</button>
</template>
<script setup>
import html2pdf from 'html2pdf.js'
const content = ref(null)
const exportPDF = () => {
const opt = {
margin: [10, 10, 10, 10],
filename: 'report.pdf',
image: { type: 'jpeg', quality: 0.98 },
html2canvas: {
scale: 2, // 清晰度(2倍最合适)
useCORS: true, // 跨域图片
letterRendering: true
},
jsPDF: {
unit: 'mm',
format: 'a4',
orientation: 'portrait'
}
}
html2pdf().set(opt).from(content.value).save()
}
</script>
优点
- 零后端,部署最简单
- 适合 H5、小程序、内部工具
致命缺点(2026 年仍未彻底解决)
- 复杂 CSS3(flex、grid、阴影、渐变)容易变形
- 中文字体模糊或缺失
- 超长页面容易内存溢出
- 分页控制极差(经常出现表格断行、页眉页脚难处理)
优化技巧:
- 使用
scale: 2提升清晰度 - 提前把需要导出的内容克隆到一个隐藏的 div 中,专门优化样式
- 对于表格推荐使用
jspdf-autotable插件单独处理
三、方案二:服务端 —— Puppeteer / Playwright(质量之王)
原理
启动无头浏览器(Headless Chrome / Firefox / WebKit),加载完整 HTML 页面后,直接调用 page.pdf() 生成 PDF。
2026 年推荐:Playwright(已全面超越 Puppeteer)
Playwright 优势:多浏览器支持、自动等待、API 更现代、稳定性更高。
核心代码(Node.js + Express)
// pdfService.js
const { chromium } = require('playwright')
async function htmlToPdf(htmlContent) {
const browser = await chromium.launch({ headless: true })
const page = await browser.newPage()
// 关键:设置视口和打印样式
await page.setContent(htmlContent, { waitUntil: 'networkidle' })
const pdfBuffer = await page.pdf({
format: 'A4',
printBackground: true,
margin: { top: '15mm', bottom: '15mm', left: '10mm', right: '10mm' },
displayHeaderFooter: true,
headerTemplate: '<div style="font-size:10px;width:100%;text-align:center;">页眉</div>',
footerTemplate: '<div style="font-size:10px;width:100%;text-align:center;">第 <span class="pageNumber"></span> 页</div>'
})
await browser.close()
return pdfBuffer
}
前端调用示例
const res = await fetch('/api/export-pdf', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ html: document.getElementById('content').outerHTML })
})
const blob = await res.blob()
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'report.pdf'
a.click()
优点
- 生成效果几乎和浏览器打印一致
- 原生支持
@page、page-break-before/after分页控制 - 中文字体完美(可加载本地字体或系统字体)
- 支持页眉页脚、加密、水印、目录等高级功能
缺点
- 需要部署 Node 服务
- 并发高时消耗服务器资源(可使用 puppeteer-cluster / playwright-cluster 做池化)
四、最终推荐(2026 年真实选择)
- 推荐客户端方案:内部工具、简单报表、H5 活动页、快速 MVP
- 强烈推荐服务端方案:合同、发票、正式报告、需要精确分页和美观样式的场景
- 混合方案(最优解):前端负责编辑预览 + 服务端负责最终生成 PDF(目前大厂主流做法)
你现在要做的是哪类 PDF?
- 简单数据报表?
- 合同/发票类正式文件?
- 还是需要页眉页脚 + 精确分页的复杂报告?
告诉我具体需求,我可以给你对应方案的最优完整代码模板。