jspdf+html2canvas实现网页转pdf

3,110 阅读2分钟

项目需求

将生成的分析报告(网页展示主要是echart的各种图表)部分下载为pdf格式的文件。

第一次实现

  1. npm安装jspdf和html2canvas

npm install jspdf html2canvas

  1. 引入jspdf和html2canvas
import jsPDF from 'jspdf';
import html2canvas from 'html2canvas';
  1. jsx
<div className="chartList" ref={pdfWrap => this.refToPDF1 = pdfWrap}>
  <div className="chartWrap">
    <h2>洗发水品类线上销售趋势</h2>
    <p>销售额增长反应消费者在该品类花费增长趋势,假定销量增长固定的情况下,销售额增长越快表明消费者愿意花费更多,消费升级趋势明显</p>
    <Echarts {...this.barChartOne} /> // 此处数据为图标option
  </div>
</div>
  1. 点击下载按钮调用下载pdf文件的方法
@autobind
downloadPdf() {
    const pdf = new jsPDF({ format: 'a4' });
    const width = pdf.internal.pageSize.getWidth();
    
    const promistList = [
      html2canvas(this.refToPDF1)
    ];
    Promise.all(promistList).then(canvases => {
      canvases.map(canvas => {
        pdf.addImage(canvas.toDataURL(), 'JPEG', 0, 0, width, canvas.height * width / canvas.width, null, 'NONE');
        // pdf.addPage();
      });
      return true;
    }).then(() => {
      pdf.internal.scaleFactor = 1.33;
      pdf.save('output.pdf');
    });
}

优化实现方式

若需要生成pdf的页码太多(在50页之上),以上的处理方法就显得很笨拙。几经思考,决定采用以下方案。

  1. 定义需要生成pdf的dom容器,容器内容为动态更新的组件
<div className="reportList" ref={pdfWrap => this.refToPDF1 = pdfWrap}>
  {this.chartComp}
</div>
  1. 点击下载按钮的时候,循环更新容器内容,每次更新后截图
@observable chartComp = null;
...

downloadPdf() {
  const pdf = new jsPDF({ orientation: 'l', unit: 'px', format: [576, 1152] });

  showDownLoadTip('正在生成分析报告...');

  this.addAllChartImgToPdf(pdf).then(() => {
    pdf.internal.scaleFactor = 1.33;
    pdf.save('output.pdf');
    hideDownLoadTip();
  });
}

@autobind
async addAllChartImgToPdf(pdf) {
    const { store: { reportList } } = this.props;
    const width = pdf.internal.pageSize.getWidth();

    for (let i = 0, len = reportList.reportData && reportList.reportData.length; i < len; i++) {
      const item = reportList.reportData[i];
      const key = item.key.split('$')[0];
      const ChartComp = chartTypeCompMap[key];

      if (ChartComp) {
        // 更新要截图的容器内容
        this.chartComp = <div key={i} className="chartList">
          <ChartComp dataItem={item} />
        </div>;

        // 等待echart重新渲染图表,此处的400ms有待商榷,目前只是本地测试这个值可以截到完整的echart图。
        await new Promise(resolve => {
          setTimeout(() => {
            resolve();
          }, 400);
        });

        await html2canvas(this.refToPDF1, {
          scale: 2,
          useCORS: true,
          width: 1152,
          letterRendering: true,
        }).then((canvas) => {
          pdf.addImage(canvas.toDataURL(), 'JPEG', 0, 0, width, canvas.height * width / canvas.width, null, 'NONE');

          showDownLoadTip(`正在生成分析报告${i + 1}/${len}...`);

          if (i < len - 1) pdf.addPage({ orientation: 'l', unit: 'px', format: [576, 1152] });
        });
      }
    }
  }

问题记录

分页的处理:若内容较多需要分页相对比较麻烦。该项目中因为涉及到图表的完整性手动分页。说白了,就是手动将内容分开为几个dom片段,然后每个dom生成一张图,每添加完一张图再手动追加一个空白页面。

若有这样的需求:若下载内容不需要预览也不想在当前页面展示而是想直接下载,此时的下载内容不能放置到display:none;隐藏域,也不能设置不透明度为0(这两种实践证明html2canvas生成不了正确的图),而是采用fixed定位到-9999px的位置。

下载为pdf的过程中发现了一个问题:原html中的字号font-size样式没有生效。解决办法:样式表里加上font-variant: normal;

另:还有一个问题发现有时候下载到的pdf文件中某些元素的背景色会丢失。解决办法:样式表里加上font-feature-settings: normal;

以上,如有问题希望得到反馈~😊