在做前端的时候,你一定遇到过这种需求:页面上有一个挺好看的报表、订单详情、统计图表,用户想一键保存成 PDF 发给别人或者存档。这功能真做起来没有看上去那么轻巧,因为浏览器本身没有“把当前页面变成 PDF”的原生能力,虽然 window.print() 被很多开发者当作后备方案,但打印出来要调样式、要改布局,效果往往一言难尽。我自己在项目里踩过一圈坑之后,现在最顺手的组合还是 html2canvas + jsPDF——html2canvas 先把 DOM 截成 canvas 图片,jsPDF 再把图片按 PDF 的页面尺寸排进去导出。这套方案能帮助我快速实现页面导出成 PDF 的需求,本文就把整个思路、代码、参数调优和常见坑全部梳理一遍,给要接这个功能的同学一份可以直接抄作业的指南。
1. 方案选型:为什么大家都在用 html2canvas + jsPDF
1.1 这两个库各管什么
要理解这套方案,先得把两个库的分工弄清楚。html2canvas 做的事情是“截图”,它会把指定的 DOM 节点重绘到一张 canvas 画布上,这张画布本质上就是一张位图,像素点里存的是页面渲染出来的视觉结果。而 jsPDF 做的事情是“排版”,它负责创建 PDF 文档、按页添加内容、控制每一页的尺寸和坐标,最终输出一个 .pdf 文件。
整个流程就是:DOM → html2canvas 截成 canvas → canvas 导出成图片数据(base64 或 dataURL)→ jsPDF 以 addImage 的方式把图片写进 PDF 的每一页 → 保存文件。
用生活化类比来说,html2canvas 像是一台照相机,把眼前页面拍下来;jsPDF 像是排版打印机,把照片按 A4 纸的尺寸裁好、排好、打印出来。因为最终落到 PDF 里的是一张位图,所以这套方案对样式的还原度取决于 html2canvas 对 CSS 的解析能力,而不是浏览器的打印引擎。
1.2 与 html2pdf 这类封装库的取舍
你可能也见过 html2pdf.js,它其实就是把 html2canvas + jsPDF 又包了一层,对外暴露一个更简洁的 API,核心逻辑和两个独立库没什么区别。我会选择直接用底层两个库,而不是用封装完成的 html2pdf,原因有三个:
- 分页逻辑需要自己控制,用封装库反而多一层黑盒,出了问题要扒进去看源码,排查成本更高。
- 版本迭代节奏不同,html2pdf 对底层库的依赖版本可能滞后,遇到新浏览器的兼容问题时,直接升级 html2canvas 和 jsPDF 更灵活。
- 我需要中途插入页眉页脚、指定某个区域不导出、动态调整图片质量,这部分在底层库里可以直接操作,封装后反而要绕过它的 API 去 hack。
不过如果你只是做一次性的简单导出,不想关心实现细节,html2pdf 也能快速解决问题,只是后续维护要注意它的依赖更新情况。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与最小可用实现
2.1 安装引入的两种方式
先解决依赖。npm 项目里常规安装:
bash复制npm install html2canvas jspdf
然后在你的模块文件里引入:
javascript复制import html2canvas from 'html2canvas';
import { jsPDF } from 'jspdf';
注意 jspdf 从 2.x 开始官方推荐按命名方式导入 jsPDF。如果是老项目用的 1.x 版本,写法是 const { jsPDF } = require('jspdf'),这个版本差异不影响整体原理,但要留意代码仓库里的包版本,避免复制网上的示例后报 undefined。
如果你不用打包工具,也可以直接在 HTML 里用 CDN 方式引入:
html复制<script src="https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jspdf@2.5.1/dist/jspdf.umd.min.js"></script>
<script>
// 全局会挂载 html2canvas 和 jspdf 的全局对象
</script>
这种方式适合写 Demo 或者在公司内部简单功能页里快速验证。我建议不管哪种方式,都先把版本固定,我记得后面踩过一个坑,html2canvas 的 1.4.x 和 1.3.x 在某些 CSS 属性的处理上不太一样,升级以后要重新回归一遍。
2.2 核心代码:把第一屏导出为 PDF
写一个最精简的导出函数,功能就是把指定的 DOM 节点截图并转成 PDF。下面这段代码可以直接放进你的项目里跑通:
javascript复制async function exportPdf() {
const element = document.getElementById('report-container');
// 第一参数是目标节点,第二参数是配置项
const canvas = await html2canvas(element, {
scale: 2, // 导出图片的分辨率倍数,2 倍在清晰度上比较均衡
us
