使用jspdf生成遇到的问题

本文记录了一种使用jsPDF和html2canvas将包含Echarts的长页面转换为多页PDF的解决方案。通过计算DOM位置,动态插入空白div以确保内容完整,避免内容被切割。在生成PDF前,首先隐藏不需要打印的元素,然后截图并按A4纸尺寸分割内容,最后保存为PDF文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

jsPDF 是一个基于 HTML5 的客户端解决方案,用于生成各种用途的 PDF 文档。

官网地址:https://rawgit.com/MrRio/jsPDF/master/docs/

1、安装:npm install jspdf

2、引入:import jsPDF from “jspdf”
今天遇到一个业务,将echarts页面转换成pdf当做报告使用,因为页面较长,pdf要好几页,简单的分割又会切割内容,记录一下解决方法。
导航条是因为原页面导航条的定位出了页面的位置,所以点击生成pdf时,生成一个临时的标题条当做报告的标题。

//引入:
import html2Canvas from 'html2canvas'
import JsPDF from 'jspdf'
...

//切割
 isSplit (nodes, index, pageHeight) {
        console.log(nodes, pageHeight)
        // 计算当前这块dom是否跨越了a4大小,以此分割
        if (nodes[index].offsetTop + nodes[index].offsetHeight < pageHeight && nodes[index + 1] && nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight) {
          //console.log(index)
          return true
        }
        return false
      },
      //参数: id名,不打印的class,要循环打印的class,复制的导航条的第一个兄弟节点的class,导航条标题
      printPagePdf (idName, noUseClassName, contentClassName, insertClassName, title) {
        window.vm.$loading.show()     //  加载中 
        let noPrintList = document.getElementsByClassName(noUseClassName) //需要隐藏的元素
        for (let i = 0; i < noPrintList.length; i++) {
          noPrintList[i].style.display = 'none'
          // noPrintList[i].style.opacity = 0
          noPrintList[i].oldheight = noPrintList[i].style.height
          noPrintList[i].style.height = 0
        }
        let ST = document.documentElement.scrollTop || document.body.scrollTop
        let SL = document.documentElement.scrollLeft || document.body.scrollLeft
        document.documentElement.scrollTop = 0
        document.documentElement.scrollLeft = 0
        document.body.scrollTop = 0
        document.body.scrollLeft = 0
        //获取滚动条的位置并赋值为0,因为是el-dialog弹框,并且内容较多出现了纵向的滚动条,截图出来的效果只能截取到视图窗口显示的部分,超出窗口部分则无法生成。所以先将滚动条置顶
        const A4_WIDTH = 592.28
        const A4_HEIGHT = 841.89
        let imageWrapper = document.querySelector("#" + idName) //  获取 DOM
        let pageHeight = imageWrapper.scrollWidth / A4_WIDTH * A4_HEIGHT
        let lableListID = imageWrapper.querySelectorAll("." + contentClassName) // 每个分割块
        // 生成导航条开始
        let navNode = document.createElement('div')
        navNode.className = 'navDiv'
        navNode.style.background = '#3e4458'
        navNode.style.height = 50 + 'px'
        navNode.style.width = '100%'
        navNode.style.color = '#fff'
        navNode.style.textAlign = 'center'
        navNode.style.lineHeight = 50 + 'px'
        navNode.innerText = title
        //导航条插入父元素第一个
        let insertNode = imageWrapper.getElementsByClassName(insertClassName)[0]
        imageWrapper.insertBefore(navNode, insertNode)
        // 生成导航条结束
        // 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
        for (let i = 0; i < lableListID.length; i++) {
          let multiple = Math.ceil((lableListID[i].offsetTop + lableListID[i].offsetHeight) / pageHeight)
          if (this.isSplit(lableListID, i, multiple * pageHeight)) {
            let divParent = lableListID[i].parentNode // 获取该div的父节点
            let newNode = document.createElement('div')
            newNode.className = 'emptyDiv'
            newNode.style.background = '#f2f4f8'
            let _H = multiple * pageHeight - (lableListID[i].offsetTop + lableListID[i].offsetHeight)
            //留白
            newNode.style.height = _H + 20 + 'px'
            newNode.style.width = '100%'
            let next = lableListID[i].nextSibling // 获取div的下一个兄弟节点
            // 判断兄弟节点是否存在
            if (next) {
              // 存在则将新节点插入到div的下一个兄弟节点之前,即div之后
              divParent.insertBefore(newNode, next)
            } else {
              // 不存在则直接添加到最后,appendChild默认添加到divParent的最后
              divParent.appendChild(newNode)
            }
          }
        }
        //接下来开始截图
        window.vm.$nextTick(() => {
          // nexttick可以保证要截图的部分全部执行完毕,具体用法自行百度...
          html2Canvas(imageWrapper, {
            allowTaint: true,
            // x: imageWrapper.getBoundingClientRect().left + 13,     // 绘制的dom元素相对于视口的位置
            // y: imageWrapper.getBoundingClientRect().top,
            // width: imageWrapper.offsetWidth - 15,                   // 因为多出的需要剪裁掉,
            // height: imageWrapper.offsetHeight,
            backgroundColor: "#f2f4f8", //一定要设置背景颜色,否则有的浏览器就会变花~,比如Edge
            useCORS: true,
            scale: 3,      // 图片模糊
            dpi: 350,
          }).then((canvas) => {
            let contentWidth = canvas.width
            let contentHeight = canvas.height
            let pageHeight = contentWidth / 592.28 * 841.89
            let leftHeight = contentHeight
            let position = 0
            let imgWidth = 595.28
            let imgHeight = 592.28 / contentWidth * contentHeight
            let pageData = canvas.toDataURL('image/jpeg', 1.0)
            let PDF = new JsPDF('', 'pt', 'a4')
            if (leftHeight < pageHeight) {
              PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight)
            } else {
              while (leftHeight > 0) {
                PDF.addImage(pageData, 'JPEG', 0, position, imgWidth, imgHeight)
                leftHeight -= pageHeight
                position -= 841.89
                if (leftHeight > 0) {
                  PDF.addPage()
                }
              }
            }
            PDF.save(title + '.pdf')
            /* 
            let pdf = new JsPDF('', 'mm', 'a4')  //A4纸,纵向
            let ctx = canvas.getContext('2d'),
              a4w = 190, a4h = 277,    //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277
              imgHeight = Math.floor(a4h * canvas.width / a4w),    //按A4显示比例换算一页图像的像素高度
              renderedHeight = 0;
            while (renderedHeight < canvas.height) {
              let page = document.createElement("canvas")
              page.width = canvas.width
              page.height = Math.min(imgHeight, canvas.height - renderedHeight)//可能内容不足一页
              //用getImageData剪裁指定区域,并画到前面创建的canvas对象中
              page.getContext('2d').putImageData(ctx.getImageData(0, renderedHeight, canvas.width, Math.min(imgHeight, canvas.height - renderedHeight)), 0, 0)
              pdf.addImage(page.toDataURL('image/jpeg', 1.0), 'JPEG', 10, 10, a4w, Math.min(a4h, a4w * page.height / page.width))    //添加图像到页面,保留10mm边距
              renderedHeight += imgHeight
              if (renderedHeight < canvas.height)
                pdf.addPage()//如果后面还有内容,添加一个空页
            }
            pdf.save('测试.pdf')
             */
             // 隐藏元素复原
            let noPrintList = document.getElementsByClassName(noUseClassName)
            for (let i = 0; i < noPrintList.length; i++) {
              // noPrintList[i].style.opacity = 1
              noPrintList[i].style.display = 'block'
              noPrintList[i].style.height = noPrintList[i].oldheight
            }
            let emptyDivList = document.getElementsByClassName('emptyDiv')
            for (let i = 0; i < emptyDivList.length; i++) {
              emptyDivList[i].style.height = 0
            }
            // 加载中隐藏
            window.vm.$loading.hide()
            //刷新页面
            location.reload()
          })
        })
      },

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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值