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])
      // 把整个页面塞到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. 这里考虑了多张图的场景,不需要的可以简写

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

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 Vue 中使用 pdf-libhtml2canvas PDF 文件需要进行以下几个步骤: 1. 安装依赖库 在项目中使用 npm 或者 yarn 安装 pdf-libhtml2canvas 依赖库。 2. 引入依赖库 在需要使用的 Vue 组件中引入 pdf-libhtml2canvas 依赖库。 ```javascript import { PDFDocument, StandardFonts } from 'pdf-lib'; import html2canvas from 'html2canvas'; ``` 3. 创建 PDF 文档对象 使用 pdf-lib 库中的 PDFDocument 类来创建 PDF 文档对象,并添加需要生 PDF 的内容。 ```javascript const pdfDoc = await PDFDocument.create(); const page = pdfDoc.addPage([595.28, 841.89]); // 设置页面大小 const font = await pdfDoc.embedFont(StandardFonts.HelveticaBold); // 在页面上添加文本内容 page.drawText('Hello, PDF!', { x: 50, y: 700, size: 24, font: font, color: rgb(0, 0, 0), }); ``` 4. 将 HTML 元素转换为 Canvas 使用 html2canvas 库中的 toCanvas 方法将需要生 PDFHTML 元素转换为 Canvas 对象。 ```javascript const canvas = await html2canvas(document.getElementById('pdf-content')); ``` 5. 将 Canvas 对象转换为 PNG 图片 将上一步得到的 Canvas 对象转换为 PNG 格式的图片。 ```javascript const pngUrl = canvas.toDataURL('image/png'); ``` 6. 添加 PNG 图片到 PDF 文档中 将上一步得到的 PNG 图片添加到 PDF 文档对象中。 ```javascript const pngImage = await pdfDoc.embedPng(pngUrl); page.drawImage(pngImage, { x: 50, y: 450, width: 500, height: 250, }); ``` 7. 导出 PDF 文件 使用 pdf-lib 库中的 save 方法将生PDF 文件导出。 ```javascript const pdfBytes = await pdfDoc.save(); const blob = new Blob([pdfBytes], { type: 'application/pdf' }); saveAs(blob, 'example.pdf'); // 将 PDF 文件保存到本地 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值