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

2024-08-30 23:20

本文主要是介绍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. 这里考虑了多张图的场景,不需要的可以简写

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

这篇关于html2canvas、pdf-lib、file-saver将html页面导出成pdf的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1122221

相关文章

解决Maven项目idea找不到本地仓库jar包问题以及使用mvn install:install-file

《解决Maven项目idea找不到本地仓库jar包问题以及使用mvninstall:install-file》:本文主要介绍解决Maven项目idea找不到本地仓库jar包问题以及使用mvnin... 目录Maven项目idea找不到本地仓库jar包以及使用mvn install:install-file基

HTML5中的Microdata与历史记录管理详解

《HTML5中的Microdata与历史记录管理详解》Microdata作为HTML5新增的一个特性,它允许开发者在HTML文档中添加更多的语义信息,以便于搜索引擎和浏览器更好地理解页面内容,本文将探... 目录html5中的Mijscrodata与历史记录管理背景简介html5中的Microdata使用M

html5的响应式布局的方法示例详解

《html5的响应式布局的方法示例详解》:本文主要介绍了HTML5中使用媒体查询和Flexbox进行响应式布局的方法,简要介绍了CSSGrid布局的基础知识和如何实现自动换行的网格布局,详细内容请阅读本文,希望能对你有所帮助... 一 使用媒体查询响应式布局        使用的参数@media这是常用的

HTML5表格语法格式详解

《HTML5表格语法格式详解》在HTML语法中,表格主要通过table、tr和td3个标签构成,本文通过实例代码讲解HTML5表格语法格式,感兴趣的朋友一起看看吧... 目录一、表格1.表格语法格式2.表格属性 3.例子二、不规则表格1.跨行2.跨列3.例子一、表格在html语法中,表格主要通过< tab

Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案

《Vue3组件中getCurrentInstance()获取App实例,但是返回null的解决方案》:本文主要介绍Vue3组件中getCurrentInstance()获取App实例,但是返回nu... 目录vue3组件中getCurrentInstajavascriptnce()获取App实例,但是返回n

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

前端CSS Grid 布局示例详解

《前端CSSGrid布局示例详解》CSSGrid是一种二维布局系统,可以同时控制行和列,相比Flex(一维布局),更适合用在整体页面布局或复杂模块结构中,:本文主要介绍前端CSSGri... 目录css Grid 布局详解(通俗易懂版)一、概述二、基础概念三、创建 Grid 容器四、定义网格行和列五、设置行

前端下载文件时如何后端返回的文件流一些常见方法

《前端下载文件时如何后端返回的文件流一些常见方法》:本文主要介绍前端下载文件时如何后端返回的文件流一些常见方法,包括使用Blob和URL.createObjectURL创建下载链接,以及处理带有C... 目录1. 使用 Blob 和 URL.createObjectURL 创建下载链接例子:使用 Blob

Vuex Actions多参数传递的解决方案

《VuexActions多参数传递的解决方案》在Vuex中,actions的设计默认只支持单个参数传递,这有时会限制我们的使用场景,下面我将详细介绍几种处理多参数传递的解决方案,从基础到高级,... 目录一、对象封装法(推荐)二、参数解构法三、柯里化函数法四、Payload 工厂函数五、TypeScript

Android实现打开本地pdf文件的两种方式

《Android实现打开本地pdf文件的两种方式》在现代应用中,PDF格式因其跨平台、稳定性好、展示内容一致等特点,在Android平台上,如何高效地打开本地PDF文件,不仅关系到用户体验,也直接影响... 目录一、项目概述二、相关知识2.1 PDF文件基本概述2.2 android 文件访问与存储权限2.