html2canvas、pdf-lib、file-saver将html页面导出成pdf

html2canvas、pdf-lib、file-saver将html页面导出成pdf

项目背景

需要根据用户的账号信息,生成一个pdf报告发给客户,要求报告包含echart饼图、走势图等。

方案

使用html2canvas,将页面转成图片,再通过pdf-lib将图片转成pdf文件,最后通过file-saver保存到客户端。

需要注意:由于截长图放到pdf里面,会导致图片被截断,就是可能是某一行的文案或者某一个图被中间截断,所以方案是出一版ui设计稿,把页面、边框、背景都设计好,并且每一页的模块也是需要固定的

这里要求设计出的设计稿是595 * 842的,刚好的标准的A4格式,也就是pdf默认的大小格式,截图生成pdf采取的是分页截图,通过循环每次生成一页图片,放到pdf文件,最终导出文件
在这里插入图片描述

在这里插入图片描述

使用html2canvas,将页面转成图片

const canvas = await html2canvas(element, {scale: 3, // 提高分辨率height: element.scrollHeight,width: element.scrollWidth,useCORS: true,
})
  1. scale: 用于提高Canvas的分辨率。这个值通常大于1,以便在Canvas上渲染出更清晰的图像
  2. height和width: 分别设置为element.scrollHeight和element.scrollWidth,这确保了Canvas的大小与元素的可滚动区域的大小相匹配,即使元素的实际显示区域(即视口)较小
  3. useCORS: 设置为true,允许html2canvas处理跨域图像。这意味着如果元素中包含了来自不同源的图像,并且这些图像服务器支持CORS,那么这些图像也可以被正确地渲染到Canvas上

通过pdf-lib将图片转成pdf文件并保存

const canvas = await html2canvas(element, {scale: canvasConfig.scale, // 提高分辨率height: element.scrollHeight,width: element.scrollWidth,useCORS: true,
})
const imgData = canvas.toDataURL('image/png')
const pngImage = await pdfDoc.embedPng(imgData)
let page = pdfDoc.addPage([595, 842])
// 把整个页面塞到pdf
page.drawImage(pngImage, {x: 0,y: 0,width: canvas.width,height: canvas.height,
})
const pdfBytes = await pdfDoc.save()
const blob = new Blob([pdfBytes], { type: 'application/pdf' })
saveAs(blob, 'example.pdf')
  1. 通过pdfDoc.embedPng()将html2canvas生成的图片转成png图片
  2. pdfDoc.addPage新增一页pdf,宽高定义为标准的A4格式
  3. 将图片塞到pdf里面,需要注意:y值得从左下角开始计算得,并不是左上角
  4. 最后将pdf转成blob,通过saveAs保存为pdf文件

完整代码

const canvasConfig = {scale: 3,pageWidth: 595,pageHeight: 842,
}const processCreatePdf = async () => {try {const pdfDoc = await PDFDocument.create([canvasConfig.pageWidth, canvasConfig.pageHeight])for (let pageIndex = 1; pageIndex <= 10; pageIndex++) {const element = document.getElementById(`page-${pageIndex}`)if (!element) { continue }const canvas = await html2canvas(element, {scale: canvasConfig.scale, // 提高分辨率height: element.scrollHeight,width: element.scrollWidth,useCORS: true,})const imgData = canvas.toDataURL('image/png')const pngImage = await pdfDoc.embedPng(imgData)let page = pdfDoc.addPage([canvas.width, canvas.height])// 把整个页面塞到pdfpage.drawImage(pngImage, {x: 0,y: 0,width: canvas.width,height: canvas.height,})}const pdfBytes = await pdfDoc.save()const blob = new Blob([pdfBytes], { type: 'application/pdf' })saveAs(blob, 'example.pdf')} catch (e) {console.error(e)}
}

生成得pdf效果如下:
在这里插入图片描述
在这里插入图片描述

整体得下效果还行,但是生成得echart饼图比较模糊
在这里插入图片描述

试了很多方法都无法解决该问题,最终只能通过调用echart官方api,生成图片,再把图片覆盖到页面中得饼图位置

echart图片模糊解决方案

先贴上代码

// 饼图子组件中、生成组件方法
useImperativeHandle(ref, () => ({getCanvasImg: (pageHeight, scale) => {let { x, y, width, height } = getChartPosition(pageHeight, 'chart-id', scale)x =  x * scaley = y * scalewidth = width * scaleheight = height * scaleconst url = getCanvasImg(echartRef, width, height)return { x, y, width, height, url }},
}))// 获取echart在pdf的位置
const getChartPosition = (pageHeight, chartId) => {const chartElement = document.getElementById(chartId) || {offsetLeft: 0,offsetTop: 0,clientHeight: 0,clientWidth: 0,}return {x: chartElement.offsetLeft,y: pageHeight - chartElement.offsetTop - chartElement.clientHeight,width: chartElement.clientWidth,height: chartElement.clientHeight,}
}// 调用官方api生成图片
const getCanvasImg = (ref, width, height) => {const chart = ref.current?.getEchartsInstance()return chart?.getDataURL({type: 'png',pixelRatio: 3,backgroundColor: '#FEFBF9',width: width,height,})
}
  1. 通过getChartPosition获取echart在pdf页面中的定位,包括x、y、width、height
  2. 通过echart官方api生成图片,这里width、height规定为echart元素的大小,避免图片变形
  3. 可以看到x、y、width、height都成以了scale,这个是放大的倍数,和上述html2canvas生成截图的参数一致

在生成页面中,调用方法getCanvasImg动态插入echart

const processCreatePdf = async () => {try {const pdfDoc = await PDFDocument.create([canvasConfig.pageWidth, canvasConfig.pageHeight])for (let pageIndex = 1; pageIndex <= 10; pageIndex++) {const element = document.getElementById(`page-${pageIndex}`)if (!element) { continue }const canvas = await html2canvas(element, {scale: canvasConfig.scale, // 提高分辨率height: element.scrollHeight,width: element.scrollWidth,useCORS: true,})const imgData = canvas.toDataURL('image/png')const pngImage = await pdfDoc.embedPng(imgData)let page = pdfDoc.addPage([canvas.width, canvas.height])// 把整个页面塞到pdfpage.drawImage(pngImage, {x: 0,y: 0,width: canvas.width,height: canvas.height,})await processInsertChartImg(pdfDoc, page, pageIndex)}const pdfBytes = await pdfDoc.save()const blob = new Blob([pdfBytes], { type: 'application/pdf' })saveAs(blob, 'example.pdf')} catch (e) {// eslint-disable-next-line no-consoleconsole.error(e)}
}/** 由于直接生成的canvas的echart不清晰,手动插入echart */
const processInsertChartImg = async (pdfDoc, page, pageIndex) => {const targetRef = pageRefMap[`${pageIndex}`]if (!targetRef) { return }for (let i = 1; i <= 5; i++) {const funcName = `getCanvasImg${i > 1 ? i : ''}`const func = targetRef.current[funcName]if (!func) { continue }const { x, y, url, width, height } = func(canvasConfig.pageHeight, canvasConfig.scale, canvasConfig.pageWidth)if (!url) { continue }const chartImg = await pdfDoc.embedPng(url)page.drawImage(chartImg, { x, y, width, height })}
}
  1. 多了processInsertChartImg方法用于动态插入echart
  2. 这里考虑了多张图的场景,不需要的可以简写

效果:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/52844.shtml

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

相关文章

Visual Studio Code离线汉化

从官网下载Visual Studio Code安装包后&#xff0c; 下载Visual Studio Code&#xff1a;https://code.visualstudio.com/ 若因网络等问题无法在线安装语言包&#xff0c;可以尝试离线安装&#xff1a; 从官网下载语言包&#xff1a; Extensions for Visual Studio family …

Stable Diffusion majicMIX_realistic模型的介绍及使用

一、简介 majicMIX_realistic模型是一种能够渲染出具有神秘或幻想色彩的真实场景的AI模型。这个模型的特点是在现实场景的基础上&#xff0c;通过加入一些魔法与奇幻元素来营造出极具画面效果和吸引力的图像。传统意义的现实场景虽然真实&#xff0c;但通常情况下缺乏奇幻性&a…

【网络世界】网络层

目录 &#x1f308;前言&#x1f308; &#x1f4c1; 网络层 &#x1f4c1; IPV4 &#x1f4c2; 什么是IP地址 &#x1f4c2; 网段划分 &#x1f4c2; 特殊IP &#x1f4c2; 内网和公网 &#x1f4c2; IPV4的危机 &#x1f4c1; IP协议格式 &#x1f4c1; 路由 &#x1f…

极限的性质【上】《用Manim可视化》

通过前面的极限的定义&#xff0c;现在是计算极限的时候了。然而&#xff0c;在此之前&#xff0c;我们需要一些极限的性质&#xff0c;这将使我们的工作变得简单一些。我们先来看看这些。 极限的性质&#xff1a; 1.常数对极限的影响 1.首先&#xff0c;我们假设和存在&…

通过Origin提取图片数据

第一步&#xff1a; Tool --> Digitizer 第二步&#xff1a;点击文件&#xff0c;导入图片 第三步&#xff1a;设置坐标轴位置和数值&#xff08;Edit Aix&#xff09; 滑动鼠标放大图片&#xff0c;将X1移动到0&#xff0c;X2移动到80&#xff0c;Y1移动到97.0&#xff0c…

Kubernetes 上安装 Jenkins

安装 Helm curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash添加 Jenkins Helm 仓库 首先添加 Jenkins Helm 仓库 helm repo add jenkins https://charts.jenkins.io helm repo update安装 Jenkins 使用 Helm 安装 Jenkins 的最新版本&…

本地部署Xinference实现智能体推理工作流(二)

第二篇章 Dify接入 Xinference 部署的本地模型 1. 安装Dify 克隆 Dify 源代码至本地。 git clone https://github.com/langgenius/dify.git 2. 启动Dify 进入 Dify 源代码的 docker 目录&#xff0c;执行一键启动命令&#xff1a; cd dify/docker cp .env.example .env d…

【OWOD论文】开放世界中OD代码_2_模型部分

简介 本文记录OWOD代码中的模型代码部分。数据部分可看我上一个博客【【OWOD论文】开放世界中OD代码_1_数据部分-CSDN博客】 模型代码 1 起步 在代码中找到 detectron2\engine\defaults.py DefaultTrainer类 __init__方法 根据上述 build_model 回溯到 detectron2\modeling\…

无人机校企合作:组装、维修、研发全面提升学生技能方好就业

无人机校企合作在组装、维修、研发等方面全面提升学生技能&#xff0c;进而促进学生就业&#xff0c;是一个具有前瞻性和实践性的教育模式。以下是对该合作模式的详细分析&#xff1a; 一、合作背景与意义 随着无人机技术的快速发展和广泛应用&#xff0c;市场对无人机专业人…

叉车(工业车辆)安全管理系统,云端监管人车信息运营情况方案

近年来&#xff0c;国家和各地政府相继出台了多项政策法规&#xff0c;从政策层面推行叉车智慧监管&#xff0c;加大叉车安全监管力度。同时鼓励各地结合实际&#xff0c;积极探索智慧叉车建设&#xff0c;实现作业人员资格认证、车辆状态认证、安全操作提醒、行驶轨迹监控等&a…

react学习之useState和useEffect

useState useState 可以使函数组件像类组件一样拥有 state&#xff0c;函数组件通过 useState 可以让组件重新渲染&#xff0c;更新视图。 实际使用 setstate()中回调函数的返回值将会成为新的state值回调函数执行时&#xff0c; React会将最新的state值作为参数传递 const A…

【HarmonyOS 4.0】@BuilderParam 装饰器

1. BuilderParam 装饰器 BuilderParam 装饰器用于装饰自定义组件(struct)中的属性&#xff0c;其装饰的属性可作为一个UI结构的占位符&#xff0c;待创建该组件时&#xff0c;可通过参数为其传入具体的内容。参数必须满足俩个条件&#xff1a; 2.1 参数类型必须是个函数&#x…

windows安全软件之火绒杀毒的密码忘记后处理

一、问题描述 某次&#xff0c;想升级系统补丁&#xff0c;但多次尝试后都失败&#xff0c;排查杀毒软件影响过程中&#xff0c;发现火绒杀毒配置了密码保护&#xff0c;但因时间太久&#xff0c;密码已无从考证&#xff0c;那我们应该怎样处理这种情况呢&#xff1f; 二、处…

鸿蒙XComponent组件的认识

概述&#xff1a; XComponent组件作为一种渲染组件&#xff0c;通常用于满足开发者较为复杂的自定义渲染需求&#xff0c;例如相机预览流的显示、游戏画面的渲染、自定义视频播放器等等。其中Native API是其核心内容&#xff01; 其可通过指定其type字段来实现不同的功能&…

jenkins安装k8s插件发布服务

1、安装k8s插件 登录 Jenkins&#xff0c;系统管理→ 插件管理 → 搜索 kubernetes&#xff0c;选择第二个 Kubernetes&#xff0c;点击 安装&#xff0c;安装完成后重启 Jenkins 。 2、对接k8s集群、申请k8s凭据 因为 Jenkins 服务器在 kubernetes 集群之外&#xff0c;所以…

解决huggingface下载时Username/Password Authentication Failed.问题

项目场景&#xff1a; 使用huggingface 下载数据集。 问题描述 运行命令&#xff1a; wget https://huggingface.co/datasets/yangtaointernship/RealEstate10K-subset/resolve/main/google_scanned_objects.zip?downloadtrue 完整报错如下&#xff1a; --2024-08-30 15:…

Windows通过网线连接开发板共享网络

Windows端 打开更开适配器选项右键WLAN–属性–共享 右键以太网–属性–Internet协议版本4(TCP/IPv4) 记住IP地址 开发板端 查看网卡 ifconfig设置IP在同一网段 ifconfig eth0 192.168.137.2 netmask 255.255.255.0设置网关 route add default gw 192.168.137.1配置DNS su…

哪个牌子的电容笔好用又实惠?西圣、绿联、摩米士电容笔实测大比拼

​现在市面上的电容笔很多&#xff0c;在选择时会让人感到很纠结。那么多的选择&#xff0c;究竟哪个牌子的电容笔好用又实惠呢&#xff1f;一款优质的电容笔应考虑握持舒适度、笔尖材质、电池续航能力以及书写流畅度等因素。作为一位多年的数码爱好者&#xff0c;我今天将针对…

Android APK打包脚本

build.gradle版本 同目录创建config.gradle文件写入需要的信息入 config.gradle文件内容 ext { /*** 自定义APP运行环境* dev: 开发* test: 测试* pro: 生产*/ env "pro" /*** 动态参数配置&#xff0c;根据自己需要添加参数* APP_ID: 包名* VERSION_CODE: 版本号…

国产网卡品牌崛起,做好网络信息安全的“守门人”

在信息技术日新月异的时代背景下&#xff0c;信息安全不仅关乎个人隐私保护&#xff0c;更是国家安全与经济发展的基石。LR-LINK联瑞凭借其前瞻性的视野和深厚的研发实力&#xff0c;成功自主研发出全国产化的FPGA&#xff08;现场可编程门阵列&#xff09;网闸隔离卡方案&…