☰
Vue3中使用html2canvas+jspdf实现HTML转PDF完整方案
2026/11/4 10:00:44 网站建设 项目流程

1. Vue3项目中HTML转PDF的完整实现方案

在Web开发中,经常需要将页面内容导出为PDF文件。最近我在一个Vue3项目中实现了这个功能,通过html2canvas+jspdf的方案完美解决了需求。这个方案最大的优势是能100%还原页面样式,包括CSS3效果和自定义字体。

注意:虽然浏览器有原生的打印功能(Ctrl+P),但无法精确控制分页和样式,而我们的方案可以完全自定义PDF输出效果。

1.1 技术选型分析

为什么选择html2canvas+jspdf组合?

  1. html2canvas:将DOM元素转换为canvas,保留所有视觉样式

    • 支持CSS3属性(圆角、阴影等)
    • 处理跨域图片资源
    • 精确控制渲染质量
  2. jsPDF:在浏览器端生成PDF文件

    • 支持分页、页眉页脚
    • 可设置页面尺寸(A4/A3等)
    • 提供多种输出方式(下载、Blob、Base64)

对比其他方案:

  • window.print():依赖浏览器实现,样式不可控
  • PDFKit:需要Node环境,不适合纯前端方案
  • Puppeteer:需要后端服务支持

2. 环境准备与核心实现

2.1 安装依赖

推荐使用pnpm(当然npm/yarn也可以):

pnpm install jspdf html2canvas

这两个库的版本选择建议:

  • jspdf:最新版(目前2.5.1)
  • html2canvas:1.4.1+(解决了很多渲染问题)

2.2 核心工具函数实现

创建pdfExport.ts工具文件:

import jsPDF from 'jspdf' import html2canvas from 'html2canvas' // 类型定义 type ExportOptions = { fileName: string elementId: string returnType?: 'download' | 'blob' | 'file' margin?: { top?: number right?: number bottom?: number left?: number } } /** * 通用PDF导出方法 * @param options 导出配置项 * @returns 根据returnType返回不同结果 */ export async function exportToPDF(options: ExportOptions) { const { fileName = 'document', elementId, returnType = 'download', margin = { top: 20, right: 10, bottom: 10, left: 10 } } = options const targetElement = document.getElementById(elementId) if (!targetElement) { throw new Error(`Element with ID ${elementId} not found`) } // 1. 使用html2canvas转换DOM为canvas const canvas = await html2canvas(targetElement, { allowTaint: true, useCORS: true, scale: 2, // 提高渲染质量 logging: false, // 关闭调试日志 windowHeight: targetElement.scrollHeight }) // 2. 初始化PDF const PDF = new jsPDF('p', 'pt', 'a4') const pageWidth = PDF.internal.pageSize.getWidth() const pageHeight = PDF.internal.pageSize.getHeight() // 3. 计算布局参数 const contentWidth = pageWidth - margin.left - margin.right const contentHeight = pageHeight - margin.top - margin.bottom const scale = contentWidth / canvas.width const scaledHeight = canvas.height * scale const totalPages = Math.ceil(scaledHeight / contentHeight) // 4. 分页处理 for (let page = 0; page < totalPages; page++) { if (page > 0) PDF.addPage() const sourceY = (page * contentHeight) / scale const sourceHeight = Math.min( contentHeight / scale, canvas.height - sourceY ) // 创建临时canvas处理当前页 const tempCanvas = document.createElement('canvas') const ctx = tempCanvas.getContext('2d')! tempCanvas.width = canvas.width tempCanvas.height = sourceHeight ctx.drawImage( canvas, 0, sourceY, canvas.width, sourceHeight, 0, 0, canvas.width, sourceHeight ) const imgData = tempCanvas.toDataURL('image/jpeg', 1.0) // 添加白色背景 PDF.setFillColor(255, 255, 255) PDF.rect(0, 0, pageWidth, pageHeight, 'F') // 添加内容 PDF.addImage( imgData, 'JPEG', margin.left, margin.top, contentWidth, sourceHeight * scale ) } // 5. 根据返回类型处理结果 switch (returnType) { case 'download': PDF.save(`${fileName}.pdf`) return true case 'blob': return PDF.output('blob') case 'file': const blob = PDF.output('blob') return new File([blob], `${fileName}.pdf`, { type: 'application/pdf' }) } }

3. Vue组件中的实际应用

3.1 准备打印内容

在Vue组件中,我们需要准备一个隐藏的容器来存放要导出的内容:

<template> <div> <!-- 正常显示的内容 --> <button @click="exportPDF">导出PDF</button> <!-- 隐藏的打印内容 --> <div id="pdf-content" style=" position: absolute; left: -9999px; width: 210mm; /* A4宽度 */ padding: 15mm; background: white; " > <h1>这是要导出的标题</h1> <p>这里是正文内容...</p> <!-- 更复杂的内容结构 --> </div> </div> </template> <script setup> import { exportToPDF } from '@/utils/pdfExport' const exportPDF = async () => { try { await exportToPDF({ fileName: '我的文档', elementId: 'pdf-content', returnType: 'download' }) } catch (error) { console.error('导出失败:', error) alert('导出PDF时出错,请重试') } } </script>

3.2 动态内容处理

如果需要导出动态生成的内容,可以使用ref来更新隐藏区域:

<script setup> import { ref } from 'vue' const reportData = ref({ title: '', content: '' }) // 更新打印内容 const updatePrintContent = () => { // 从API获取数据 fetchReportData().then(data => { reportData.value = data }) } const exportPDF = async () => { // 确保数据已加载 await updatePrintContent() // 导出PDF exportToPDF(/* ... */) } </script>

4. 高级功能与优化技巧

4.1 处理特殊样式问题

常见样式问题及解决方案:

  1. 字体缺失:

    @font-face { font-family: 'CustomFont'; src: url('/fonts/custom.woff2') format('woff2'); } .print-area { font-family: 'CustomFont', sans-serif; }
  2. 背景色丢失:

    html2canvas(element, { backgroundColor: '#FFFFFF' // 显式设置背景色 })
  3. 图片模糊:

    html2canvas(element, { scale: 2, // 提高渲染分辨率 dpi: 300 // 提高DPI })

4.2 分页优化

自动分页有时会切断内容,可以手动控制分页:

// 在内容中添加分页标记 <div style="page-break-after: always;"></div> // 或者在jsPDF中手动添加 PDF.addPage()

4.3 性能优化

对于复杂页面:

  1. 先隐藏不需要导出的元素
  2. 分步渲染复杂组件
  3. 使用web worker处理大型文档
async function exportLargeDocument() { // 1. 隐藏非必要元素 document.querySelectorAll('.no-export').forEach(el => { el.style.visibility = 'hidden' }) // 2. 导出PDF await exportToPDF(/* ... */) // 3. 恢复显示 document.querySelectorAll('.no-export').forEach(el => { el.style.visibility = '' }) }

5. 常见问题排查

5.1 内容截断问题

现象:PDF中内容显示不全

解决方案:

  1. 检查容器高度是否足够
    html2canvas(element, { windowHeight: element.scrollHeight + 100 // 增加额外高度 })
  2. 确保所有内容已加载完成
    await nextTick() // Vue中等待DOM更新

5.2 图片不显示

现象:PDF中图片显示为空白

解决方案:

  1. 确保图片允许跨域
    html2canvas(element, { useCORS: true })
  2. 服务器配置CORS头
    Access-Control-Allow-Origin: *

5.3 文字模糊

现象:PDF中文字不清晰

解决方案:

  1. 提高渲染质量
    html2canvas(element, { scale: 3, dpi: 300 })
  2. 使用矢量字体替代图片

5.4 内存不足

现象:大文档导出时浏览器崩溃

解决方案:

  1. 分段渲染
  2. 使用web worker
  3. 降低渲染质量

6. 扩展应用:文件流处理

除了直接下载,我们经常需要将PDF作为文件流处理:

6.1 上传到服务器

const uploadPDF = async () => { const pdfFile = await exportToPDF({ fileName: 'report', elementId: 'content', returnType: 'file' }) const formData = new FormData() formData.append('file', pdfFile) await axios.post('/api/upload', formData, { headers: { 'Content-Type': 'multipart/form-data' } }) }

6.2 预览PDF

使用PDF.js实现预览:

const previewPDF = async () => { const pdfBlob = await exportToPDF({ elementId: 'content', returnType: 'blob' }) const url = URL.createObjectURL(pdfBlob) window.open(url) // 记得释放URL setTimeout(() => { URL.revokeObjectURL(url) }, 1000) }

6.3 邮件附件

生成邮件附件链接:

const sendEmail = async () => { const pdfFile = await exportToPDF({ fileName: '报告', elementId: 'content', returnType: 'file' }) const mailtoLink = `mailto:?subject=查看报告&body=请查收附件&attachment=${encodeURIComponent(pdfFile.name)}` // 实际项目中需要通过API发送带附件的邮件 window.location.href = mailtoLink }

7. 项目实战经验分享

在实际项目中,我总结了以下几点经验:

  1. 字体处理:中文字体文件较大,建议只嵌入使用的字符集

    // 在jsPDF中注册字体 PDF.addFont('customFont.ttf', 'CustomFont', 'normal') PDF.setFont('CustomFont')
  2. 复杂表格:遇到复杂表格时,建议:

    • 使用div+CSS模拟表格布局
    • 避免使用原生table元素
    • 添加边框时使用CSS而非单元格边框
  3. 长内容优化:

    • 实现懒加载分页
    • 使用虚拟滚动技术
    • 对图片进行压缩
  4. 移动端适配:

    @media print { /* 打印专用样式 */ }
  5. 错误处理:

    try { await exportToPDF(/* ... */) } catch (error) { if (error.message.includes('渲染超时')) { // 重试或降低质量 } // 其他错误处理 }

这个方案已经在我们的生产环境运行了半年多,导出了超过10万份PDF文档,稳定性得到了充分验证。对于更复杂的需求,可以考虑集成PDFKit等后端方案作为补充,但对于大多数前端导出需求,这个方案已经完全够用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询