前端解决方案:实现网页截图并导出PDF功能

server/2025/4/1 4:41:51/

前端解决方案:实现网页截图并导出PDF功能

前端开发中,我们经常会遇到需要将网页内容导出为PDF的需求。本文将以一个准考证预览和导出的例子,带你一步步实现这个功能。我们会处理包括跨域图片、Canvas绘图、PDF生成等多个技术要点。

请添加图片描述

一、基础环境搭建

首先,我们需要搭建一个基础的HTML结构,并引入必要的依赖。

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>网页截图导出PDF示例</title></head><body><!-- 引入依赖 --><script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script></body>
</html>

这里我们引入了两个重要的库:

  • html2canvas:用于将网页内容转换为canvas图像
  • jsPDF:用于生成PDF文件

二、创建页面内容

接下来,我们创建一个简单的准考证预览界面:

<div id="ticket"><h1>准考证</h1><table border="1"><tr><th>考生姓名</th><th>张三猫</th></tr><tr><td>照片</td><td><img src="https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg" alt="" /></td></tr></table>
</div><button onclick="fetchImage()">检测图片是否支持跨域</button>
<button onclick="newImage()">图片转base64</button>
<button onclick="exportToPDF()">导出PDF准考证</button>

三、处理跨域图片问题

在处理外部图片时,我们首先需要解决跨域问题。

运维:需设置图片允许跨域访问,以阿里云 OSS 跨域规则配置为例。
在这里插入图片描述

前端:先检测图片是否支持跨域访问,支持图片跨域访问的情况下,再把图片转base64。

1. 检测图片跨域支持

function fetchImage() {fetch('https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg').then((res) => {console.log('支持跨域', res.type)}).catch((err) => {console.log('不支持跨域', err)})
}

2. 图片转Base64

function newImage() {// 创建图片const img = new Image()img.src = 'https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg'// 设置跨域img.crossOrigin = 'anonymous'// 监听图片加载img.onload = () => {// 创建canvasconst canvas = document.createElement('canvas')// 设置canvas的宽高canvas.width = img.widthcanvas.height = img.height// 获取canvas的上下文const ctx = canvas.getContext('2d')// 绘制图片ctx.drawImage(img, 0, 0)// 转换为base64const base64 = canvas.toDataURL('image/jpeg')console.log('base64转换成功', base64)}
}

四、图片处理工具函数

为了确保所有图片都能正确加载和处理,我们需要两个重要的工具函数:

1. 转换图片为Base64

async function convertImageToBase64(url) {return new Promise((resolve, reject) => {const img = new Image()img.src = urlimg.crossOrigin = 'anonymous'img.onload = () => {const canvas = document.createElement('canvas')canvas.width = img.widthcanvas.height = img.heightconst ctx = canvas.getContext('2d')ctx.drawImage(img, 0, 0)resolve(canvas.toDataURL('image/jpeg'))}img.onerror = () => {console.log('图片加载失败')reject(new Error('图片加载失败'))}})
}

2. 等待所有图片加载完成

function waitForImagesLoaded() {return Promise.all(Array.from(document.images).filter((img) => !img.complete).map((img) =>new Promise((resolve) => {img.onload = img.onerror = resolve})))
}

五、实现PDF导出功能

最后,我们来实现核心的PDF导出功能:

async function exportToPDF() {try {// 1. 等待所有图片加载await waitForImagesLoaded()// 2. 处理页面中的所有图片const images = document.querySelectorAll('img')for (const img of images) {try {const base64 = await convertImageToBase64(img.src)img.src = base64} catch (e) {console.error('图片转换失败', e)}}// 3. 将页面转换为canvasconst ticket = document.getElementById('ticket')const canvas = await html2canvas(ticket, {scale: 2, // 提高清晰度useCORS: true, // 允许跨域})const imgData = canvas.toDataURL('image/png')// 4. 创建PDF文档const pdf = new jspdf.jsPDF({orientation: 'portrait', // 竖向unit: 'mm', // 单位:毫米format: 'a4', // A4纸张})// 5. 计算适合的图片尺寸const pageWidth = pdf.internal.pageSize.getWidth()const pageHeight = pdf.internal.pageSize.getHeight()const imgWidth = pageWidth - 20 // 左右各留10mm边距const imgHeight = (canvas.height * imgWidth) / canvas.width// 6. 将图片添加到PDF中pdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight)// 7. 下载PDF文件pdf.save('张三的准考证.pdf')} catch (error) {console.error('PDF导出失败', error)}
}

六、完整代码

将上述所有代码组合在一起,就构成了一个完整的网页截图并导出PDF的功能。

<!DOCTYPE html>
<html lang="zh-CN"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>网页截图导出PDF示例</title></head><body><!-- 引入依赖 --><script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script><div id="ticket"><h1>准考证</h1><table border="1"><tr><th>考生姓名</th><th>张三猫</th></tr><tr><td>照片</td><td><img src="https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg" alt="" /></td></tr></table></div><button onclick="fetchImage()">检测图片是否支持跨域</button><button onclick="newImage()">图片转base64</button><button onclick="exportToPDF()">导出PDF准考证</button><script>function fetchImage() {fetch('https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg').then((res) => {console.log('支持跨域', res.type)}).catch((err) => {console.log('不支持跨域', err)})}function newImage() {// 创建图片const img = new Image()img.src = 'https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg'// 设置跨域img.crossOrigin = 'anonymous'// 监听图片加载img.onload = () => {// 创建canvasconst canvas = document.createElement('canvas')// 设置canvas的宽高canvas.width = img.widthcanvas.height = img.height// 获取canvas的上下文const ctx = canvas.getContext('2d')// 绘制图片ctx.drawImage(img, 0, 0)// 转换为base64const base64 = canvas.toDataURL('image/jpeg')console.log('base64转换成功', base64)}}// 先获取图片的base64编码async function convertImageToBase64(url) {return new Promise((resolve, reject) => {const img = new Image()img.src = 'https://xueyingyu.oss-cn-guangzhou.aliyuncs.com/cat.jpg'img.crossOrigin = 'anonymous'img.onload = () => {const canvas = document.createElement('canvas')canvas.width = img.widthcanvas.height = img.heightconst ctx = canvas.getContext('2d')console.log(11111, img)ctx.drawImage(img, 0, 0)resolve(canvas.toDataURL('image/jpeg'))}img.onerror = () => {console.log(222222, '图片加载失败')}})}// 等待图片加载function waitForImagesLoaded() {return Promise.all(Array.from(document.images).filter((img) => !img.complete).map((img) =>new Promise((resolve) => {img.onload = img.onerror = resolve}),),)}// 修改导出函数async function exportToPDF() {// 先等待图片加载await waitForImagesLoaded()// 再处理图片const images = document.querySelectorAll('img')for (const img of images) {try {const base64 = await convertImageToBase64(img.src)img.src = base64} catch (e) {console.error('图片转换失败', e)}}// 截图到canvas中const ticket = document.getElementById('ticket')const canvas = await html2canvas(ticket, {scale: 2, // 缩放比例useCORS: true, // 允许跨域})const imgData = canvas.toDataURL('image/png')// 创建pdfconst pdf = new jspdf.jsPDF({orientation: 'portrait', // 方向: 竖屏unit: 'mm', // 单位: 毫米format: 'a4', // 纸张大小: A4})// 获取pdf的宽高const pageWidth = pdf.internal.pageSize.getWidth()const pageHeight = pdf.internal.pageSize.getHeight()// 计算图片缩放比例以适应页面宽度const imgWidth = pageWidth - 20 // 留边距const imgHeight = (canvas.height * imgWidth) / canvas.width// 添加图片到pdfpdf.addImage(imgData, 'PNG', 10, 10, imgWidth, imgHeight)// 下载pdfpdf.save('张三的准考证.pdf')}</script></body>
</html>

七、技术要点总结

  1. 跨域处理

    • 使用 crossOrigin = 'anonymous' 处理跨域图片
    • 将图片转换为Base64格式避免跨域问题
  2. 异步处理

    • 使用 Promise 处理图片加载
    • 使用 async/await 简化异步代码
  3. Canvas操作

    • 创建Canvas元素
    • 设置Canvas尺寸
    • 在Canvas中绘制图片
  4. PDF生成

    • 设置PDF属性(方向、单位、纸张大小)
    • 计算图片在PDF中的合适尺寸
    • 添加图片到PDF并下载

八、注意事项

  1. 确保服务器端图片资源允许跨域访问(设置正确的CORS头)
  2. 考虑图片加载失败的情况,添加适当的错误处理
  3. 根据实际需求调整PDF的参数(如边距、缩放比例等)
  4. 在生产环境中建议使用可靠的CDN或本地托管依赖库

九、扩展优化

  1. 添加加载提示
  2. 支持自定义PDF文件名
  3. 支持自定义PDF页面大小和方向
  4. 添加水印或其他安全标记
  5. 优化图片质量和文件大小

希望这篇教程能帮助你理解和实现网页截图并导出PDF的功能。如果你有任何问题,欢迎在评论区讨论!


http://www.ppmy.cn/server/180125.html

相关文章

从 Word 到 HTML:使用 Aspose.Words 轻松实现 Word 文档的高保真转换

从 Word 到 HTML&#xff1a;使用 Aspose.Words 轻松实现 Word 文档的高保真转换 前言一、环境准备二、核心代码实现1. 将 Word 转换为 HTML 文件流2. 优化超链接样式 三、测试效果四、总结 前言 在日常开发中&#xff0c;我们经常需要将 Word 文档转换为 HTML&#xff0c;用于…

wordpress主题开发框架(灵狐框架),开发文档使用教程

Fox Framework 开发文档 一、下载与安装 1. 下载 从相应的资源库或者提供的渠道下载 Fox Framework 的压缩包。 2. 安装 将下载的压缩包解压后&#xff0c;把 fox - framework 文件夹上传到 WordPress 网站的 wp - content/plugins 目录下。登录 WordPress 后台&#xff0…

python 将mkv格式视频转换成mp4格式

在Python中&#xff0c;可以使用moviepy库来将MKV格式的视频转换成MP4格式。moviepy是一个用于视频编辑的强大库&#xff0c;支持多种视频格式的处理。 from moviepy.editor import VideoFileClipdef convert_mkv_to_mp4(mkv_file_path, mp4_file_path):try:video VideoFileC…

视图、MySQL、触发器、存储过程、流程控制语句

DAY19.1 Java核心基础 MySQL 视图 数据库中的一张虚拟的表&#xff0c;允许不同用户和不同程序以不同的方式查询同一张表的数据 基于数据表&#xff0c;创建一个虚拟的表&#xff0c;然后可以选择需要展示的字段 为不同的用户创建不同的视图&#xff0c;一个视图包含薪资&…

python+unittest+requests+HTMLRunner编写接口自动化测试集

问题描述&#xff1a; 搭建接口测试框架&#xff0c;执行用例请求多个不同请求方式的接口 实现步骤&#xff1a; ① 创建配置文件config.ini&#xff0c;写入部分公用参数&#xff0c;如接口的基本url、测试报告文件路径、测试数据文件路径等配置项 1 [DATABASE] 2 data_ad…

C++细节知识for面试

1. linux上C程序可用的栈和堆大小分别是多少&#xff0c;为什么栈大小小于堆&#xff1f; 1. 栈&#xff08;Stack&#xff09;大小 栈默认为8MB&#xff0c;可修改。 为什么是这个大小&#xff1a; ​安全性&#xff1a;限制栈大小可防止无限递归或过深的函数调用导致内存…

Web网页内嵌福昕OFD版式办公套件实现在线预览编辑PDF、OFD文档

PDF&#xff0c;即Portable Document Format&#xff0c;用于以一种独立于应用程序、硬件、操作系统的方式共享和查看文档&#xff1b;OFD&#xff0c;即Office Open Document Format for Document&#xff0c;是一种在政府公文和法律文件等领域广泛应用的电子文件格式&#xf…

离线黑客攻击之硬盘加密

1. 引言 在前文中,我们探讨了攻击者在获得物理访问权限后,如何重置 Windows 和 Linux 设备的密码。这使我们从管理员或安全管理者的角度思考:如何防止此类攻击? 尽管 BIOS/EFI 保护措施(如 UEFI Secure Boot)可以在一定程度上限制攻击者的操作,但它们无法防止攻击者直…