当前位置:首页>java>前端一行代码生成数千页PDF,dompdf.js新增分页功能

前端一行代码生成数千页PDF,dompdf.js新增分页功能

  • 2026-10-10 16:29:10
前端一行代码生成数千页PDF,dompdf.js新增分页功能

先说结论:现在前端真可以一行代码,把几千页的 PDF 直接在浏览器里干出来,而且是可选中、可搜索的那种,不是截图糊糊的一张大图片,靠的就是这货——dompdf.js 新增的分页能力。

你肯定遇到过这种场景:产品突然拉个需求,要导出「几十页报表 PDF」,还要样式跟页面一样,页眉页脚有 logo、有页码,表格不能被切断,图片不能糊,文字还能复制。

传统做法大概就这几种:

  • html2canvas + jsPDF:先截图,再塞进 PDF,一放大就糊,文字不是真文字;
  • 纯 jsPDF 手写排版:定位全靠算坐标,复杂布局写起来要怀疑人生;
  • 丢给后端用 wkhtmltopdf 之类:一堆环境、字体、权限问题,上线一次就能折腾一礼拜。

尤其是那种几十、上百页的大报表,你会发现前端能做的,要么糊,要么丑,要么写死人。

所以我第一次看到 dompdf.js 的分页功能的时候,内心 OS 就一句:这才像 202x 年的前端库干的事啊。

dompdf.js 是啥?一句话版本

简单粗暴一点讲:

dompdf.js = 在浏览器里,把一个 DOM 节点「排版 ➜ 渲染 ➜ 导出」成矢量 PDF的库,支持分页、页眉页脚、加密压缩这些高级操作。

重点几个点:

  • 不截图,导出来的 PDF 里是文字、矢量图形,可以复制/搜索;
  • 支持多页,甚至压到几千页 PDF(当然你内存得扛得住
  • 完全跑在前端,不占用后端资源,也不需要额外服务。

它对外暴露的核心就是一个函数:dompdf(targetDom, options) => Promise<Blob>。

所以真的可以写出这种一行:

import dompdf from'dompdf.js';dompdf(document.querySelector('#report'), { pagination: true });

这行就是「把 #report 这个 DOM 节点按分页规则变成 PDF Blob」的完整入口。

下载、上传、预览怎么搞,你再接在后面就行。

真·一行导出多页 PDF 的完整例子

光说一行有点耍流氓,我们写一个最常见的「A4 报表导出」场景,你可以直接抄去项目里改一改就用。

HTML 里先准备一个容器,宽度一定要和纸张尺寸对应,这是分页能不能算准的关键:([codelove.tw][1])

<!-- 注意这个宽度:A4 宽度对应 794px --><divid="report"style="width: 794px; margin: 0 auto;"><!-- 你的表格 / 报表 / 合同内容都塞这里 --></div><buttonid="exportBtn">导出 PDF</button>

JS 里这么写:

import dompdf from'dompdf.js';const btn = document.querySelector('#exportBtn');btn.addEventListener('click', async () => {const el = document.querySelector('#report');// 真正干活的就这一行:const blob = await dompdf(el, {pagination: true,        // 开启分页format: 'a4',            // 纸张大小compress: true,          // 压缩一下体积(可选)pageConfig: {            // 页眉页脚(可选)header: {content: '某某系统报表',height: 40,contentFontSize: 10,contentPosition: 'center',      },footer: {content: '第${currentPage}页 / 共${totalPages}页',height: 40,contentFontSize: 10,contentPosition: 'center',      },    },  });// 后面这些是常规的 blob 下载套路const url = URL.createObjectURL(blob);const a = document.createElement('a');  a.href = url;  a.download = `report-${Date.now()}.pdf`;document.body.appendChild(a);  a.click();  a.remove();  URL.revokeObjectURL(url);});

你看,核心逻辑就那一行 dompdf(el, { ... }),其他都是打工仔给它收拾烂摊子。

分页到底是怎么算的?

dompdf.js 内部其实干了三大件事(为了方便理解,我稍微意译了一下官方的描述):

  1. 先把 DOM 解析成一个「带位置信息的树」

    它会遍历你的页面,把每个元素的 width/height/left/top、样式、文本节点都算出来,弄成类似这样的结构:

    {bounds: { left: 8, top: 0, width: 794, height: 1300 },elements: [    {bounds: { left: 8, top: 1000, width: 794, height: 300 },textNodes: [        {text: '这是一个文本节点',bounds: { left: 16, top: 1115, width: 300, height: 24 }        }      ],    },  ],}
  2. 按页高切片:该翻页就翻页

    假设你 A4 的可用高度(扣掉页眉页脚)是 1123px,那它就会按类似的逻辑走一遍(伪代码):

    functionshouldBreak(bounds, pageHeight) {return bounds.top + bounds.height > pageHeight;}
    • 如果一个 div 整块高度超过一页,就拆成两块;
    • 如果是文本节点,它会在合适的位置把文字截断,一半留在当前页,另一半挪到下一页;
    • 同时把后续所有元素的 top 重新减掉前面占的高度,保证每一页自己的坐标系是从 0 开始。
  3. 按页循环,交给底层 PDF 引擎绘制

    dompdf.js 底下是用 jsPDF 之类的东西去画图的(文本、矩形、图片等),分页之后就是:

    pages.forEach((page, index) => {if (index > 0) jspdf.addPage();// 画页眉// 画内容// 画页脚});

    你不需要关心这些细节,真正有用的是:它已经帮你想好了大部分分页规则,能把复杂布局切得比较自然。

有些东西我就是不想被拆开,怎么办?

比如合同里的一个签名区域、一个完整的卡片、一张大图,你不希望它被拆成半张上一页半张下一页,这种时候可以在 DOM 上加一个标记属性,告诉 dompdf.js:这块别拆,宁可整体挪到下一页。

比如这样:

<divclass="card"divisionDisable><h3>用户信息</h3><p>姓名:XXX</p><p>证件号:YYYY</p><p>签名区:</p><!-- ... --></div>

它在分割的时候会检测这个属性,发现你不让拆,就整块算一个元素,顶多把前面那一页空出一块,整体丢到下一页去,看起来会比「拦腰斩断」舒服很多。

在真实项目里,几千页 PDF 怎么搞才不翻车?

说一千道一万,你真要搞「数千页」那种超大 PDF,还是得考虑几个现实问题。

  1. 内容越「图文并茂」,页数就越少

    纯文本的 PDF,几千页没什么问题;如果每页都塞满大图,几十页可能就已经很大了。dompdf.js 自己也提到,这个跟文件总大小绑死的。

  2. 图片一定要配好跨域 & CORS

    如果你的图片是 CDN / 其他域名的,记得:

    dompdf(el, {pagination: true,useCORS: true,});

    同时服务端得配好 Access-Control-Allow-Origin,不然 PDF 里那块就是一片空白。

  3. 别忘了压缩 & 加密这些高级选项

    dompdf.js 还支持一些高级参数,比如:

    dompdf(el, {pagination: true,compress: true,       // 压缩 PDF 体积encryption: {userPassword: '123456',ownerPassword: 'admin-xxx',userPermissions: ['print', 'copy'], // 限制打印 / 拷贝等  },});

    压一下体积,对几百页以上的 PDF 很有用。

  4. 导出时给个 loading,别让用户以为卡死了

    生成几百页 PDF 还是挺吃 CPU 的,动辄好几秒,建议包一层:

    asyncfunctionexportPdf() {  showLoading('正在生成 PDF,请稍候...');try {const blob = await dompdf(/* ... */);// 下载逻辑  } catch (e) {console.error(e);    alert('导出失败,请稍后重试');  } finally {    hideLoading();  }}

dompdf.js 这种库,本质上就是把你脑子里「排版」这件事自动化了:你给它 DOM,它帮你算坐标、控制分页、处理页眉页脚,前端这边只要关注页面长什么样就行,这一点跟之前自己手撸坐标的时代已经完全不是一个世界。

我自己在写这类东西的习惯,也是先从小 demo 开始,搞清楚几个最关键的问题:宽度怎么配、分页规则会不会坑、图片和字体能不能正常出来,然后再慢慢往项目里砌。风格上和之前写数据库那些技术踩坑记录是一脉相承的。

总之,如果你现在项目里还在用截图方式搞 PDF,真心可以抽半天,把 dompdf.js 的分页功能拉下来玩一玩,起码以后再遇到「全前端生成多页 PDF」这种需求,不至于第一反应就是「我能不能跟产品吵一架先」。

-END-

我为大家打造了一份RPA教程,完全免费:songshuhezi.com/rpa.html

🔥私藏精品🔥

虎哥作为一名老码农,整理了全网最全《前端资料合集》。总量高达650GB

最新文章

随机文章