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

前端解决方案:实现网页截图并导出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.mzph.cn/pingmian/73446.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】表操作

表操作 一、创建表 1、语句2、语句介绍3、注意事项4、介绍5、示例 二、查看表结构 1、语句2、介绍3、返回的信息4、示例 三、添加字段 1、语句2、语句介绍3、示例 四、修改 1、语句2、语句介绍3、示例 五、删除 1、语句2、示例 六、修改表名 1、语句2、语句介绍3、示例 七、删…

[新闻.AI]国产大模型新突破:阿里开源 Qwen2.5-VL-32B 与 DeepSeek 升级 V3 模型

&#xff08;本文借助 Deepseek-R1 协助生成&#xff09; 在2025年3月24日至25日的短短24小时内&#xff0c;中国AI领域迎来两大重磅开源更新&#xff1a;阿里通义千问团队发布多模态大模型Qwen2.5-VL-32B-Instruct&#xff0c;而DeepSeek则推出编程能力大幅提升的DeepSeek-V3…

深入剖析C# List<T>的底层实现与性能奥秘

一、动态数组的本质&#xff1a;List的架构设计 在C#的集合类型体系中&#xff0c;List作为最常用的线性数据结构&#xff0c;其核心实现基于动态数组机制。与传统数组不同&#xff0c;List通过智能的容量管理策略&#xff0c;在保持数组高速随机访问优势的同时&#xff0c;突…

【单元测试】

一、框架 不同的编程语言有不同的测试框架&#xff0c;以下是一些常见的测试框架&#xff1a; 1&#xff09;Java&#xff1a;JUnit、TestNG2&#xff09;Python&#xff1a;unittest、pytest3&#xff09;JavaScript&#xff1a;Jest、Mocha4&#xff09;C#&#xff1a;NUni…

机器学习——XGBoost

XGBoost(极度梯度提升树&#xff0c;eXtreme Gradient Boosting)是基于GBDT的优化模型&#xff0c;其最大特性在于对GBDT的损失函数展开到二阶导数&#xff0c;使得其梯度提升树模型更接近其真实损失 其XGBoost分类树拟合和预测方法的基本思路为&#xff1a; 遍历所有的树&…

响应“一机两用”政策 ,实现政务外网安全

在数字化办公的浪潮下&#xff0c;企业与政务机构面临着既要保障数据安全&#xff0c;又要高效访问互联网的双重需求。“一机两用”成为解决这一难题的关键。 政策驱动&#xff0c;需求迫切 随着《网络安全法》《数据安全法》等法律法规的相继出台&#xff0c;网络安全防护的要…

【后端】【Django】Django DRF API 单元测试完整方案(基于 `TestCase`)

Django DRF API 单元测试完整方案&#xff08;基于 TestCase&#xff09; 一、方案概述 使用 django.test.TestCase 和 rest_framework.test.APIClient 进行 API 单元测试&#xff0c;确保 API 正确性、权限控制、数据返回格式、业务逻辑 等。 二、基本步骤 使用 setUp() 初始…

文生图语义识别插件使用(controlnet)

1. 插件下载(github) https://github.com/Mikubill/sd-webui-controlnet https://github.com/lllyasviel/ControlNet2. 模型下载(hugging face) https://github.com/Mikubill/sd-webui-controlnet/wiki/Model-download https://huggingface.co/bdsqlsz/qinglong_controlnet-l…

学者观察 | web3.0产业发展与技术融合——北京大学研究员肖臻

导语 肖臻老师认为在未来很长一段时间内&#xff0c;Web 3.0将和现在的Web 2.0共存。Web 3.0和人工智能&#xff08;AI&#xff09;的融合发展前景非常广阔&#xff0c;Web 3.0致力于打造去中心化的互联网生态系统&#xff0c;赋予用户更大的数据所有权和控制权&#xff0c;而…

【模型压缩+推理加速】知识蒸馏综述解读

知识蒸馏综述解读 论文&#xff1a; https://arxiv.org/abs/2006.05525 最近Deepseek R1的技术报告中&#xff0c;训练部分提到使用了知识蒸馏&#xff0c;就像系统性的看看蒸馏算法的原理。看了很多的博客&#xff0c;很多都没有详细把知识蒸馏系统的讲清楚。我们还是读一下…

vivo 湖仓架构的性能提升之旅

作者&#xff1a;郭小龙 vivo互联网 大数据高级研发工程师 导读&#xff1a;本文整理自 vivo互联网 大数据高级研发工程师 郭小龙 在 StarRocks 年度峰会上的分享&#xff0c;聚焦 vivo 大数据多维分析面临的挑战、StarRocks 落地方案及应用收益。 在 即席分析 场景&#xff0c…

代码随想录算法训练营第十四天| 226.翻转二叉树、101. 对称二叉树、104.二叉树的最大深度、111.二叉树的最小深度

今日题目 226.翻转二叉树 题目链接&#xff1a;226. 翻转二叉树 - 力扣&#xff08;LeetCode&#xff09; 思考&#xff1a;翻转二叉树&#xff0c;就是对每一个根节点&#xff0c;都交换左右节点&#xff0c;左右节点进入递归继续交换它们的左右节点。 代码&#xff1a; # De…

Java设计模式--单例模式

单例模式(构造器私有) 单例模式&#xff08;Singleton Pattern&#xff09;是 Java 中最简单的设计模式之一&#xff0c;有以下特点&#xff1a; 单例类只能有一个实例。 单例类必须自己创建自己的唯一实例。 单例类必须给所有其他对象提供这一实例。 反射可以破坏单例模式…

2025年如何避免使用验证码求解器时被IP封禁

引言 2025年&#xff0c;验证码求解器已成为自动化网络抓取和其他在线流程的关键工具。然而&#xff0c;自动化用户面临的一个常见挑战是IP封禁。当网站检测到自动化活动时&#xff0c;通常会阻止发出请求的IP地址&#xff0c;导致验证码挑战无法解决。本文将探讨使用验证码求…

JVM详解(包括JVM内存模型与GC垃圾回收)

&#x1f4d6;前言&#xff1a; 学会使用Java对于一个程序员是远远不够的。Java语法的掌握只是一部分&#xff0c;另一部分就是需要掌握Java内部的工作原理&#xff0c;从编译到运行&#xff0c;到底是谁在帮我们完成工作的&#xff1f; 接下来着重对Java虚拟机&#xff0c;也就…

【无标题】大亚湾文化体育场多美啊。

请推荐一些常用并且免费的&#xff0c;可直接在线运行【Python】代码的平台并列出对应网址 好的&#xff0c;用户想让我推荐一些常用且免费的、可以直接在线运行Python代码的平台&#xff0c;并且需要列出对应的网址。我需要先回想一下自己知道的在线Python运行环境&#xff0…

权限提升—Windows权限提升土豆家族溢出漏洞通杀全系

前言 OK&#xff0c;Java安全更新不下去了&#xff0c;实在是太难啦啊&#xff0c;想起来提权这一块没怎么更新过&#xff0c;接下来都主要是更新提权这一块的文章了&#xff0c;Java安全的话以后有耐心再搞了。 手动提权 今天主要是讲这个手动的提权&#xff0c;手动提权相…

Vue3 知识点总结

Vue3 知识点总结 1. 核心概念 1.1 Composition API 1.1.1 setup 函数 setup是Vue3中的新的配置项&#xff0c;是组件内使用Composition API的入口在setup中定义的变量和方法需要return才能在模板中使用setup执行时机在beforeCreate之前&#xff0c;this不可用 export defa…

python --face_recognition(人脸识别,检测,特征提取,绘制鼻子,眼睛,嘴巴,眉毛)/活体检测

dlib 安装方法 之前博文 https://blog.csdn.net/weixin_44634704/article/details/141332644 环境: python3.8 opencv-python4.11.0.86 face_recognition1.3.0 dlib19.24.6人脸检测 import cv2 import face_recognition# 读取人脸图片 img cv2.imread(r"C:\Users\123\…

【bug】[42000][1067] Invalid default value for ‘xxx_time‘

MySQL错误解决&#xff1a;Invalid default value for xxx_time’问题分析与修复方案 问题描述 在MySQL数据库操作中&#xff0c;当尝试创建或修改表结构时&#xff0c;可能会遇到以下错误信息&#xff1a; [bug] [42000][1067] Invalid default value for xxx_time这个错误…