利用 html2canvas 和 jspdf 导出 echarts ( html页面 )为pdf

前言:这段时间在项目开发中,遇到这样一个需求:前端在一个页面中使用echarts生成了几个图表,需要可以将他们导出为一个pdf。虽然echarts自身有下载,但只能 单个图表下载为图片,所以不满需求。

根据调研,决定采用 html2canvas 和 jspdf 的方式实现,即使用 html2canvas 将页面转成图片后,放到pdf中导出。

一、安装插件与引入

html2canvas:npm install html2canvas --save-dev

                        import html2canvas from 'html2canvas';

jspdf:npm install jspdf --save-dev

           import jsPDF from 'jspdf';

二、核心js ( mainBox 为要导出的盒子id 

html2canvas(document.getElementById('mainBox'),{
   dpi: 200,//导出pdf清晰度
   useCORS: true // 【重要】开启跨域配置
   }).then(function(canvas) {
        // document.body.appendChild(canvas);
        let contentWidth = canvas.width;
        let contentHeight = canvas.height;

        //一页pdf显示html页面生成的canvas高度;
        let pageHeight = contentWidth / 592.28 * 841.89;
        //未生成pdf的html页面高度
        let leftHeight = contentHeight;
        //pdf页面偏移
        let position = 0;
        //html页面生成的canvas在pdf中图片的宽高(a4纸的尺寸[595.28,841.89])
        let imgWidth = 595.28;
        let imgHeight = 592.28 / contentWidth * contentHeight;

        let pageData = canvas.toDataURL('image/jpeg', 1);
        let pdf = new jsPDF('', 'pt', 'a4');

        //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
        //当内容未超过pdf一页显示的范围,无需分页
        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('TestReport.pdf');
   });

三、我踩过的坑

1. html2canvas 截图只能接到当前屏幕的,滚动到屏幕以外的截取不到?

         解决方法:请注意如果内容高度超过了屏幕高度,请确认是否有滚动条,我就是因为项目对滚动条做了特殊处理,不会显示出来,滚动是自己封装实现的,导致了截图不全的问题。

         还有一种情况,我将下载按钮放在要导出的盒子下方,会导致截图不全,但我将按钮放在盒子上方就可以,没找到原因....

html2canvas 和 jspdf 这个方法网上有很多现实文章,可自行搜索参考。特别是 html2canvas 的用法,有坑,使用时请注意


续集:

使用场景:导出页面,页面中有大量图片,文字以及附件的展示(附件内容不管) =》 类似一张试卷

遇到的问题:

        1. 截图后图片不显示 => 原因:在html2canvas里面,是使用脚本去操作的,也就是说使用脚本把html转换成canvas,但是有一个限制,那就是不能使用跨源的图片。而 img 标签本身可以避免跨域问题,所以显示没有问题。

        后期处理跨域即可       

        2. 多行输入框 textarea 截图后内容不换行

利用 html2canvas 和 jspdf 导出 echarts ( html页面 )为pdf_第1张图片

        将 textarea 换成普通div即可,并按 textarea 完善样式

 

        3. 内容被断行

利用 html2canvas 和 jspdf 导出 echarts ( html页面 )为pdf_第2张图片

       这个需要计算一张纸能显示多少内容并且换页即可

处理换页完整逻辑:

// --------------------------- 方法一 --------------------------- 
// 导出内容
handleExport() {
  this.exportLoading = true;

  // 定义A4 纸的宽高
  const A4_WIDTH = 595.28;
  const A4_HEIGHT = 841.89;

  this.$nextTick(() => {
    // 获取到截图dom
    const target = this.$refs.mainBox;

    // 以A4纸的大小去计算出 一页pdf能显示高 => 向上取整 (两种方式计算的结果是一样的,下面的更好记录 页面宽/页面高 = A4宽 / A4 高)
    // let pageHeight = Math.ceil(target.scrollWidth / A4_WIDTH * A4_HEIGHT);
    let pageHeight = Math.ceil(target.scrollWidth / (A4_WIDTH / A4_HEIGHT));


    // 获取分割dom,此处为class类名为question-item的dom
    let lableListID = target.getElementsByClassName('print-item');
    // 进行分割操作,当dom内容已超出a4的高度,则将该dom前插入一个空dom,把他挤下去,分割
    for (let i = 0; i < lableListID.length; i++) {
      /*
        * offsetTop 是当前元素距离弹窗顶部的距离 => 这个偏移量是内容到dialog顶部(包含了dialog header);
        * offsetHeight 是当前元素本身的高度,包含内边距(padding)和边框(border)
        * 当前元素高度+距离顶部的偏移量  /  一页显示的高度  取整 => 得到的是该dom应该放在第几页
        */ 
      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 = '#01195e';  // 加上颜色可以很明显的看出来该元素的高度
  
        // 插入空元素的高度 = 当前元素在第几页(该页及其前面页的总高度) - (该元素的偏移量+本身高度)
        let _H = multiple * pageHeight - (lableListID[i].offsetTop + lableListID[i].offsetHeight);
        // 插入元素的高度 = pdf这一页剩余空间 + 65(dialog header的高度,需要减去) + 50 (保证下一页顶部有一点padding)
        newNode.style.height = _H + 65 + 50 + '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);
        }
      }
    }
    this.pdf();
  });
},



// --------------------------- 方法二 --------------------------- 
// 判断是否需要添加空白div
isSplit (nodes, index, pageHeight) {
  // 计算当前这块dom是否跨越了a4大小,以此分割 => 当前这个dom在前一页(小于一页显示高度pageHeight),但下一个dom在下一页了(大于一页显示高度pageHeight)
  if (nodes[index].offsetTop + nodes[index].offsetHeight < pageHeight && nodes[index + 1] && nodes[index + 1].offsetTop + nodes[index + 1].offsetHeight > pageHeight) {
    return true;
  }
  return false;
},



// --------------------------- 方法三 --------------------------- 
pdf() {
  const JsPDF = jsPDF;
  setTimeout(() => {
    html2canvas(this.$refs.mainBox, {
      dpi: 200, //导出pdf清晰度
      useCORS: true // 【重要】开启跨域配置
    }).then(canvas => {
      // document.body.appendChild(canvas);
      let contentWidth = canvas.width;
      let contentHeight = canvas.height;

      //一页pdf显示html页面生成的canvas高度;
      let pageHeight = contentWidth / 595.28 * 841.89;
      //未生成pdf的html页面高度
      let leftHeight = contentHeight;
      //pdf页面偏移
      let position = 0;
      //html页面生成的canvas在pdf中图片的宽高(a4纸的尺寸[595.28,841.89])
      let imgWidth = 595.28;
      let imgHeight = 595.28 / contentWidth * contentHeight;

      let pageData = canvas.toDataURL('image/jpeg', 1);
      let pdf = new JsPDF('', 'pt', 'a4');

      //有两个高度需要区分,一个是html页面的实际高度,和生成pdf的页面高度(841.89)
      //当内容未超过pdf一页显示的范围,无需分页
      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(`TestReport.pdf`);


      this.exportLoading = true;
      this.dialogVisible = false;
    });
    
  }, 300)
},

官方文档:html2canvas:Getting Started | html2canvas

                  jspdf:jspdf - npm

参考博客:https://www.jianshu.com/p/651c40d565e4

                 

文章仅为本人学习过程的一个记录,仅供参考,如有问题,欢迎指出

对博客文章的参考,若原文章博主介意,请联系删除!请原谅

你可能感兴趣的:(常用插件,html2canvas,jspdf,html转为pdf)