likes
comments
collection
share

jspdf+html2canvas导出pdf遇到的问题

作者站长头像
站长
· 阅读数 58

原理

需要导出的DOM元素,利用Html2canvas生成图片,然后用jspdf的addImage方法添加到pdf中。 但在这个过程中,我遇到了一些问题,记录下。

问题一. 图片跨域

解决方法1:图片增加crossOrigin属性

<img crossOrigin="anonymous"/>

jspdf+html2canvas导出pdf遇到的问题 这种方法在直接打开index.html文件时图片无法展示。

解决方法2:将图片换成svg,将其写入html文件中。

<div class="no-data">
    <svg width="64" height="41" viewBox="0 0 64 41" xmlns="http://www.w3.org/2000/svg" class="ant-empty-img-simple">
      <g transform="translate(0 1)" fill="none" fill-rule="evenodd">
        <ellipse cx="32" cy="33" rx="32" ry="7" class="ant-empty-img-simple-ellipse"></ellipse>
        <g fill-rule="nonzero" class="ant-empty-img-simple-g">
          <path
            d="M55 12.76L44.854 1.258C44.367.474 43.656 0 42.907 0H21.093c-.749 0-1.46.474-1.947 1.257L9 12.761V22h46v-9.24z">
          </path>
          <path
            d="M41.613 15.931c0-1.605.994-2.93 2.227-2.931H55v18.137C55 33.26 53.68 35 52.05 35h-40.1C10.32 35 9 33.259 9 31.137V13h11.16c1.233 0 2.227 1.323 2.227 2.928v.022c0 1.605 1.005 2.901 2.237 2.901h14.752c1.232 0 2.237-1.308 2.237-2.913v-.007z"
            class="ant-empty-img-simple-path"></path>
        </g>
      </g>
    </svg>
  </div>

这种方法保证图片在哪种方式下都不跨域。但是在html中写入svg让代码的可阅读性大大下降。

解决方法3:将图片用base64加密。

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAjCAYAAAAkCQwqAAAAAXNSR0IArs4c6QAAAxpJREFUaEPtmYeK4zAQQJXeIL2H/P+HhfRNg/R6PHNzmF0lkWJl13ArMCnI45mnaZIjt9vtphyPzWajVquVSiQSTiSfTidVKBRULpdzIs8vJOIawGw2U9vtVmWzWZVOp50ovN/v/8msVCpOZIoQpwBQdDQaqWaz6cx4UfRdsp0BwE0xvlwuv8VVAbFer9VisfAAuwovJwCu16tnPDFKrL5zkFvIMUCIRqOBH+UEAManUilVKpUCK2QiAC84HA4ehKAjMICPjw9Ph2q1GlQXq/tdPTcQAFbieDyqRqNhpbyryePx2MsF5J1Xx8sAXMfiKwbQwgyHw0C55yUAJKH5fO40G78CgHuCVh9rALvdTuF6rVbLS3xhGNIjEIqZTMZKJSsAGE/Gp8t7Vofj8bjK5/NWytybTLhdLpeHsvAEOlAqgw0EKwCQHgwGqlgsPjUMhRhBE+RkMlH0Gclk8ukzl8ularfbVl2oNQDcn4eYDBIUAF4NFWo9AEzrPYvD82z2IG8FQK2mObJRyA8Wj6PUmvYYoQOAS5IrTEJG51HcTyiZ3h86ACg/nU69EDCJYT8EGixCoF6vKxKqyQgdAJQ+n8/e4YhtHgAAG6tYLGZiuzcnlACMtXcw8VsA0Ad0Oh0H6roX0e/3rQ9jrKoA7kxpMy1L7k18LJHFoUM1zRlI+wVgcyj66wH/ewjI1jPMOcD2wPRpDmAjIheNCa0piSaMgwTN6RBNFwemcj3S9S4Atp9cYjxC+B12AOw9pHkSAPy+11BpAeDqsp310xMP6Ha7YXQA1ev1vM2XruvEK3Tl8QsAVpk2VPfKEG+gt6cRikQioYKAvjRCtVpN+74AfYHw2RO+AKDUsfr33pnKS893vwCxpYte8hJVdy8A2Jl+9gKrEEAwYNim4ik2GxVbg2zmiy5sm+95JsbrjvG0ADASd5dEqPMGDivCBEB36AIMSYAkRB0c4zIoMGxW5ifmYiTGiuHPctVTAH4jxDN0nz9prBjNp3w31ccKwD2hAJGL0JFcIaEj/8n/DxsTX3URg+TT2739XWH/f6bG6ub9AYeQfbawholHAAAAAElFTkSuQmCC"/>

这种解决方法兼容性很好。

解决方案4:echarts中的image使用base64方式。

rich: {
    first: {
        width: 20,
        height: 20,
        borderWidth: 0,
        color: 'rgba(0, 0, 0, 0)',
        backgroundColor: {
            image: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAAAjCAYAAAAkCQwqAAAAAXNSR0IArs4c6QAAAxpJREFUaEPtmYeK4zAQQJXeIL2H/P+HhfRNg/R6PHNzmF0lkWJl13ArMCnI45mnaZIjt9vtphyPzWajVquVSiQSTiSfTidVKBRULpdzIs8vJOIawGw2U9vtVmWzWZVOp50ovN/v/8msVCpOZIoQpwBQdDQaqWaz6cx4UfRdsp0BwE0xvlwuv8VVAbFer9VisfAAuwovJwCu16tnPDFKrL5zkFvIMUCIRqOBH+UEAManUilVKpUCK2QiAC84HA4ehKAjMICPjw9Ph2q1GlQXq/tdPTcQAFbieDyqRqNhpbyryePx2MsF5J1Xx8sAXMfiKwbQwgyHw0C55yUAJKH5fO40G78CgHuCVh9rALvdTuF6rVbLS3xhGNIjEIqZTMZKJSsAGE/Gp8t7Vofj8bjK5/NWytybTLhdLpeHsvAEOlAqgw0EKwCQHgwGqlgsPjUMhRhBE+RkMlH0Gclk8ukzl8ularfbVl2oNQDcn4eYDBIUAF4NFWo9AEzrPYvD82z2IG8FQK2mObJRyA8Wj6PUmvYYoQOAS5IrTEJG51HcTyiZ3h86ACg/nU69EDCJYT8EGixCoF6vKxKqyQgdAJQ+n8/e4YhtHgAAG6tYLGZiuzcnlACMtXcw8VsA0Ad0Oh0H6roX0e/3rQ9jrKoA7kxpMy1L7k18LJHFoUM1zRlI+wVgcyj66wH/ewjI1jPMOcD2wPRpDmAjIheNCa0piSaMgwTN6RBNFwemcj3S9S4Atp9cYjxC+B12AOw9pHkSAPy+11BpAeDqsp310xMP6Ha7YXQA1ev1vM2XruvEK3Tl8QsAVpk2VPfKEG+gt6cRikQioYKAvjRCtVpN+74AfYHw2RO+AKDUsfr33pnKS893vwCxpYte8hJVdy8A2Jl+9gKrEEAwYNim4ik2GxVbg2zmiy5sm+95JsbrjvG0ADASd5dEqPMGDivCBEB36AIMSYAkRB0c4zIoMGxW5ifmYiTGiuHPctVTAH4jxDN0nz9prBjNp3w31ccKwD2hAJGL0JFcIaEj/8n/DxsTX3URg+TT2739XWH/f6bG6ub9AYeQfbawholHAAAAAElFTkSuQmCC',
            },
        },
          title: {
            color: '#fff',
            backgroundColor: '#68B92E',
            borderWidth: 0,
            fontSize: 14,
            lineHeight: 20,
            width: 20,
            height: 20,
            align: 'center',
            borderRadius: 4,
        },
    },

在echarts中添加图片时,建议使用base64加密的图片。

问题二:echarts在用canvas渲染时导出时不显示

这个问题的本质和问题一是一样的,最简单的解决办法是用SVGRenderer渲染echarts图形,或者是自己再按照上述的方式对每一个echarts图形进行base64加密也可以的。

问题三:导出的文件挤在一起或者是进行了裁减

这个问题最主要的是在生成pdf的时候需要动态配置pdf是横向还是纵向的,如果要生成pdf的DOM元素的高度大于DOM元素的宽度,那应该配置成横向的,相反应该是纵向的。

if (size[0] > size[1]) {
  pdf = new jsPDF('l', 'pt', size); // 横向
} else {
  pdf = new jsPDF('p', 'pt', size); //纵向
}

问题四:生成的文件模糊

解决这个问题就是放大canvas图片。具体代码见最后一节。

关于分页

如果没有分页和打印需求,建议不要做分页。

如果非要分页,建议后端做,这种方法非常容易出现截断或者空白的问题。

angular中使用jspdf和html2canvas生成pdf文件

代码如下:

import { Injectable } from '@angular/core';
import jsPDF from 'jspdf';
import { Subject } from 'rxjs';
const html2Canvas: any = (window as any).html2canvas;

@Injectable({
  providedIn: 'root',
})
export class JspdfService {
  private _pdfLoadingSource = new Subject<boolean>();
  pdfLoading$ = this._pdfLoadingSource.asObservable();

  constructor() {}

  loadingPdf(mission: boolean) {
    this._pdfLoadingSource.next(mission);
  }

  async pdfHandler(targetDom: any, scale = 2) {
    this.loadingPdf(true);
    setTimeout(async () => {
      // 将克隆节点动态追加到body后面。
      const cloneDom = targetDom.cloneNode(true);
      // 设置克隆节点的css属性,因为之前的层级为0,我们只需要比被克隆的节点层级低即可。
      cloneDom.style.position = 'absolute';
      cloneDom.style.top = '0';
      cloneDom.style.index = '-1';
      cloneDom.style.height = targetDom.height;
      // 将克隆节点动态追加到body后面。
      window.document.getElementById('pdf-con')!.appendChild(cloneDom);
      const canvas = await html2Canvas(cloneDom, {
        // 截取标签转换为canvas
        canvas: this.createCanvas(cloneDom),
        useCORS: true,
        background: '#FFFFFF',
      });
      this.downloadPdf(canvas, scale);
      cloneDom.hidden = true; // 隐藏之前的元素,更好对比
    }, 1000);
  }

  downloadPdf(canvas: any, scale = 2) {
    // 将canvas变成PDF并下载
    const size = [canvas.width / scale, canvas.height / scale]; // pdf真实宽高
    let pdf;
    if (size[0] > size[1]) {
      pdf = new jsPDF('l', 'pt', size); // 横向
    } else {
      pdf = new jsPDF('p', 'pt', size); // 纵向
    }
    const ctx = canvas.getContext('2d'),
      pdfw = size[0],
      pdfh = size[1],
      imgHeight = Math.floor((pdfh * canvas.width) / pdfw); // 按实际界面可视区显示比例换算一页图像的像素高度
    const page = document.createElement('canvas');
    page.width = canvas.width;
    page.height = canvas.height;
    // 用getImageData剪裁指定区域,并画到前面创建的canvas对象中
    page!
      .getContext('2d')!
      .putImageData(
        ctx.getImageData(
          0,
          0,
          canvas.width,
          Math.min(imgHeight, canvas.height)
        ),
        0,
        0
      );
    pdf.addImage(
      page.toDataURL('image/jpeg', 1.0),
      'JPEG',
      0,
      0,
      pdfw,
      Math.min(pdfh, (pdfw * page.height) / page.width)
    ); // 添加图像到页面
    const name = '测试文件名';
    pdf.save(`${name}.pdf`); // 保存PDF
    this.loadingPdf(false);
  }

  createCanvas(target: any, scale = 2) {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    canvas.width = target.offsetWidth * scale; // 画布实际宽度
    canvas.height = target.offsetHeight * scale; // 画布实际高度
    canvas.style.width = target.offsetWidth + 'px'; // 浏览器上显示的宽度
    canvas.style.height = target.offsetHeight + 'px'; // 浏览器上显示的高度
    context!.scale(scale, scale); // 等比缩放
    return canvas;
  }
}

调用方法: this.jspdfService.pdfHandler(document.getElementById('需要导出的dom元素ID'));

转载自:https://juejin.cn/post/7379047318518399014
评论
请登录