html-pdf、html2pdf.js、jsPDF 和 pdfmake 是用于在 Web 应用中生成 PDF 文档的 JavaScript 库,但它们在架构、能力和运行环境上差异显著。html-pdf 是一个仅限 Node.js 的包,使用 PhantomJS 将 HTML 转换为 PDF,目前已官方弃用。html2pdf.js 在浏览器中运行,利用 html2canvas 和 jsPDF 将 DOM 元素捕获为 PDF。jsPDF 是一个底层 PDF 创建库,支持浏览器和 Node.js 环境,允许通过代码绘制文本、图像和图形,但不直接渲染 HTML。pdfmake 则采用声明式的文档定义方式生成 PDF,对布局、表格和样式有强大支持,同样兼容浏览器和 Node.js。
在 Web 应用中生成 PDF 是常见需求 —— 无论是生成发票、报表还是可打印摘要。但选择哪个库,取决于你的输入源(HTML 还是结构化数据)、运行环境(浏览器还是 Node.js)以及对输出质量的要求。下面我们从工程实践角度深入分析。
html-pdf 曾是一个流行的 Node.js 包,通过 PhantomJS 渲染 HTML 生成 PDF。但该包现已在 npm 和 GitHub 上被官方标记为弃用,作者明确建议迁移到 Puppeteer 等现代库 。PhantomJS 本身早已停止维护,这意味着无法支持现代 CSS(如 Flexbox、Grid),且存在未修复的安全漏洞 。
它只能在 Node.js 中运行,无法用于浏览器环境。因此,新项目绝不应使用 html-pdf。
// html-pdf:已弃用的用法(仅 Node.js)
const pdf = require('html-pdf');
const html = '<h1>Hello World</h1>';
pdf.create(html).toFile('./output.pdf', (err, res) => {
if (err) return console.log(err);
console.log(res.filename);
});
💡 迁移建议:Node.js 环境下使用 Puppeteer 或 Playwright 进行现代、可靠的 HTML 到 PDF 转换。
html2pdf.js 完全在浏览器中运行,旨在将任意 DOM 元素转换为 PDF。其内部结合了两个库 :
<canvas>(将文本和布局光栅化为图像)这意味着输出是基于像素的图像,而非矢量文本。放大后可能模糊,文件体积也较大。但它的优势是使用极其简单,能快速捕获 UI 的视觉状态。
// html2pdf.js:将 DOM 元素转为 PDF
import html2pdf from 'html2pdf.js';
const element = document.getElementById('invoice');
html2pdf().from(element).save();
你可以通过配置设置边距、文件名或页面尺寸:
html2pdf()
.set({ margin: 10, filename: 'report.pdf', image: { type: 'jpeg', quality: 0.95 } })
.from(element)
.save();
✅ 适用场景:需要“导出为 PDF”按钮,且希望结果与浏览器中显示一致。
❌ 不适用场景:需要可搜索文本、小文件体积或精确排版。
jsPDF 提供通过代码绘制 PDF 的底层 API。它不理解 HTML 或 CSS —— 你需要使用坐标手动定位文本、图像和图形。这赋予你完全控制权,但也意味着布局逻辑需自行实现。
它支持浏览器和 Node.js(在 Node 中需配合 canvas 或图像插件),并生成矢量文本、可搜索的 PDF 。
// jsPDF:通过代码创建 PDF
import { jsPDF } from 'jspdf';
const doc = new jsPDF();
doc.setFontSize(22);
doc.text('Hello world!', 20, 20);
doc.save('output.pdf');
插入图片需要预先加载:
const img = new Image();
img.src = '/logo.png';
img.onload = () => {
doc.addImage(img, 'PNG', 15, 40, 180, 160);
doc.save('with-image.pdf');
};
对于表格或分页,需借助 jspdf-autotable 等插件。否则,所有布局都需手动计算。
✅ 适用场景:生成简单、数据驱动的文档(如快递单、证书),且能通过代码定义布局。
❌ 不适用场景:需要复现复杂 HTML 布局或自动分页。
pdfmake 使用类似 JSON 的文档定义来描述 PDF 结构。你将内容定义为对象数组(文本、表格、图像、列),pdfmake 会自动处理布局、分页、页眉页脚和样式 。
它支持矢量文本、带行合并的表格、分页、页眉页脚和自定义字体。输出文件干净、可搜索且体积小 。
// pdfmake:声明式 PDF
import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';
pdfMake.vfs = pdfFonts.pdfMake.vfs;
const docDefinition = {
content: [
{ text: '发票', style: 'header' },
{ text: '客户:张三' },
{
table: {
body: [
['商品', '价格'],
['小部件', '¥10'],
['小配件', '¥20']
]
}
}
],
styles: {
header: { fontSize: 18, bold: true }
}
};
pdfMake.createPdf(docDefinition).download('invoice.pdf');
它还支持动态页眉页脚:
const docDefinition = {
header: () => '我的公司',
footer: (currentPage, pageCount) => `第 ${currentPage} 页,共 ${pageCount} 页`,
content: [/* ... */]
};
✅ 适用场景:需要生成专业、可打印的文档,包含表格、一致样式和自动分页。
❌ 不适用场景:输入源是无法转换为结构化数据的原始 HTML。
这是核心架构差异:
| 库 | 输入类型 | 渲染方式 |
|---|---|---|
html-pdf | HTML (Node.js) | PhantomJS (已弃用) |
html2pdf.js | DOM 元素 | 光栅化为图像 |
jsPDF | 代码(图元) | 矢量绘制 |
pdfmake | JSON 定义 | 矢量布局引擎 |
如果你的内容是浏览器中的 HTML,且需要视觉快照 → 选 html2pdf.js。
如果你有结构化数据(如 API 返回),需要生成高质量 PDF → 选 pdfmake。
如果你需要像素级控制坐标 → 选 jsPDF。
html2pdf.js(设计如此)jsPDF、pdfmake(Node.js 中需少量配置)html-pdf注意:jsPDF 和 pdfmake 虽支持 Node.js,但它们不渲染 HTML —— 它们从代码或定义生成 PDF。
html2pdf.js 生成较大文件,因为内容以图像形式嵌入。jsPDF 和 pdfmake 生成小体积、文本可搜索的 PDF。html-pdf 的输出质量依赖 PhantomJS,缺乏现代渲染能力。| 场景 | 推荐库 |
|---|---|
| 将屏幕上的 DOM 转为 PDF(如“导出为 PDF”按钮) | html2pdf.js |
| 从 JSON 数据生成发票、合同或报表 | pdfmake |
| 创建简单、代码定义的 PDF(如标签、证书) | jsPDF |
| Node.js 中 HTML 转 PDF(新项目) | 不要用这些 —— 用 Puppeteer |
| Node.js 中 HTML 转 PDF(旧项目) | 从 html-pdf 迁移 |
html-pdf 不应在任何新项目中使用。它对 PhantomJS 的依赖使其过时、不安全且与现代 Web 标准不兼容。如需服务端 HTML 转 PDF,请使用基于 Chromium 的方案如 Puppeteer。对于客户端需求,根据输入源在 html2pdf.js(视觉快照)和 pdfmake/jsPDF(结构化高质量文档)之间选择。
不要在新项目中使用 html-pdf —— 该包已在 npm 和 GitHub 上被官方标记为弃用,且依赖已停止维护的 PhantomJS 。它仅适用于 Node.js,无法用于客户端 PDF 生成。现有项目应迁移到 Puppeteer 等现代替代方案。
当需要将现有 HTML 内容(如报表、发票或仪表盘)直接在浏览器中转为 PDF 且希望保留视觉样式时,选择 html2pdf.js 。它适合“一键导出”场景,但需注意其内部使用 html2canvas 进行光栅化,可能导致文本不可搜索、文件体积大或复杂 CSS 渲染不准确。
当需要对 PDF 内容进行精细控制,并通过代码而非 HTML 生成文档时,选择 jsPDF 。它适用于生成标签、证书或简单表单等场景,但缺乏原生 HTML/CSS 支持,复杂布局(如表格)需依赖插件或手动实现。
当需要基于结构化数据生成包含复杂布局(如表格、页眉页脚、多栏)的专业级 PDF 时,选择 pdfmake 。它使用声明式 API,自动处理分页和样式,输出为矢量文本,文件小且可搜索,适合发票、合同等业务文档。

Example Business Card
-> and its Source file
Have a look at the releases page: https://github.com/marcbachmann/node-html-pdf/releases
Install the html-pdf utility via npm:
$ npm install -g html-pdf
$ html-pdf test/businesscard.html businesscard.pdf
var fs = require('fs');
var pdf = require('html-pdf');
var html = fs.readFileSync('./test/businesscard.html', 'utf8');
var options = { format: 'Letter' };
pdf.create(html, options).toFile('./businesscard.pdf', function(err, res) {
if (err) return console.log(err);
console.log(res); // { filename: '/app/businesscard.pdf' }
});
var pdf = require('html-pdf');
pdf.create(html).toFile([filepath, ]function(err, res){
console.log(res.filename);
});
pdf.create(html).toStream(function(err, stream){
stream.pipe(fs.createWriteStream('./foo.pdf'));
});
pdf.create(html).toBuffer(function(err, buffer){
console.log('This is a buffer:', Buffer.isBuffer(buffer));
});
// for backwards compatibility
// alias to pdf.create(html[, options]).toBuffer(callback)
pdf.create(html [, options], function(err, buffer){});
html-pdf can read the header or footer either out of the footer and header config object or out of the html source. You can either set a default header & footer or overwrite that by appending a page number (1 based index) to the id="pageHeader" attribute of a html tag.
You can use any combination of those tags. The library tries to find any element, that contains the pageHeader or pageFooter id prefix.
<div id="pageHeader">Default header</div>
<div id="pageHeader-first">Header on first page</div>
<div id="pageHeader-2">Header on second page</div>
<div id="pageHeader-3">Header on third page</div>
<div id="pageHeader-last">Header on last page</div>
...
<div id="pageFooter">Default footer</div>
<div id="pageFooter-first">Footer on first page</div>
<div id="pageFooter-2">Footer on second page</div>
<div id="pageFooter-last">Footer on last page</div>
config = {
// Export options
"directory": "/tmp", // The directory the file gets written into if not using .toFile(filename, callback). default: '/tmp'
// Papersize Options: http://phantomjs.org/api/webpage/property/paper-size.html
"height": "10.5in", // allowed units: mm, cm, in, px
"width": "8in", // allowed units: mm, cm, in, px
- or -
"format": "Letter", // allowed units: A3, A4, A5, Legal, Letter, Tabloid
"orientation": "portrait", // portrait or landscape
// Page options
"border": "0", // default is 0, units: mm, cm, in, px
- or -
"border": {
"top": "2in", // default is 0, units: mm, cm, in, px
"right": "1in",
"bottom": "2in",
"left": "1.5in"
},
paginationOffset: 1, // Override the initial pagination number
"header": {
"height": "45mm",
"contents": '<div style="text-align: center;">Author: Marc Bachmann</div>'
},
"footer": {
"height": "28mm",
"contents": {
first: 'Cover page',
2: 'Second page', // Any page number is working. 1-based index
default: '<span style="color: #444;">{{page}}</span>/<span>{{pages}}</span>', // fallback value
last: 'Last Page'
}
},
// Rendering options
"base": "file:///home/www/your-asset-path/", // Base path that's used to load files (images, css, js) when they aren't referenced using a host
// Zooming option, can be used to scale images if `options.type` is not pdf
"zoomFactor": "1", // default is 1
// File options
"type": "pdf", // allowed file types: png, jpeg, pdf
"quality": "75", // only used for types png & jpeg
// Script options
"phantomPath": "./node_modules/phantomjs/bin/phantomjs", // PhantomJS binary which should get downloaded automatically
"phantomArgs": [], // array of strings used as phantomjs args e.g. ["--ignore-ssl-errors=yes"]
"localUrlAccess": false, // Prevent local file:// access by passing '--local-url-access=false' to phantomjs
// For security reasons you should keep the default value if you render arbritary html/js.
"script": '/url', // Absolute path to a custom phantomjs script, use the file in lib/scripts as example
"timeout": 30000, // Timeout that will cancel phantomjs, in milliseconds
// Time we should wait after window load
// accepted values are 'manual', some delay in milliseconds or undefined to wait for a render event
"renderDelay": 1000,
// HTTP Headers that are used for requests
"httpHeaders": {
// e.g.
"Authorization": "Bearer ACEFAD8C-4B4D-4042-AB30-6C735F5BAC8B"
},
// To run Node application as Windows service
"childProcessOptions": {
"detached": true
}
// HTTP Cookies that are used for requests
"httpCookies": [
// e.g.
{
"name": "Valid-Cookie-Name", // required
"value": "Valid-Cookie-Value", // required
"domain": "localhost",
"path": "/foo", // required
"httponly": true,
"secure": false,
"expires": (new Date()).getTime() + (1000 * 60 * 60) // e.g. expires in 1 hour
}
]
}
The full options object gets converted to JSON and will get passed to the phantomjs script as third argument.
There are more options concerning the paperSize, header & footer options inside the phantomjs script.