2026/2/16 9:35:53
网站建设
项目流程
sever2012做网站,软件代理商招募,萍乡市网站建设,网页项目策划书模板封装一个好用的页面导出 PDF 工具 Hook (html2canvas jspdf)
在最近的一个项目中#xff0c;遇到一个将页面内容#xff08;详情页#xff09;导出为 PDF的需求,但是好像目前没有直接把dom转成pdf这样一步到位的技术#xff0c;所以自己封装了一个间接转换的方法#xff…封装一个好用的页面导出 PDF 工具 Hook (html2canvas jspdf)在最近的一个项目中遇到一个将页面内容详情页导出为 PDF的需求,但是好像目前没有直接把dom转成pdf这样一步到位的技术所以自己封装了一个间接转换的方法基于 Vue3 TypeScript 的通用 Hook 封装利用 html2canvas 和 jspdf 实现网页内容导出为 PDF并解决了 滚动截断 、 清晰度不足 以及 自动分页 等常见问题。一、 技术选型html2canvas : 将 DOM 元素转换为 Canvas 图片。jspdf : 将 Canvas 图片生成 PDF 文件。封装 : 使用 Hook 方式封装方便复用。二、 核心痛点与解决方案在实现过程中我们通常会遇到以下几个坑导出内容不全 如果页面有滚动条直接截图只能截取可视区域。解法 在截图前将 DOM 高度设置为 auto 并获取 scrollHeight 传递给 html2canvas 的 windowHeight 参数。图片模糊 默认截图出来的 PDF 很模糊。解法 设置 scale: 2 提高 Canvas 的像素密度。PDF 分页问题 长图直接放入 PDF 会被压缩变形。解法 计算内容高度与 A4 纸高度的比例通过循环 addPage() 实现自动分页切割。三、 源码实现新建文件 useExportPdf.ts,下载依赖 html2canvas 和 jspdf 然后引入/* by yours.tools - online tools website : yours.tools/zh/generatebtcwallets.html */ import html2canvas from html2canvas; import jsPDF from jspdf; /** * 导出页面为 PDF * param dom 需要导出的 DOM 元素 * param fileName 导出的文件名不含后缀 */ export const useExportPDF async (dom: HTMLElement, fileName: string) { const element dom; if (!element) { console.error(导出失败未找到导出元素); return; } // 1. 解决滚动截断问题获取元素实际高度 const originalHeight element.scrollHeight; // 临时设置高度为 auto确保能截取到所有内容 const originalStyleHeight element.style.height; element.style.height auto; try { // 2. 将 DOM 转换为 Canvas const canvas await html2canvas(element, { useCORS: true, // 允许跨域图片 scale: 2, // 2倍缩放解决模糊问题 scrollY: -window.scrollY, // 修正滚动条偏移 scrollX: 0, windowHeight: originalHeight, // 告诉 html2canvas 完整高度 }); // 3. 初始化 PDF 实例 // p: 纵向, mm: 单位毫米, a4: 纸张格式 const pdf new jsPDF(p, mm, a4); // A4 纸内容宽度留边距 const imgWidth 190; // 根据宽度计算等比例的高度 const imgHeight (canvas.height * imgWidth) / canvas.width; // 获取 PDF 页面可用高度 const pdfPageHeight pdf.internal.pageSize.getHeight(); // 4. 处理分页逻辑 let position 0; // 第一页 pdf.addImage(canvas, PNG, 10, 10 - position, imgWidth, imgHeight); position pdfPageHeight; // 这里简化处理按页面高度分页 // 如果内容高度超过一页循环添加新页 while (position imgHeight) { pdf.addPage(); // 移动图片位置实现视觉上的“接续” // 注意这里简单的 position pageHeight 可能需要根据实际情况调整 // 比如减去一些边距来防止文字被切断Demo 中使用了简化的逻辑。 pdf.addImage(canvas, PNG, 10, 10 - position, imgWidth, imgHeight); position pdfPageHeight; } // 5. 保存文件 pdf.save(${fileName}.pdf); } catch (error) { console.error(导出 PDF 异常:, error); } finally { // 6. 恢复原始样式 element.style.height originalStyleHeight; } };四、 如何在组件中使用在 Vue 组件中我们只需要获取到 DOM 引用然后调用这个 Hook 即可。/* by yours.tools - online tools website : yours.tools/zh/generatebtcwallets.html */ a-button typeprimary clickexportPDF v-ifdisabled 导出PDF /a-buttonimport { useExportPDF } from //hooks/exportpdf/useExportpdf; // 导出的 DOM 元素 const pdfContainer refHTMLDivElement(null); // 导出 const exportPDF async () { loading.value true; try { await useExportPDF(pdfContainer.value, xxxxpdf); loading.value false; } catch (e) { console.log(e); loading.value false; } };五、 值得注意的事项html2canvas 将dom元素转成canvas图片的时候如果dom元素中有图片需要解决跨域问题这个一般来讲可以在服务端图片源设置 图片的响应头Response Header必须包含 CORS 头允许你的域名访问或者Nginx配置下代理或者前端解决的话就把图片转成Base64格式但是如果图片比较多就不建议前端解决了第一个因为转图片格式图片一多就消耗更多时间第二个是因为转成Base64格式的图片会增加文件大小。还有一个就是如果你想导致的内容之中有些是不用导出或者根据不同条件来区分是否导出可以使用 />。最终实现效果需要导出的页面导出的pdf六、 总结通过这个封装我们实现了一个轻量级且功能完备的 PDF 导出工具。它不仅解决了最让人头疼的 长页面截断 问题还通过 scale 参数保证了导出的清晰度。七、 小思考为啥img标签就能通过图片url加载图片但是把图片转成Canvas就会出现跨域问题简单来说就是 标签只是“展示”数据而 转成Canvas 需要“读取”数据 。浏览器的安全策略同源策略就是“看一眼”和“拿走数据”的区别标签展示数据跟把图片转成转成Canvas浏览器都会请求图片服务器返回图片数据。区别在于1. 标签加载请求头 浏览器发起请求时 Origin 字段可能不被包含或者是 null 或者仅仅作为 Referer 发送。它通常被视为一个“简单请求”。响应头 服务器返回图片数据。通常 不需要 包含 Access-Control-Allow-Origin 等 CORS 相关头信息。结果 浏览器接收到数据渲染引擎直接解码并在屏幕上绘制像素。JavaScript 无法接触到这些数据。2.开启 CORS 的情况 crossoriginanonymous 或 Canvas 请求当你为了 Canvas 导出而给图片添加 crossorigin 属性或者使用 JS fetch 请求图片时请求头 浏览器 强制添加 Origin: https://你的域名.com 字段明确告诉服务器是谁在请求。响应头关键差别 如果服务器支持跨域 必须返回 Access-Control-Allow-Origin: * 或 Access-Control-Allow-Origin: https://你的域名.com 。如果服务器不支持 服务器可能正常返回了图片数据状态码 200但 缺少了 CORS 响应头 。结果 如果 有 CORS 头浏览器认为这份数据是“安全”的允许 Canvas 读取和导出。如果 没有 CORS 头虽然数据下载下来了但浏览器网络层或渲染层会 拦截 这次加载报错 CORS policy 图片甚至可能直接裂开加载失败更别说画到 Canvas 上了。