html2canvas + jspdf 纯前端HTML导出PDF的实现与问题

前言

        这几天接到一个需求,富文本编辑器的内容不仅要展示出来,还要实现展示的内容导出pdf文件。一开始导出pdf的功能是由后端来做的,然后发现对于宽度太大的图片,导出的pdf文件里部分图片内容被遮盖了,但在前端是正常显示的,只因为class样式后端无法解析。然后,后端开发人员就嫌麻烦,让前端来实现导出pdf的功能。。。

html2canvas(V1.4.1)

         html2canvas 用于将 html 元素渲染成图像,可以将整个页面或特定区域以图像形式进行捕获。这对于将复杂的 html 结构转换为 PDF 格式非常有用,因为它可以捕获 html 中的样式、布局和图像等细节。

        官网:https://html2canvas.hertzen.com/

引用

npm install html2canvas@1.4.1 --save
或
yarn add html2canvas@1.4.1
或
<script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>

示例

<template>
  <div id="pdfBody" style="margin: 30px;">
    <input class="test_input" type="text" autocomplete="off" placeholder="输入框111"/>
    <input class="test_btn" id="testBtn" type="button" value="按钮"/>
  </div>
</template>

<script>
  import html2canvas from "@/utils/htmlToPdf/html2canvas.min";

  export default {
    name: "Test",
    mounted() {
      document.getElementById('testBtn').addEventListener('click', () => {
        this.canvasTest();
      });
    },
    methods: {
      createCanvas(dom) {
        html2canvas(dom, {
          useCORS: true, //允许跨域
          scale: 2, //按比例增加分辨率
          dpi: 200, //将分辨率提高到特定的DPI(每英寸点数)
          ignoreElements: (e) => {
            //过滤head、body等无用的标签元素
            return !(e.contains(dom) || dom.contains(e) || e.tagName === 'STYLE' || e.tagName === 'LINK');
          },
          onclone: (doc) => {
            //Scoped CSS无法直接应用,需要手动处理
            let inputDocArr = doc.getElementsByClassName('test_input');
            [].forEach.call(inputDocArr, inputDoc => {
              inputDoc.style.height = '36px';
              inputDoc.style.lineheight = '36px';
              inputDoc.style.width = '200px';
              inputDoc.style.margin = '20px';
              inputDoc.style.borderRadius = '5px';
              inputDoc.style.border = '1px solid #DCDFE6';
            });

            let btnDocArr = doc.getElementsByClassName('test_btn');
            [].forEach.call(btnDocArr, btnDoc => {
              btnDoc.style.color = '#FFFFFF';
              btnDoc.style.backgroundColor = '#1890ff';
              btnDoc.style.border = '1px solid #1890ff';
              btnDoc.style.height = '36px';
              btnDoc.style.width = '80px';
              btnDoc.style.borderRadius = '20px';
            });
          }
        }).then(canvas => {
          dom.appendChild(canvas);
        }).finally(() => {
          console.log('create canvas finish!');
        });
      },
      canvasTest() {
        let pdfBody = document.getElementById('pdfBody');
        this.createCanvas(pdfBody);
      }
    }
  }
</script>

<style scoped>
  .test_input {
    height: 36px;
    line-height: 36px;
    width: 200px;
    margin: 20px;
    border-radius: 5px;
    border: 1px solid #DCDFE6;
  }

  .test_input:focus {
    border: 1px solid #1890ff;
    outline: none;
  }

  .test_btn {
    color: #FFFFFF;
    background-color: #1890ff;
    border: 1px solid #1890ff;
    height: 36px;
    width: 80px;
    border-radius: 20px;
    cursor: pointer;
  }
</style>

 配置项

        如果想隐藏某个元素不让其显示出来,参考以下几种方案:

        1、元素标签添加 data-html2canvas-ignore 属性,示例如下:

        2、配置项中添加 ignoreElements 属性,手动过滤某个元素,示例如上代码;

        3、配置项中添加 onclone 属性,手动处理某个元素的显示与隐藏,即:element.style.display = 'hidden'。

jsPDF(V1.5.3)

         jsPDF 是一个 PDF 生成库,它允许你通过 JavaScript 代码创建和编辑 PDF 文档。

        官网:https://github.com/parallax/jsPDF

引用

npm install jsPDF@1.5.3 --save
或
yarn add jsPDF@1.5.3
或
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js"></script>

 示例

<template>
  <div id="pdfBody" style="margin: 30px;">
    <h3>测试创建pdf文件!</h3>
    <input id="testPdf" type="button" value="pdf测试"/>
  </div>
</template>

<script>
  import jsPDF from "@/utils/htmlToPdf/jspdf.min";

  export default {
    name: "Test",
    mounted() {
      document.getElementById('testPdf').addEventListener('click', () => {
        this.pdfTest();
      });
    },
    methods: {
      pdfTest() {
        let doc = new jsPDF({
          orientation: 'p',
          unit: 'mm',
          format: 'a4'
        });
        doc.text("Hello world!", 10, 10);
        doc.save("测试.pdf");
      }
    }
  }
</script>

设置中文字体

        设置英文内容是正常显示的,假若内容包含中文,会发现pdf文件里的内容显示乱码,如下图所示:

let doc = new jsPDF({
    orientation: 'p',    
    unit: 'mm',
    format: 'a4'
});
doc.text("pdf文件测试!", 10, 10);
doc.save("测试.pdf");

        解决这一问题,可以使用 jsPDF 提供的 setFont(font) 方法,具体操作如下:

        1、网上下载一个支持中文的字体tff文件,或者拷贝一份本地 window/font/ 路径下的字体文件(ps:有的字体不支持);

        2、从GitHub上下载jsPDF源码,然后打开fontconverter目录下的fontconverter.html文件;

        3、选择本地的tff文件,点击“Create”按钮,会生成一个js文件;

        4、一般情况下,生成的js文件直接引入页面使用会报错,需要手动处理生成新的js文件;

        默认生成的js文件内容如下:

(function (jsPDFAPI) {
    var font = "XXXXXX";
    var callAddFont = function () {
    this.addFileToVFS("simfang.ttf", font);
    this.addFont("simfang.ttf", "simfang", "normal");
};
jsPDFAPI.events.push(['addFonts', callAddFont])
 })(jsPDF.API);

        然后复制一份js文件,编辑内容如下格式:

export function addfont(pdf) {
	let font = 'XXXXXX';
	pdf.addFileToVFS('simfang.ttf', font);
	pdf.addFont('simfang.ttf', 'simfang', 'normal');
}

        5、将处理好的新的js文件放入项目中,然后在页面中引用,并设置字体。

<template>
  <div id="pdfBody" style="margin: 30px;">
    <h3>测试创建pdf文件!</h3>
    <input id="testPdf" type="button" value="pdf测试"/>
  </div>
</template>

<script>
  import jsPDF from "@/utils/htmlToPdf/jspdf.min";
  import {addfont} from '@/utils/htmlToPdf/simfang';

  export default {
    name: "Test",
    mounted() {
      document.getElementById('testPdf').addEventListener('click', () => {
        this.pdfTest();
      });
    },
    methods: {
      pdfTest() {
        let doc = new jsPDF({
          orientation: 'p',
          unit: 'mm',
          format: 'a4'
        });
        //设置中文字体
        addfont(doc);
        doc.setFont('simfang');
        doc.text("pdf文件测试撒大大大飒飒热通过如果还有就要让他阿松大尔特瑞特好几年,是否然后他又就很尴尬发射任务给他人用不上,二条天皇已经不是v堵塞惹我实在是肉体和set一年德国必须色让他管很多人把!", 10, 10);
        doc.save("测试.pdf");
      }
    }
  }
</script>

解决jsPDF文本不自动换行问题

        中文乱码问题解决了,然后新的问题又出现了,文本不自动换行。解决方案参考如下:

        1、使用 splitTextToSize() 方法:

let doc = new jsPDF({
    orientation: 'p',
    unit: 'mm',
    format: 'a4'    
});
//设置中文字体
addfont(doc);
doc.setFont('simfang');
let con = "pdf文件测试撒大大大飒飒热通过如果还有就要让他阿松大尔特瑞特好几年,是否然后他又就很尴尬发射任务给他人用不上,二条天皇已经不是v堵塞惹我实在是肉体和set一年德国必须色让他管很多人把!";
let splitCon = doc.splitTextToSize(con, 190);
console.log('splitCon',splitCon)
doc.text(splitCon, 10, 10);
doc.save("测试.pdf");

        2、text() 方法参数添加选项 maxWidth :

let doc = new jsPDF({
    orientation: 'p',
    unit: 'mm',
    format: 'a4'
});
//设置中文字体
addfont(doc);
doc.setFont('simfang');
let con = "pdf文件测试撒大大大飒飒热通过如果还有就要让他阿松大尔特瑞特好几年,是否然后他又就很尴尬发射任务给他人用不上,二条天皇已经不是v堵塞惹我实在是肉体和set一年德国必须色让他管很多人把!";
doc.text(con, 10, 10, {
    maxWidth: 190
});
doc.save("测试.pdf");

jsPDF 库 API

        文档地址:https://artskydj.github.io/jsPDF/docs/index.html

        以下是常用API,仅供参考:

方法说明
var doc = new jsPDF(orientation, unit, format, compress);

创建新文档:

orientation:"l"(横向)、"p"(纵向);

unit:"pt"、"mm"(默认)、"cm"、"m"、"in" or "px";

format:"a3"、"a4(默认)"、"a5"、"letter"、"legal"等

doc.addPage()添加一个空白页
doc.setPage(pageNumber)切换到第几个页面操作
doc.internal.getNumberOfPages()获取总页面数
doc.text(text, x, y, options);

页面中添加文本:

text:文本内容;

x:距离页面左边的距离;

y:距离页面上边的距离;

options:可选参数配置

doc.setFont(fontName, fontStyle)

页面文本设置字体:

fontName:字体名称;

fontStyle:字体风格,如"加粗"

doc.setFontSize(size);设置字体大小
doc.setTextColor(ch1, ch2, ch3, ch4);设置文本颜色,可以是颜色码或rgb值
doc.addImage(imageData,format, x, y, width, height)

在 PDF 文件中添加一个图像:

imageData:图像的数据;

format:图像的类型,如"JPEG";

x、y:分别表示图像左上角的坐标;

width:图像的宽度;

height:图像的高度

doc.save(filename)生成指定文件名的PDF文件

html2canvas + jsPDF

        jsPDF 与 html2canvas 结合使用,可以将 html 元素渲染成图像,然后将图像插入到 jsPDF 创建的 PDF 文档中。

示例

import html2canvas from "@/utils/htmlToPdf/html2canvas.min";
import jsPDF from "@/utils/htmlToPdf/jspdf.min";
import {addfont} from '@/utils/htmlToPdf/simfang';
import {Loading} from "element-ui";

export function downloadPdf(title, dom) {
  let reqLoading = Loading.service({
    fullscreen: true,
    text: '正在生成PDF文件......',
    spinner: 'el-icon-loading',
    background: 'rgba(0,0,0,0.5)'
  });

  html2canvas(dom, {
    useCORS: true, //允许跨域
    scale: 2, //按比例增加分辨率
    dpi: 200, //将分辨率提高到特定的DPI(每英寸点数)
    ignoreElements: (e) => {
      return !(e.contains(dom) || dom.contains(e) || e.tagName === 'STYLE' || e.tagName === 'LINK');
    }
  }).then(canvas => {
    // dom.appendChild(canvas);

    // 新建JsPDF对象
    let PDF = new jsPDF({
      orientation: 'p', //参数: l:横向  p:纵向
      unit: 'mm', //参数:测量单位("pt","mm", "cm", "m", "in" or "px")
      format: 'a4', //A4纸
    });
    let ctx = canvas.getContext('2d');
    let a4w = 190;
    let a4h = 272; //A4大小,210mm x 297mm,四边各保留10mm的边距,显示区域190x277,底部留5mm的页码显示位置,所以高度为272mm。
    let imgHeight = Math.floor(a4h * canvas.width / a4w) - 2; //按A4显示比例换算一页图像的像素高度
    let renderedHeight = 0;
    //计算总页数
    let pageCount = Math.ceil(canvas.height / imgHeight);
    let page = document.createElement("canvas");
    page.width = canvas.width;
    //设置中文字体
    addfont(PDF);
    PDF.setFont('simfang');

    while (renderedHeight < canvas.height) {
      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);
      //canvas转图片数据保留10mm边距
      PDF.addImage(page.toDataURL('image/jpeg', 1), 'JPEG', 10, 10, a4w, Math.min(a4h, a4w * page.height / page.width));
      renderedHeight += imgHeight;
      //计算当前页及文字
      let pageNumberText = '第' + (renderedHeight / imgHeight) + '页/共' + pageCount + '页';
      //设置文字大小
      PDF.setFontSize(4);
      //设置文字颜色
      PDF.setTextColor('#999');
      //在页脚添加页码
      PDF.text(pageNumberText, ((a4w + 8) / 2), (a4h + 16));
      //判断是否分页,如果后面还有内容,添加一个空页
      if (renderedHeight < canvas.height) {
        PDF.addPage();
      }
    }
    PDF.save(title + ".pdf");

  }).finally(() => {
    if (reqLoading) {
      reqLoading.close();
    }
  });
}

常见问题

截取的图片显示空白

1、跨域问题

        html2canvas 默认是不支持跨域图片的,对于跨域的图片默认是无法截取进去的,需要开启跨域配置:

  1. 将图片转成 base64 形式;
  2. 设置配置项 allowTaint: true 或 useCORS: true;
  3. img 标签中添加 crossOrigin="anonymous";
  4. 图片服务器配置 Access-Control-Allow-Origin: *

2、图片未加载完成

        若图片未加载完成,此时截取的也是空白,解决方法就是设置延时一定事件后处理,或图片添加 load 事件,等图片加载完在进行截图。

3、需要截图的dom元素太多

        若出现前面内容都正常截取,后面内容出现空白,大概率就是dom元素太多了。本人就碰到这种情况,即使延时10秒后截取,后面的部分还是空白,没办法,最后只能后端实现导出pdf功能了。。。

截图后部分css效果未正常显示

1、部分css样式 html2canvas 不支持

        html2canvas 无法支持全部的css样式,关于支持哪些css样式或不支持哪些css样式,可以参考文档:https://html2canvas.hertzen.com/features

2、scoped 作用域的css无法应用

        在vue中,为了避免css样式的交叉污染,都会使用 scoped 作用域的css样式,但是该作用域下的css样式无法在 html2canvas 中使用,解决方案可以在 onclone 配置项中过滤元素手动处理样式(详情可参考上述示例)

        其他的问题可以参考博文:https://www.cnblogs.com/padding1015/p/9225517.html

总结

        使用 html2canvas + jsPDF 纯前端导出pdf的方式还是有很多问题的,一般情况下都是后端进行文件的导出,前端配合解决样式问题。

        另外我看还有使用 dom-to-image 或 modern-screenshot 的,不知道效果怎么样,有时间可以试一下。

  • 12
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: HTML2Canvas是一个将HTML DOM元素转换为canvas的JavaScript库,而jspdf是一个用于生成PDF文档的JavaScript库。因此,您可以使用HTML2CanvasHTML DOM元素转换为Canvas,然后使用jspdf将Canvas转换为PDF。 要处理分页图片和被截断的文本问题,您可以使用以下步骤: 1. 将HTML DOM元素转换为Canvas。您可以使用HTML2Canvas库来完成此操作。请确保在转换时包含所有的分页图片和文本。 2. 将所有的Canvas合并在一起,以便生成单个PDF文件。您可以使用canvas.toDataURL()方法将每个Canvas转换为图像数据URL。然后,将所有的图像数据URL合并在一起,并使用jspdf将它们转换为PDF。 3. 处理被截断的文本。如果文本被截断,您可以使用CSS的white-space属性来处理它。例如,您可以设置white-space:pre-wrap属性来打破单词并允许文本换行。 4. 处理分页图片。如果您有分页图片,您可以将它们分成多个Canvas,并将它们一起合并到PDF中。这将确保每个分页图片都在正确的位置显示。 总的来说,使用HTML2Canvasjspdf生成PDF文档是一个十分强大的工具。通过遵循上述步骤,您可以轻松地处理文本被截断和分页图片的问题,并生成高质量的PDF文件。 ### 回答2: html2canvas是一个javascript库,用于将HTML元素渲染为Canvas,并提供了将Canvas导出为图片的功能。而jspdf是另一个javascript库,用于生成PDF文件。 使用html2canvasjspdf可以实现前端生成PDF文件的功能。首先,使用html2canvasHTML页面的内容渲染为Canvas。然后,将Canvas转换为图像数据,并将图像数据添加到jspdfPDF文件中。最后,可以将生成的PDF文件保存或下载。 但是在处理分页时,把整个HTML页面转换为Canvas,可能会导致图片和文字被截断的问题。这是因为Canvas在渲染时有大小限制。 为了解决这个问题,可以通过在特定位置手动分页来控制页面内容的展示。首先,确定每一页需要展示的内容,并在对应的位置手动分页。具体的做法是使用CSS的page-break属性来设置页面的分页位置,以确保每一页不会出现图片和文字被截断的情况。 在使用html2canvas时,可以通过设置ignoreElements属性来忽略某些元素不进行渲染,以避免被截断。例如,对于需要分页的图片,可以在转换为Canvas时,将其设置为忽略的元素,保证图片完整显示在各个页面中。 总而言之,使用html2canvasjspdf可以在前端实现PDF文件的生成,但处理分页时需要注意图片和文字被截断的问题。通过手动设置分页位置和忽略特定元素,可以解决这个问题,确保生成的PDF文件中的内容完整可读。 ### 回答3: html2canvas是一个JavaScript库,可以将HTML元素转换为canvas画布,并且可以配合jspdf库生成PDF文件。 要处理分页图片和文字被截断的问题,可以按照以下步骤进行: 1. 引入html2canvasjspdf库。 2. 创建一个用于生成PDF的按钮或者其他触发事件。 3. 使用html2canvas库将需要转换为PDFHTML元素转换为canvas画布。可以使用html2canvas()方法,并将需要转换的元素传递给该方法。 4. 使用toDataURL()方法将canvas画布转换为一个base64编码的图像字符串。 5. 创建一个jspdf实例。可以使用`new jsPDF()`创建一个新的jspdf实例。 6. 使用jspdf的addImage()方法将之前生成的base64编码的图像字符串添加到页面上。可以通过指定x、y坐标和图像的宽度和高度来设置图像在PDF中的位置和尺寸。 7. 使用jspdf的save()方法保存生成的PDF文件。 对于分页图片和文字被截断的问题html2canvasjspdf并不能自动处理。可以通过以下方法来解决: 1. 对于图片,可以在将HTML元素转换为canvas之前,通过调整元素的尺寸、位置或者裁剪来确保图片完整显示在一个页面中。 2. 对于文字,可以通过调整元素的字体大小、行高或者缩减内容来确保文字完整显示在一个页面中。 3. 如果需要处理多页的情况,可以使用循环或者递归的方式将分页的内容分别转换为canvas,并通过jspdf的addPage()方法在PDF中添加新的页面。 总结来说,通过html2canvasjspdf配合使用,可以在前端生成PDF文件。对于处理分页图片和文字被截断的问题,需要手动调整HTML元素的尺寸、位置和内容,以确保完整显示在PDF中。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值