三步用 html-pdf-chrome 把 HTML 转成 PDF:CreateOptions 实战指南
【免费下载链接】html-pdf-chromeHTML to PDF or image (jpeg, png, webp) converter via Chrome/Chromium项目地址: https://gitcode.com/gh_mirrors/ht/html-pdf-chrome
html-pdf-chrome 是一款基于无头 Chrome 的 HTML 转 PDF 库。它让真实 Chrome 内核渲染你的页面,再输出 PDF 或 PNG、JPEG、WebP 图片,适合报表生成、数据导出、网页存档等自动化场景。
手工转换 HTML 的麻烦在哪
先看看没有工具时,你平时是怎么干的:
- 手动截图:浏览器打开页面,用截图工具截一张,长页面根本截不全,分辨率也不可控。
- 打印成 PDF:用浏览器"打印为 PDF",页边距、纸张方向每次都要手点,没法写进脚本。
- 定期报表:定时任务已经能生成 HTML 模板,但"转 PDF 并发邮件"这一步只能靠人,全自动无从谈起。
共同点是:这些操作要么不可重复,要么无法参数化。html-pdf-chrome 的做法是用一行create()调用替代它们,所有版式细节都通过CreateOptions对象用代码描述。
上手体验:三步拿到第一份 PDF
第一步,安装依赖:npm install html-pdf-chrome。
第二步,让 Chrome 以无头模式监听调试端口:
chrome --headless --disable-gpu --remote-debugging-port=9222第三步,调库生成文件:
import * as htmlPdf from 'html-pdf-chrome'; const options: htmlPdf.CreateOptions = { port: 9222, // 指向常驻 Chrome 的调试端口 }; // 传 HTML 字符串或 URL 都可以,返回结果对象 const result = await htmlPdf.create('<h1>Hello PDF</h1>', options); await result.toFile('first.pdf'); // 也可以 toBuffer()/toStream()/toBase64()如果host、port都不传,库会为这一次生成临时启动一个 Chrome 实例,用完即关。能复用就复用,启动开销能省则省。
按场景整理配置参数
所有可选项集中在CreateOptions接口里,类型定义见源码文件src/CreateOptions.ts。逐项背参数效率低,按场景分组记更容易。
页面版式:方向、边距与纸张设置
printOptions直接对应 Chrome 的打印参数。scale就像打印机的缩放比例,小于 1 时内容整体缩小、能塞进更多行:
printOptions: { landscape: false, // false 纵向;宽表格改 true 横向 paperWidth: 8.27, // 纸张宽度(英寸),A4 约 8.27 x 11.69 paperHeight: 11.69, marginTop: 0.4, // 四边距单位都是英寸,留白太大会浪费纸 marginBottom: 0.4, marginLeft: 0.4, marginRight: 0.4, scale: 1.0, displayHeaderFooter: true, // 页眉页脚模板生效的前提 headerTemplate: '<span class="pageNumber"></span>', // 内置占位符:pageNumber/totalPages/title }| 参数 | 说明 | 建议值 |
|---|---|---|
landscape | 纸张方向 | 报表用false,宽表用true |
paperWidth/paperHeight | 纸张尺寸(英寸) | A4:8.27 x 11.69;Letter:8.5 x 11 |
marginTop等四个边距 | 页面留白 | 0.3 ~ 0.5 |
scale | 打印缩放 | 0.8 ~ 1.0 |
headerTemplate/footerTemplate | 页眉页脚 HTML 模板 | 与displayHeaderFooter一起开启 |
输出格式:PDF 还是图片
默认输出 PDF;只要传了screenshotOptions,输出就变成图片。deviceMetrics决定"浏览器窗口"多大,相当于先决定取景框再拍照:
screenshotOptions: { format: 'jpeg', // png / jpeg / webp quality: 85, // 仅 jpeg 生效,1-100 clip: { x: 0, y: 0, width: 800, height: 600 }, // 只截这一块区域 }, deviceMetrics: { width: 1200, // 视口宽度 height: 800, deviceScaleFactor: 2, // 2 倍像素,图更清晰但文件更大 mobile: false, }| 参数 | 类型 | 说明 |
|---|---|---|
screenshotOptions.format | 'png' \| 'jpeg' \| 'webp' | 图片格式,默认 png |
screenshotOptions.quality | number | 仅 jpeg 有效的压缩质量 |
screenshotOptions.clip | object | 裁剪区域,不传则截整个视口 |
deviceMetrics.deviceScaleFactor | number | 像素密度,2 表示 2 倍清晰度 |
页面就绪等待:completionTrigger
这是新手最容易踩的点:页面 JS 还没取完数据,PDF 就先转好了,产出的是一张"空壳"。解决办法是用completionTrigger告诉库"等到什么信号再打印":
// 等待 id 为 dataLoaded 的节点出现,最多等 10 秒 completionTrigger: new htmlPdf.CompletionTrigger.Element('#dataLoaded', 10000)| 触发器 | 适用场景 |
|---|---|
Timer(毫秒) | 内容加载耗时基本固定,最省事 |
Element(css选择器) | 等某个数据节点渲染出来 |
Event(事件名) | 页面里自己 dispatch 完成事件 |
LifecycleEvent('networkIdle') | 请求密集,等网络空闲 |
Variable('变量名') | 前端把变量置 true 表示完成 |
Callback('回调名') | 前端主动调用约定好的回调 |
头部与身份:请求头和 Cookie
页面需要登录态或带 token 才能拿到数据时,用这两项把身份带过去:
extraHTTPHeaders: { 'Authorization': 'Bearer xxx', // 随每次请求发出 }, cookies: [ { name: 'session', value: 'abc', domain: '.example.com' }, // 预置登录态 ], clearCache: true // 加载前清缓存,防止渲染到旧内容错误捕获:看到页面里发生了什么
无头 Chrome 里没人盯着控制台,页面报错不会有任何提示。挂上两个回调,页面内部的消息会转发到你的 Node 进程:
runtimeConsoleHandler: (event) => { // 页面的 console.log / warn / error 都会到这里 console.log('页面控制台:', event.type, event.args); }, runtimeExceptionHandler: (exception) => { // 捕获未处理的异常,排查白屏利器 console.error('页面异常:', exception.exceptionDetails); }, timeout: 30000 // 整体超时(毫秒),防止无限等待两个实战案例
案例 1:带认证头的报表 PDF
const options: htmlPdf.CreateOptions = { port: 9222, printOptions: { landscape: true, // 报表列多,用横向 printBackground: true, // 保留表头背景色 marginTop: 0.5, marginBottom: 0.5, marginLeft: 0.5, marginRight: 0.5, }, extraHTTPHeaders: { 'Authorization': 'Bearer report-token' }, completionTrigger: new htmlPdf.CompletionTrigger.Timer(2000), timeout: 60000, // 数据量大,超时放宽 }; const html = await renderReportTemplate(); // 模板引擎生成 HTML const pdf = await htmlPdf.create(html, options); await pdf.toFile('monthly-report.pdf');案例 2:移动端视口截图
const options: htmlPdf.CreateOptions = { port: 9222, screenshotOptions: { format: 'jpeg', quality: 85 }, deviceMetrics: { width: 375, // 常见手机视口宽度 height: 667, deviceScaleFactor: 2, mobile: true, // 模拟移动设备,CSS 媒体查询按手机端匹配 }, completionTrigger: new htmlPdf.CompletionTrigger.LifecycleEvent('networkIdle'), }; const result = await htmlPdf.create('https://example.com', options); await result.toFile('mobile-view.jpg');避坑指南 🚧
现象:导出的 PDF 内容是空壳或半截原因:动态内容还没渲染完就触发了转换。 解决:按页面特性选completionTrigger——有明确节点用Element,请求多用LifecycleEvent('networkIdle'),并给足超时时间。
现象:报连接失败,create 直接抛错原因:Chrome 没启动,或调试端口没开、端口号对不上。 解决:先执行chrome --headless --disable-gpu --remote-debugging-port=9222,再让options.port与之一致。
现象:PDF 中文乱码原因:HTML 没声明字符集,渲染时编码猜错。 解决:在文档头部加<meta charset="UTF-8">。
现象:长时间运行后 Chrome 越来越卡、内存持续增长原因:同一个实例反复生成页面,内存慢慢累积。 解决:用 pm2 这类进程管理器让 Chrome 常驻并崩溃自动拉起,同时定期重启实例。
现象:每生成一份 PDF 要多等好几秒原因:没配host/port,每次都在临时启动一个新的 Chrome。 解决:让 Chrome 常驻,CreateOptions里指定端口复用,这是无头 Chrome 生成 PDF 最主要的提速手段。
速查卡
| 场景 | 关键参数 | 建议值 |
|---|---|---|
| 复用常驻 Chrome | host/port | port: 9222 |
| A4 纵向报表 | printOptions | 纵向 + 边距 0.3~0.5 英寸 |
| 横向宽表 | printOptions.landscape | true |
| 页码页眉 | displayHeaderFooter+ 模板 | pageNumber/totalPages占位符 |
| 导出图片 | screenshotOptions | format: 'png'/'jpeg'/'webp' |
| 2 倍清晰度 | deviceMetrics.deviceScaleFactor | 2 |
| 数据驱动页面 | completionTrigger.Element | 等待数据节点,超时 10 秒 |
| 请求密集页面 | completionTrigger.LifecycleEvent | 'networkIdle' |
| 需登录态 | extraHTTPHeaders/cookies | 传 token 或会话 Cookie |
| 长耗时任务 | timeout | 30 ~ 60 秒 |
配置的核心其实就三块:port决定连哪个 Chrome,printOptions决定版式,completionTrigger决定什么时候动手。把这三块理顺,html-pdf-chrome 就能覆盖绝大多数 HTML 转 PDF 的需求;参数细节可直接阅读src/CreateOptions.ts中的类型定义与注释。
【免费下载链接】html-pdf-chromeHTML to PDF or image (jpeg, png, webp) converter via Chrome/Chromium项目地址: https://gitcode.com/gh_mirrors/ht/html-pdf-chrome
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考