使用 html2canvas+jspdf 生成可编辑的矢量 pdf

文章深入探讨了前端生成 PDF 的痛点,对比了 Puppeteer、jsPDF/PDFKit、PDFMake 和 html2canvas+jsPDF 等主流方案的优缺点,并引出了作者开发的 dompdf.js。该库的核心创新在于,它改造了 html2canvas 的渲染流程,将原本绘制到 Canvas 的 API 调用替换为 jsPDF 的矢量 API,从而直接将 DOM 内容转换为矢量 PDF,解决了传统方案生成图片式 PDF 导致的文件体积大、无法编辑、清晰度低等问题。文章详细阐述了 dompdf.js 的实现原理、已支持的功能(如文本、图片、边框、背景渲染),并介绍了通过 foreignObjectRendering 处理复杂布局的方法。最后,提供了详尽的安装和基础使用代码示例,并明确了其适用场景与局限性。




本文将介绍我的开源 js 库 dompdf.js,一个从未被开发者提及过的前端生成 pdf 解决方案,它可以在不依赖任何后端服务的情况下,直接在浏览器中将 html 生成为真正的, 非图片式,可编辑的,高清晰度的 pdf 文件。

pdf生成示例-我的上一篇文章

image.png

1. 在线体验

dompdfjs.lisky.com.cn

2. Git 仓库地址 (欢迎 Star⭐⭐⭐)

github.com/lmn1919/dom…

3. 为什么是 dompdf.js

查询了前端生成 pdf 方法的相关资料,发现目前前端生成 pdf 文件的主流方法有以下几种:

方案类型 优点 缺点
Puppeteer 等无头浏览器 ✅ 渲染效果与浏览器完全一致
✅ 支持 JavaScript 动态内容
❌ 资源消耗大(内存、CPU)
❌ 启动速度慢
❌ 并发处理能力有限
❌ 需要服务器环境
jsPDF/PDFKit ✅ 纯前端实现,无服务器依赖
✅ 文件体积小
✅ 生成速度快
✅ 支持矢量图形
❌ 需要手动构建 PDF 结构
❌ 不支持复杂 HTML 布局
❌ 学习成本较高
❌ 样式支持有限
PDFMake ✅ 纯前端解决方案
✅ 声明式 API,易于使用
✅ 支持表格、图表等
✅ 模板化程度高
❌ 不支持 HTML 直接转换
❌ 需要重新构建文档结构
❌ 样式定制能力有限
❌ 复杂布局实现困难
html2canvas + jsPDF ✅ 实现简单
✅ 纯前端方案
✅ 所见即所得
❌ 生成图片式 PDF,无法编辑
❌ 文件体积大
❌ 清晰度不够
❌ 不支持文字选择
dompdf.js ✅ 纯前端实现
✅ 快速上手,对代码入侵小
✅ 文件体积小
✅ 支持文字选择和编辑
✅ 矢量图形,清晰度高
❌ 部分 CSS 属性支持有限
❌ 复杂布局可能有差异
❌ 浏览器兼容性要求

很多文章都力推 html2canvas + jsPDF 方案,说明大家生成 pdf 的需求的复杂度都不怎么高,所以大部分场景下 dompdf.js 是够用的,比如报表,合同,体检报告,简历生成,一些简单的文档。

4. dompdf.js 是如何实现的?

其实 dompdf.js 也是基于 html2canvas+jspdf 实现的,但是为什么 dompdf.js 生成的 pdf 文件可以二次编辑,更清晰,体积小呢?

不同于普通的 html2canvas + jsPDF 方案,将 dom 内容生成为图片,再将图片内容用 jspdf 绘制到 pdf 上,这就导致了生成的 pdf 文件体积大,无法编辑,放大后会模糊。

html2canvas 原理简介

  • 遍历 DOM 树 :html2canvas 从你指定的 DOM 节点开始,递归遍历其所有子节点,构建一个内部的、描述页面结构的渲染队列。
  • 计算样式( getComputedStyle ) :对于每一个节点,它会调用 window.getComputedStyle() 来获取该元素最终在浏览器中呈现的所有 CSS 属性值。这是获取视觉信息的关键,因为它包含了所有 CSS 规则(内联、内部、外部样式表)层叠计算后的最终结果。
  • 构建渲染模型 :它将每个 DOM 节点和其计算后的样式包装成一个渲染对象。这个对象包含了绘制该元素所需的所有信息,如位置( top , left )、尺寸( width , height )、背景、边框、文本内容、字体属性、堆叠上下文( z-index )等
  • 创建 Canvas 上下文 :在内存中创建一个 canvas 元素,并获取其 2D 渲染上下文( CanvasRenderingContext2D )。
  • 模拟浏览器绘制 :html2canvas 按照 DOM 的堆叠顺序(考虑 z-index )和布局,遍历之前构建的渲染队列,将每个元素绘制到 Canvas 上。这个过程是将 CSS 属性“翻译”成 Canvas API 调用的过程:
CSS 属性 Canvas API 调用 jsPDF API 调用
background-color ctx.fillStyle + ctx.fillRect() doc.setFillColor() + doc.rect(x, y, w, h, 'F')
border ctx.strokeStyle + ctx.strokeRect() doc.setDrawColor() + doc.rect(x, y, w, h, 'S')
color, font-family, font-size ctx.fillStyle, ctx.font + ctx.fillText() doc.setTextColor() + doc.setFont() + doc.text()
border-radius 通过 arcTo() 或 bezierCurveTo() 创建剪切路径(clip())来实现 doc.roundedRect() 或通过 doc.lines() 绘制圆角路径
image ctx.addImage() doc.addImage()

dompdf.js 改造了 html2canvas 的最后一步,将绘制 canvas 的 API,换成了 jspdf 的 API,从而实现了从 dom 到 pdf 的操作,创建可以编辑的 pdf 文件。

5.实现功能

功能 状态 说明
文本渲染 支持基础文本内容渲染,font-family,font-size,font-style,font-variant,color 等,支持文字描边,不支持文字阴影
图片渲染 支持网络图片,base64 图片,svg 图片
边框 支持 border-width,border-color,border-style,border-radius
背景 支持背景颜色,背景图片,背景渐变
canvas 支持渲染 canvas
svg 支持渲染 svg
阴影渲染 使用 foreignObjectRendering,支持边框阴影渲染
渐变渲染 使用 foreignObjectRendering,支持背景渐变渲染
复杂表格 支持复杂表格渲染
iframe 暂不支持渲染 iframe
分页 暂不支持分页

6.使用 foreignObjectRendering 渲染复杂表格,边框阴影,渐变

在 dom 十分复杂,或者 pdf 无法绘制的情况(比如:复杂的表格,边框阴影,渐变等),可以考虑使用 foreignObjectRendering。 给要渲染的元素添加 foreignObjectRendering 属性,就可以通过 svg 的 foreignObject 将它渲染成一张背景图插入到 pdf 文件中。

但是,由于 foreignObject 元素的渲染依赖于浏览器的实现,因此在不同的浏览器中可能会有不同的表现。 所以,在使用 foreignObjectRendering 时,需要注意以下事项:

  1. foreignObject 元素的渲染依赖于浏览器的实现,因此在不同的浏览器中可能会有不同的表现。
  2. IE 浏览器完全不支持,推荐在 chrome 和 firefox,edge 中使用。
  3. 生成的图片会导致 pdf 文件体积变大。

示例

<div style="width: 100px;height: 100px;" foreignObjectRendering>
  <div
    style="width: 50px;height: 50px;border: 1px solid #000;box-shadow: 2px 2px 5px rgba(0,0,0,0.3);background: linear-gradient(45deg, #ff6b6b, #4ecdc4);"
  >
    这是一个div元素
  </div>
</div>

7.使用

安装

    npm install dompdf.js --save

基础用法

import dompdf from "dompdf.js";
dompdf(document.querySelector("#capture"), {
  useCORS: true, //是否允许跨域
})
  .then(function (blob) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "example.pdf";
    document.body.appendChild(a);
    a.click();
  })
  .catch(function (err) {
    console.log(err, "err");
  });

字体支持

如果需要自定义字体,在这里将字体 tff 文件转化成 base64 格式的 js 文件,中文字体推荐使用思源黑体,体积较小。 在代码中引入该文件即可。

<script type="text/javascript" src="./SourceHanSansSC-Normal-Min-normal.js"></script>;
dompdf(document.querySelector("#capture"), {
  useCORS: true, //是否允许跨域
  fontConfig: {
    fontFamily: "SourceHanSansSC-Normal-Min",
    fontBase64: window.fontBase64,
  },
})
  .then(function (blob) {
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "example.pdf";
    document.body.appendChild(a);
    a.click();
  })
  .catch(function (err) {
    console.log(err, "err");
  });

写在最后

dompdf.js 为前端 PDF 生成提供了一个新的选择。它摆脱了传统的“截图”模式,让我们能在客户端直接生成高质量、可编辑的结构化 PDF 文档。它特别适合用在在线简历生成、数据报告导出、电子发票打印等场景,但是复杂专业的pdf生成需求不建议使用。

希望这个小工具能对你有所帮助。立即去 GitHub 上看看,给它一个 Star,也欢迎大家在项目中使用它!


AI 前线

Kimi K2 实战:构建端到端旅游优惠查找器 AI 应用教程

2025-12-23 22:45:57

AI 前线

Gemini 2.5 的对话式图像分割

2025-12-23 22:46:03

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索