在网页开发中,实现 PDF 下载功能是常见的需求。以下是几种主流实现方式及其详细代码示例:
方案一,使用浏览器原生API(window.print)
<!DOCTYPE html><html><head> <title>打印为PDF</title> <style> @media print { .no-print { display: none; } body { margin: 0; padding: 10mm; } } </style></head><body> <div id="printable-content"> <h1>可打印内容</h1> <p>使用浏览器打印功能保存为PDF</p> </div>
<button class="no-print" onclick="window.print()">打印/保存为PDF</button></body></html>
这个插件仅仅是唤起打印的功能,让用户另存为 pdf 不合适
方案二,使用纯前端方案(jsPDF + html2canvas)
<!DOCTYPE html><html><head> <title>HTML转PDF</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script> <style> #content { width: 800px; padding: 20px; background: #f5f5f5; } </style></head><body> <div id="content"> <h1>这是要导出为PDF的内容</h1> <p>使用jsPDF和html2canvas库可以轻松实现HTML转PDF功能</p> <table border="1"> <tr><th>姓名</th><th>年龄</th></tr> <tr><td>张三</td><td>25</td></tr> </table> </div>
<button onclick="generatePDF()">下载PDF</button>
<script> function generatePDF() { const { jsPDF } = window.jspdf; const element = document.getElementById('content');
html2canvas(element).then(canvas => { const imgData = canvas.toDataURL('image/png'); const pdf = new jsPDF('p', 'mm', 'a4'); const imgProps = pdf.getImageProperties(imgData); const pdfWidth = pdf.internal.pageSize.getWidth(); const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight); pdf.save('document.pdf'); }); } </script></body></html>
看着几乎完美,这个技术栈,最核心的就是:必须要用到 dom 元素渲染,试想一下,你做了一个导出功能,总不能让客户必须先打开页面等 html 渲染完后,再导出吧?或者display:none,打印出来一个空白。
此路不通,就只能重新寻找新的方向
方案三,html2pdf
npm install html2pdf.js
<template> <div class="container"> <button @click="generatePDF">下载PDF</button> </div></template><script setup>import html2pdf from 'html2pdf.js'let element = ` <h1>前端人</h1> <p>学好前端,走遍天下都不怕</p> ...`;function generatePDF() { const opt = { margin: 10, filename: 'hello_world.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' } }; html2pdf().from(element).set(opt).save();}</script>
功能正常,似乎一切都完美
问题没有想的那么简单如果我们的html是纯文本元素,这程序跑起来没有任何问题,但我们抓取的信息都源于互联网,html结构怎么可能会这么简单?如果我们的html中包含图片信息 ,此时你会发现,导出来的 pdf,图片占位处是个空白块
那我理解的图片同步加载是什么意思呢?简单来说,就是将图片转成Base64,因为这种方式,即使说无网的情况也能正常加载图片,因此我凭感觉断定,这就是图片同步加载
基于这个思路,我写了个完整 demo
<template> <div class="container"> <button @click="generatePDF">下载PDF</button> </div></template><script setup>import html2pdf from 'html2pdf.js'async function convertImagesToBase64(htmlString) { const tempDiv = document.createElement('div'); tempDiv.innerHTML = htmlString; const images = tempDiv.querySelectorAll('img'); for (const img of images) { try { const base64 = await getBase64FromUrl(img.src); img.src = base64; } catch (error) { console.error(`无法转换图片 ${img.src}:`, error); } } return tempDiv.innerHTML;}function getBase64FromUrl(url) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'Anonymous'; img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); const dataURL = canvas.toDataURL('image/png'); resolve(dataURL); }; img.onerror = () => { reject(new Error('图片加载失败')); }; img.src = url; });}let element = ` <div> <img src='http://t13.baidu.com/it/u=2041049195,1001882902&fm=224&app=112&f=JPEG?w=500&h=500' style="width: 300px;" /> <p>职业:前端</p> <p>技能:唱、跳、rap</p> </div>`;function generatePDF() { element =`<style> img { max-width: 100%; max-height: 100%; vertical-align: middle; height: auto !important; width: auto !important; margin: 10px 0; } </style>` + element; convertImagesToBase64(element) .then(convertedHtml => { const opt = { margin: 10, filename: '前端大法好.pdf', image: { type: 'jpeg', quality: 0.98 }, html2canvas: { scale: 2 }, jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' } }; html2pdf().from(convertedHtml).set(opt).save(); }) .catch(error => { console.error('转换过程中出错:', error); });}</script>
此时就大功告成啦!不过得提一句:图片的 URL 链接必须是同源或者允许跨越的,否则就会存在图片加载异常的问题。
阅读原文:原文链接
该文章在 2025/7/3 14:24:50 编辑过