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

news2024/9/28 15:30:50

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])
      // 把整个页面塞到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')
  } 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 * scale
    y = y * scale
    width = width * scale
    height = height * scale
    const 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])
      // 把整个页面塞到pdf
      page.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-console
    console.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.coloradmin.cn/o/2088372.html

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

相关文章

食家巷一窝丝:匠心传承的美味传奇

在美食的广袤天地中&#xff0c;有一种独特的存在 —— 食家巷一窝丝。它不仅仅是一道美食&#xff0c;更是一种文化的传承&#xff0c;一段历史的记忆。食家巷一窝丝&#xff0c;以其精湛的制作工艺和独特的口感令人陶醉。每一根丝都细如发丝&#xff0c;均匀整齐&#xff0c;…

基于CW2217B的库仑计硬件设计

一、CW2217简介: CW2217B是一款适用于穿戴设备的超低功耗锂电池电量计芯片。芯片监测电池在充放电状态下的电压,电流和温度,运行专利“FastCali”电量计算法,结合电池建模信息,可准确计算电池的剩余电量。CW2217B适用于包括锂锰,锂钴和聚合物等多种类型的锂电池应用。 …

fastadmin 列表显示渲染图片

想把地址显示成图片 在对应的js文件里加上这句话 formatter: Table.api.formatter.image 如果想点击图片列可以看图片的大图&#xff0c;则需要加上 events: Table.api.events.image 效果 Table.api.formatter Table.api.formatter封装了许多FastAdmin表格列表中常用的单元…

从入门到精通:TEMU商家如何利用自养号测评打造爆款

TEMU平台以其独特的魅力和无限潜力&#xff0c;吸引着众多商家竞相入驻。然而&#xff0c;面对激烈的市场竞争&#xff0c;如何快速提高销量&#xff0c;成为摆在每位商家面前的重大课题。本文将深入剖析一种高效且具策略性的方法——精细化自养号测评&#xff0c;帮助TEMU商家…

比较器(过零/非过零检测+噪声的影响+滞回+输出限幅+窗口比较器)

2024-8-30&#xff0c;星期五&#xff0c;7:25&#xff0c;天气&#xff1a;晴&#xff0c;心情&#xff1a;晴。今天周五啦&#xff0c;终于可以休息了&#xff0c;也祝大家周末快乐&#xff0c;玩的开心&#xff01;。 今天开始了第八章基本运算放大器电路的学习&#xff0c…

Python计算机视觉 第6章-图像聚类

Python计算机视觉 第6章-图像聚类 6.1 K-means聚类 K-means 聚类 是一种常用的无监督学习算法&#xff0c;用于将数据集划分为 ( K ) 个簇。 算法步骤 选择 ( K ) 个初始簇中心&#xff08;可以是随机选择或其他启发式方法&#xff09;。将每个数据点分配到距离其最近的簇…

排查C++程序CPU异常占用方法

如果服务器资源允许可以考虑一些CPU资源占用工具&#xff0c;例如Perf、SystemTap。 服务器资源不允许的情况下可以使用GDB调试。 步骤如下&#xff1a; 1、使用top查看CPU高占用的程序&#xff0c;记下它的进程ID&#xff1b; 2、多线程服务排查时&#xff0c;需要排查对应…

案例分享 | Digimat应用于金属材料的仿真

Digimat——应用于金属材料的仿真 Digimat是一款专注于多尺度复合材料非线性材料本构预测和材料建模的商用软件包。Digimat能够帮助用户预测多相材料的宏观性能&#xff0c;支持的材料范围涉及包含连续纤维、长纤维、短纤维、纤维编织、晶须、颗粒、片层等所有增强相和包括树脂…

代理IP设置白名单:让你的网络更安全高效

在网络世界中&#xff0c;代理IP不仅能帮助我们提升网络速度、保护隐私&#xff0c;还能通过设置白名单来进一步增强网络的安全性和稳定性。本文将详细介绍代理IP设置白名单的方法和注意事项&#xff0c;让你在使用代理IP时更加得心应手。 什么是代理IP白名单&#xff1f; 代…

11款主流图纸加密软件盘点,图纸加密软件大盘点

在数字化时代&#xff0c;图纸作为企业核心知识产权的重要组成部分&#xff0c;其安全保护显得尤为重要。随着技术的发展&#xff0c;越来越多的图纸加密软件应运而生&#xff0c;为企业提供了多样化的安全解决方案。以下是11款主流图纸加密软件的详细介绍&#xff0c;它们各自…

Linux基础知识(一、什么是Linux)

一、Linux之父——林纳斯本纳第克特托瓦兹&#xff08;Linus Benedict Torvalds&#xff09; Linux操作系统最初是在1991年10月份由芬兰赫尔辛基大学的在校生Linus Torvalds所发布,最初被发布的LINUX0.02版本因其高质量的代码与开放源代码&#xff0c;迅速引起了一大批黑客的加…

空间旋转与四元数

参考资料&#xff1a;https://krasjet.github.io/quaternion/quaternion.pdf 一、二维空间与复数 已知复数 z a b i zabi zabi&#xff0c;我们规定其向量表示形式为 [ a b ] \left [ \begin{matrix}a\\b\end{matrix}\right ] [ab​]。我们可以将其看成是复数域的两个基底 {…

【生日视频制作】美女举牌变魔术卡牌AE模板修改文字软件生成器教程特效素材【AE模板】

美女举牌变魔术卡牌生日视频制作教程AE模板改字软件生成器素材 怎么如何做的【生日视频制作】美女举牌变魔术卡牌AE模板修改文字软件生成器教程特效素材【AE模板】 生日视频制作步骤&#xff1a; 安装AE软件 下载AE模板 把AE模板导入AE软件 修改图片或文字 渲染出视频

活动|华院计算惊艳亮相第十届博博会,以AI驱动数智文博

2024年8月23日至26日&#xff0c;第十届“中国博物馆及相关产品与技术博览会”&#xff08;以下简称“博博会”&#xff09;花落草原都市内蒙古自治区呼和浩特市敕勒川国际会展中心。“博博会”自2004年创办以来&#xff0c;已成功在多个城市举办&#xff0c;成为我国文博界最具…

C语言 ——— 文件的随机读写

目录 学习并使用fseek函数​编辑 学习并使用ftell函数​编辑 学习并使用rewind函数​编辑 学习并使用fseek函数 函数的功能&#xff1a; 根据文件指针的位置和偏移量来定位文件指针 函数的参数&#xff1a; FILE* stream&#xff1a;文件类型的指针 long int offset&am…

英飞凌HSM内核开发-软件工程介绍

介绍 一个具有HSM核心的安全软件解决方案至少由两个项目组成&#xff1a; 一个用于HSM&#xff08;即“veHsm配置”&#xff09;。一个用于主机&#xff08;即“主机配置”&#xff09;。 如果主机核心上的软件包含第三个AUTOSAR基础软件&#xff08;BSW&#xff09;堆栈&#…

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

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

行情能反转吗?想开个两融账户融资融券利率最低多少?

今日早盘指数放量大涨&#xff0c;走出大阳线。电子板块表现活跃&#xff0c;银行板块表现落后。截止11点30分&#xff0c;上证指数涨1.34%&#xff0c;深成指涨2.80%&#xff0c;创业板指涨3%&#xff0c;北证50指数涨2.38%。市场放量上涨是否迎来拐点&#xff1f;不妨大胆预测…

指针初阶(数组指针与二维数组)

0.二维数组特性 ①.存储格式 二维数组&#xff0c;在存储空间内的存储顺序是连续存储&#xff0c;按行优先存。 假设定义一个2X3的数组&#xff0c;其在存储空间的存储格式如下&#xff1a; ②.表示方法 且二维数组还有一个特性&#xff0c;例如有二维数组 a[3][2] ,那么 a[0] …

关于SSL认证后出现的http和https无法同时访问问题

--痛苦是人最好的试金石 Pain is mans best litmus test 问题&#xff1a; 我现在在我的Tomcat上面加了一个SSL 认证&#xff0c;但是我就是指认证了一个xxx.xxx.com的域名&#xff0c;我其他的域名 也在同一个server.xml配置文件中&#xff0c;现在xxx.xx.com可以https可以使用…