关于前端导出pdf的坑/html2canvas/pdf-lib/dom-to-image/jspdf

项目场景:

最近在做AIGC这快,由AI生成pdf,再导出pdf,需要导出PDF格式

实现思路:

正常前端导pdf实现思路就是 将html页面生成pdf文件,需首先将页面转换为图片,然后再输出成pdf。

一开始根据常规方法,使用html2canvas和jspdf,

 const pdfDoms = document.querySelectorAll('.pdfDom');
 const doc = new jsPDF('l', 'pt', [842, 598]);
  let newArr = []
 for (let index = 0; index < pdfDoms.length; index++) {
    let dom = pdfDoms[index];
    let canvas = await html2canvas(dom, { useCORS: true, scale: 2 })
    let imgData = canvas.toDataURL('image/png');
    let width = dom.offsetWidth;
    let height = dom.offsetHeight;
    let bili = width / height;
    let height2 = 842 / bili;
 

   if (index != pdfDoms.length - 1) {
      newArr.push({
        size: {
          width: 842,
          height: height2,
        },
        content: imgData
      })
      

    }
}

大概走这么个流程,先建一个宽842,高598的pdf,然后每页根据dom节点的高度,自适应的把每页插入pdf,最后导出。好了,第一个坑来了,生成的每页pdf的高度,全是一样的,改不了,因为最外层写死了,当时想到一个方案,要不把每个导出的dom节点高度固定吧,但是这样一来,用户的内容就有限了,这个肯定行不通(于是我在那调节高度,这个地方踩坑了3天)

后来无意中发现一个库(PDF-LIB),地址:SaveOptions · PDF-LIB,看了一下官方文档,好像需要把每个数据节点传入,再生成pdf,想了一下准备放弃,这时候突然看到可以直接把图片导pdf的一个小demo,于是试了试,

const topdf = async (imgs, title) => {
  const doc = await PDFDocument.create()
  for (const key in imgs) {
    if (Object.hasOwnProperty.call(imgs, key)) {
      const element = imgs[key];
      const image = await doc.embedPng(element)
      const pdfPage = doc.addPage([image.width, image.height])
      pdfPage.drawImage(image, {
        x: 0,
        y: 0,
        width: image.width,
        height: image.height,
      })
    }
  }

  const pdfBytes = await doc.save()

  const blob = new Blob([pdfBytes], { type: 'application/pdf' });
  var downloadLink = document.createElement('a');
  downloadLink.href = URL.createObjectURL(blob);
  downloadLink.download = `${title}.pdf`; // 设置下载文件的名称 
  downloadLink.click();
}

同样的原理,通过dom转图片,再插入pdf,但是这里有个地方需要注意, 

const pdfBytes = await doc.save()

这个pdfBytes生成的是个流,如果在node环境下,可以直接通过fs模块导出,但是我们现在在浏览器环境下,需要把这个流转换成二进制blob,再通过浏览器导出,

可以每页自适应高度导出,但是好像还是有偏差

原来的dom在浏览器上是这么显示的

但是导出长这样了

页面文字样式偏移了, 查了很多,大家都踩了这个坑,用html2canvas绘制完图片后,始终会有个偏移距离,之前的解决办法是设 scrollY: 0,scrollX: 0 这两个参数为0,但是怎么改都不行,

后来想了一下,又找了个组件库dom-to-image,这个和html2canvas的使用方法差不多,


    let canvas = await domtoimage.toPng(domitem,options)

直接在第一个参数上传dom就可以,然后再结合PDF-LIB来导出,这时候样式不会乱了,每页pdf的高度也都是自适应,但是又出新问题了,导出的分辨率实在太低了,这样用户是真的没法使用,在某些需要大屏展示的场景下,肯定不行,体验感很差,于是开始debig,开始修改dom-to-image这个npm包的源码,在里面加了缩放

 
        function newCanvas (domNode) {
            var canvas = document.createElement('canvas');
            var ctx = canvas.getContext('2d');
            ctx.mozImageSmoothingEnabled = false;
            ctx.webkitImageSmoothingEnabled = false;
            ctx.msImageSmoothingEnabled = false;
            ctx.imageSmoothingEnabled = false;
            var scale = options.scale || 1; // 默认值1
            canvas.width = (options.width * scale) || util.width(domNode);
            canvas.height = (options.height * scale) || util.height(domNode);
            ctx.scale(scale, scale) // 添加了scale参数
            if (options.bgcolor) {
                ctx.fillStyle = options.bgcolor;
                ctx.fillRect(0, 0, canvas.width, canvas.height);
            }

            return canvas;
        }

 这样一来,再结合传入的宽高,完美解决,最后就差一步了,这样的话,只能在我本地有效,同事拉代码后依旧是原来的清晰度,同事那边不会生效,打包后也不会生效,于是直接把依赖中的文件拿出来吧,直接本地引入,这样线上到时候也能用了

 最后放上完整的导出代码

// import  domtoimage  from 'dom-to-image';
import domtoimage from './dom-to-image';
import { PDFDocument  } from 'pdf-lib'
import { sleep } from './secret';
/**
 * 导出PDF
 * @param {导出后的文件名} title 
 * @param {要导出的dom节点:react使用ref} ele 
 */


const topdf = async (imgs, title) => {
  const doc = await PDFDocument.create()
  for (const key in imgs) {
    if (Object.hasOwnProperty.call(imgs, key)) {
      const element = imgs[key];
      const image = await doc.embedPng(element)
      const pdfPage = doc.addPage([image.width, image.height])
      pdfPage.drawImage(image, {
        x: 0,
        y: 0,
        width: image.width,
        height: image.height,
      })
    }
  }

  const pdfBytes = await doc.save()

  const blob = new Blob([pdfBytes], { type: 'application/pdf' });
  var downloadLink = document.createElement('a');
  downloadLink.href = URL.createObjectURL(blob);
  downloadLink.download = `${title}.pdf`; // 设置下载文件的名称 
  downloadLink.click();
}
const exportPDF = async (title) => {
  const pdfDoms = document.querySelectorAll('.pdfDom');
 
  let newArr = []
  for (let index = 0; index < pdfDoms.length; index++) {
    let domitem = pdfDoms[index];
    const options = { 
      scale: 4,  
      height: domitem.scrollHeight , 
      width: domitem.scrollWidth  , 
    };
    let canvas = await domtoimage.toPng(domitem,options)
    newArr.push(canvas)
    sleep(1000)
  }
    topdf(newArr,title)

  
}


export default exportPDF;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值