慷慷外婆 發表於 2026-1-16 17:40:00

前端 HTML 转 PDF

<h1 data-id="heading-0">🧑‍💻 写在开头</h1>
<p>点赞 + 收藏 === 学会🤣🤣🤣</p>
<div>
<p><strong>前端 HTML 转 PDF 的工具函数</strong>,核心作用是:把网页中指定 ID 的 DOM 元素(比如表格、报表、表单等),通过&nbsp;<code>html2canvas</code>&nbsp;和&nbsp;<code>jspdf</code>&nbsp;两个库转换成 PDF 文件并下载到本地。</p>
<p>简单说:它能让用户 “一键下载” 网页上的某个区域为 PDF(比如报表、数据统计页、合同预览页等),还预留了 “水印功能” 的注释代码(可按需启用)。</p>
<h3 data-id="heading-0">核心依赖说明</h3>
<p>函数依赖两个关键库,必须先安装才能使用:</p>
<p><img src="https://img2024.cnblogs.com/blog/2149129/202601/2149129-20260116173806831-1805805151.png" alt="ScreenShot_2026-01-16_173725_018" loading="lazy"></p>
</div>
<div>
<div>
<h3 data-id="heading-1">函数核心逻辑(分步拆解)</h3>
<p>函数名&nbsp;<code>htmlToPdf</code>,接收两个参数:</p>
<ul>
<li><code>title</code>:下载的 PDF 文件名(比如 “2024 年报表”);</li>
<li><code>htmlId</code>:需要转换的 HTML 元素的 ID(比如&nbsp;<code>#report-container</code>)。</li>
</ul>
<p>整体流程:<strong>定位 HTML 元素 → 滚动置顶避免截图不全 → (可选加水印)→ HTML 转 Canvas → Canvas 转 PDF → 下载 PDF</strong></p>
<h4 data-id="heading-2">1. 准备工作:定位元素 + 滚动置顶</h4>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">const element = document.querySelector(htmlId); // 找到要转PDF的HTML元素
// 滚动置顶:避免元素被滚动条遮挡,导致截图不全
window.pageYOffset = 0;
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;</pre>
</div>
<div>
<div>
<h4 data-id="heading-3">2. 可选:添加水印(已注释,需启用可取消注释)</h4>
<p>注释部分的逻辑是:创建一个带文字水印(比如 “我是水印”)的 Canvas,作为背景图添加到目标元素上,转 PDF 时水印会一起被截取(适合需要版权保护的场景)。</p>
<h4 data-id="heading-4">3. HTML 转 Canvas(核心步骤)</h4>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">html2Canvas(element, {
allowTaint: true, // 允许跨域图片(如果元素内有跨域图片需开启)
useCORS: true, // 启用CORS跨域支持
scale: 2, // 缩放2倍:提升PDF清晰度(代价是文件变大)
height: element.scrollHeight, // 用元素实际滚动高度:避免只截取可视区域(关键!)
windowHeight: element.scrollHeight
}).then(canvas =&gt; {
// Canvas生成成功后,进入PDF生成步骤
});</pre>
</div>
<h4 data-id="heading-5">4. Canvas 转 PDF 并下载</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">const contentWidth = canvas.width; // Canvas宽度
const contentHeight = canvas.height; // Canvas高度
const pageHeight = (contentWidth * 841.89) / 592.28; // A4纸的高度(按比例计算)
let leftHeight = contentHeight; // 未生成PDF的剩余高度
let position = 0; // PDF页面偏移量

// 创建A4尺寸的PDF(纵向:"p" = portrait)
const pdf = new JsPDF("p", "pt", "a4");
// Canvas转成图片数据(jpeg格式,质量1.0)
const pageData = canvas.toDataURL("image/jpeg", 1.0);

// 处理分页:如果内容高度超过1页A4纸,自动分页
if (leftHeight &lt; pageHeight) {
// 单页:直接嵌入图片(20是左右边距,避免内容贴边)
pdf.addImage(pageData, "JPEG", 20, 20, imgWidth, imgHeight);
} else {
// 多页:循环截取内容,每满1页添加新页面
while (leftHeight &gt; 0) {
    pdf.addImage(pageData, "JPEG", 20, position, imgWidth, imgHeight);
    leftHeight -= pageHeight;
    position -= 841.89; // 向下偏移A4纸高度
    if (leftHeight &gt; 0) pdf.addPage(); // 剩余内容不为空时,新增一页
}
}

// 触发下载:文件名=title+".pdf"
pdf.save(title + ".pdf");</pre>
</div>
<h3 data-id="heading-6">如何使用</h3>
<h4 data-id="heading-7">1. 安装依赖</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;"># npm 安装
npm install html2canvas jspdf --save

# pnpm 安装
pnpm add html2canvas jspdf</pre>
</div>
<h4 data-id="heading-8">2.创建一个htmlToPdf.js文件</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">import html2Canvas from "html2canvas";
import JsPDF from "jspdf";
// title:下载文件的名称 &nbsp;htmlId:包裹的标签的id
const htmlToPdf = (title, htmlId) =&gt; {
&nbsp; const element = document.querySelector(htmlId);
&nbsp; window.pageYOffset = 0;
&nbsp; document.documentElement.scrollTop = 0;
&nbsp; document.body.scrollTop = 0;
&nbsp; setTimeout(() =&gt; {
&nbsp; &nbsp; // // 以下注释的是增加导出的pdf水印 !!!!!!!!!!!!!
&nbsp; &nbsp; // const value = '我是水印'
&nbsp; &nbsp; // //创建一个画布
&nbsp; &nbsp; // let can = document.createElement('canvas')
&nbsp; &nbsp; // //设置画布的长宽
&nbsp; &nbsp; // can.width = 400
&nbsp; &nbsp; // can.height = 500
&nbsp; &nbsp; // let cans = can.getContext('2d') as any
&nbsp; &nbsp; // //旋转角度
&nbsp; &nbsp; // cans.rotate((-15 * Math.PI) / 180)
&nbsp; &nbsp; // cans.font = '18px Vedana'
&nbsp; &nbsp; // //设置填充绘画的颜色、渐变或者模式
&nbsp; &nbsp; // cans.fillStyle = 'rgba(200, 200, 200, 0.40)'
&nbsp; &nbsp; // //设置文本内容的当前对齐方式
&nbsp; &nbsp; // cans.textAlign = 'left'
&nbsp; &nbsp; // //设置在绘制文本时使用的当前文本基线
&nbsp; &nbsp; // cans.textBaseline = 'Middle'
&nbsp; &nbsp; // //在画布上绘制填色的文本(输出的文本,开始绘制文本的X坐标位置,开始绘制文本的Y坐标位置)
&nbsp; &nbsp; // cans.fillText(value, can.width / 8, can.height / 2)
&nbsp; &nbsp; // let div = document.createElement('div')
&nbsp; &nbsp; // div.style.pointerEvents = 'none'
&nbsp; &nbsp; // div.style.top = '20px'
&nbsp; &nbsp; // div.style.left = '-20px'
&nbsp; &nbsp; // div.style.position = 'fixed'
&nbsp; &nbsp; // div.style.zIndex = '100000'
&nbsp; &nbsp; // div.style.width = element.scrollHeight + 'px'
&nbsp; &nbsp; // div.style.height = element.scrollHeight + 'px'
&nbsp; &nbsp; // div.style.background =
&nbsp; &nbsp; // &nbsp; 'url(' + can.toDataURL('image/png') + ') left top repeat'
&nbsp; &nbsp; // element.appendChild(div) // 到页面中
&nbsp; &nbsp; html2Canvas(element, {
&nbsp; &nbsp; &nbsp; allowTaint: true,
&nbsp; &nbsp; &nbsp; useCORS: true,
&nbsp; &nbsp; &nbsp; scale: 2, // 提升画面质量,但是会增加文件大小
&nbsp; &nbsp; &nbsp; height: element.scrollHeight, // 需要注意,element的 高度 宽度一定要在这里定义一下,不然会存在只下载了当前你能看到的页面 &nbsp; 避雷避雷!!!
&nbsp; &nbsp; &nbsp; windowHeight: element.scrollHeight
&nbsp; &nbsp; }).then(function (canvas) {
&nbsp; &nbsp; &nbsp; const contentWidth = canvas.width;
&nbsp; &nbsp; &nbsp; const contentHeight = canvas.height;
&nbsp; &nbsp; &nbsp; // 一页pdf显示html页面生成的canvas高度;
&nbsp; &nbsp; &nbsp; const pageHeight = (contentWidth * 841.89) / 592.28;
&nbsp; &nbsp; &nbsp; // 未生成pdf的html页面高度
&nbsp; &nbsp; &nbsp; let leftHeight = contentHeight;
&nbsp; &nbsp; &nbsp; // 页面偏移
&nbsp; &nbsp; &nbsp; let position = 0;
&nbsp; &nbsp; &nbsp; // a4纸的尺寸,html页面生成的canvas在pdf中图片的宽高 &nbsp;//40是左右页边距
&nbsp; &nbsp; &nbsp; const imgWidth = 595.28 - 40;
&nbsp; &nbsp; &nbsp; const imgHeight = (592.28 / contentWidth) * contentHeight;
&nbsp; &nbsp; &nbsp; const pageData = canvas.toDataURL("image/jpeg", 1.0);
&nbsp; &nbsp; &nbsp; const pdf = new JsPDF("p", "pt", "a4");
&nbsp; &nbsp; &nbsp; // 有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
&nbsp; &nbsp; &nbsp; // 当内容未超过pdf一页显示的范围,无需分页
&nbsp; &nbsp; &nbsp; if (leftHeight &lt; pageHeight) {
&nbsp; &nbsp; &nbsp; &nbsp; pdf.addImage(pageData, "JPEG", 20, 20, imgWidth, imgHeight);
&nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; while (leftHeight &gt; 0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pdf.addImage(pageData, "JPEG", 20, position, imgWidth, imgHeight);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; leftHeight -= pageHeight;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; position -= 841.89;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 避免添加空白页
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (leftHeight &gt; 0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pdf.addPage();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; pdf.save(title + ".pdf");
&nbsp; &nbsp; });
&nbsp; }, 1000);
};
export default htmlToPdf;</pre>
</div>
<h4 data-id="heading-9">3. 在 Vue/React 中使用示例</h4>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">&lt;!-- Vue 示例:页面中有一个要转PDF的区域 --&gt;
&lt;template&gt;
&lt;div&gt;
    &lt;!-- 要转PDF的元素:必须有唯一ID --&gt;
    &lt;div id="report-container"&gt;
      &lt;h1&gt;2024年销售报表&lt;/h1&gt;
      &lt;table&gt;...&lt;/table&gt; &lt;!-- 报表内容 --&gt;
    &lt;/div&gt;
    &lt;!-- 下载按钮 --&gt;
    &lt;button @click="downloadPdf"&gt;下载PDF&lt;/button&gt;
&lt;/div&gt;
&lt;/template&gt;

&lt;script&gt;
import htmlToPdf from '@/utils/htmlToPdf'; // 导入函数

export default {
methods: {
    downloadPdf() {
      // 调用函数:参数1=PDF文件名,参数2=目标元素ID(注意加#)
      htmlToPdf('2024年销售报表', '#report-container');
    }
}
}
&lt;/script&gt;</pre>
</div>
<div>
<div>
<h3 data-id="heading-10">常见问题与优化</h3>
<h4 data-id="heading-11">1. 截图不全 / 内容缺失?</h4>
<ul>
<li>确保目标元素的&nbsp;<code>height</code>/<code>scrollHeight</code>&nbsp;正确(函数已处理,但如果元素是动态渲染的,可能需要调整&nbsp;<code>setTimeout</code>&nbsp;延迟时间,比如从 1000ms 改为 2000ms);</li>
<li>避免元素内有&nbsp;<code>position: fixed</code>&nbsp;的内容(会被重复截取)。</li>
</ul>
<h4 data-id="heading-12">2. 图片跨域导致截图空白?</h4>
<ul>
<li>确保&nbsp;<code>html2Canvas</code>&nbsp;配置中&nbsp;<code>allowTaint: true</code>&nbsp;和&nbsp;<code>useCORS: true</code>&nbsp;已开启;</li>
<li>图片服务器需配置 CORS 允许当前域名访问。</li>
</ul>
<h4 data-id="heading-13">3. PDF 清晰度太低?</h4>
<ul>
<li>增大&nbsp;<code>scale</code>&nbsp;参数(比如改为 3),但会导致文件变大;</li>
<li>把&nbsp;<code>toDataURL</code>&nbsp;的质量参数从 1.0 保留(已最优)。</li>
</ul>
<h4 data-id="heading-14">4. 想要启用水印?</h4>
<ul>
<li>取消代码中水印相关的注释,修改&nbsp;<code>value</code>&nbsp;为你的水印文字(比如 “内部资料”);</li>
<li>调整&nbsp;<code>rotate</code>&nbsp;旋转角度、<code>font</code>&nbsp;字体大小、<code>fillStyle</code>&nbsp;透明度(0.4 为半透明)。</li>
</ul>
</div>
<div>
<h3 id="tid-D8HBxE">如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。</h3>
</div>
<p><em><img src="https://img2024.cnblogs.com/blog/2149129/202501/2149129-20250122165814748-630765389.png" alt="" loading="lazy"></em></p>
</div>
</div>
</div><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/19493338
頁: [1]
查看完整版本: 前端 HTML 转 PDF