前端JS生成PDF的一次踩坑之行

3945 107 技术 2019-02-18

前言

最近公司的项目提出一个新需求,需要在前端生成并输出一份pdf合同。由于合同的语言是中文,所以找到最好的方法还是通过html2canvas.js转换成canvas,然后canvas输出成pdf。但还是踩了许多坑,所以总结一下此次踩的坑以及过程。


首先需要引入html2canvas.js,这个插件的作用是将页面或者DOM元素绘制到canvas画布上

还需要引入jspdf.js,作用是将绘制好的canvas转换成pdf,并输出。

1. 合同样式

首先在html上复现出合同的样式(附上参考片段)

<p class="title_1"><span class="item">1</span>授权合作内容</p>
<p class="title_2"><span class="item">1.1</span>授权作品: 授权方享有的以音乐内容为主的录音录像制品及相关资料,资料包括但不限于歌手信息、肖像、图片、文字介绍等。除《授权歌曲清单》所列出的作品外,协议期内授权方新增音乐作品时,应主动提交更新作品清单,该清单亦属于协议,与本协议具有相同效力。</p>
<p class="title_2"><span class="item">1.2</span>授权权利:授权作品之 <strong>词曲著作权、录音录像制作者权、表演者权、信息网络传播权、邻接权、肖像权</strong>等,允许星晖使用、传播、复制、改编、表演、转授、宣传推广等的权利。</p>
<p class="title_2"><span class="item">1.3</span>授权领域:包括但不限于电脑、手机、平板电脑等智能端互联网,电信运营商网络、智能硬件厂商等。</p>

2. 生成canvas

使用html2canvas()函数将指定的DOM生成canvas并转成base64格式,这里注意!,默认情况下生成的canvas清晰度是很差的,需要进行放大倍数处理,这样最终生成出来的pdf清晰度才高。 (附上函数代码)

// name参数为传入的一个需要复制的DOM的id,
function addPdfPage(name) {
    var page = new Promise(function(resolve, reject) {
        var copyDom = $("#" + name + "");
        var width = copyDom.offsetWidth;
        var height = copyDom.offsetHeight;
        var scale = 2; //放大倍数
        $('body').append('<canvas class="canvas_pdf ' + name + '"></canvas>');
        var canvas = $('.' + name)[0];
        canvas.width = width * scale;
        canvas.height = height * scale;
        var content = canvas.getContext("2d");
        content.scale(scale, scale);
        var rect = copyDom.get(0).getBoundingClientRect(); //获取元素相对于视察的偏移量
        content.translate(-rect.left, -rect.top); //设置context位置,值为相对于视窗的偏移量负值,让图片复位
        html2canvas(copyDom[0], {
            dpi: window.devicePixelRatio * 2,
            scale: scale,
            canvas: canvas,
            width: width,
            heigth: height,
        }).then(function(canvas) {
            var contentWidth = canvas.width;
            var contentHeight = canvas.height;
            var imgWidth = 595.28;
            var imgHeight = 592.28 / contentWidth * contentHeight;
            pageData.push({ // 将当前页面的数据存入pageData数组
                img: canvas.toDataURL('image/jpeg', 1.0),
                width: imgWidth,
                height: imgHeight
            })
            resolve();
        });
    });
    return page;
}

3. 输出并保存PDF到本地

遍历pageData数组并调用pdf.addImage()方法将页面图片逐个添加到pdf对象中。最后调用pdf.save()方法输出最终生成的pdf。

var pdf = new jsPDF('', 'pt', 'a4'), pageData = [];
addPdfPage('page1').then(function() {
    for (i in pageData) {
        pdf.addImage(pageData[i].img, 'JPEG', 0, 0, pageData[i].width, pageData[i].height);
    }
    pdf.save('合同.pdf'); // 输出最终生成的pdf
});

最终效果

最终的效果可以参考下面的效果图,需要代码或者哪里不懂的可以私信我。涉及项目隐私的部分已经做了打码处理,见谅~

13653457-5cdcd73c425b9b4c

给各位即将踩坑的同猿们几点提醒:

  • 如果页面上有表格的话,应该将表格的边框属性重置为0,例如<table border="0" cellspacing="0" cellpadding="0"></table>,然后在css上写表格样式。这样输出的表格才完美。
  • 由于html转成canvas再转成图片,遇到分页的时候会直接被截断,关于这个问题我研究出两种方法,大致参考一下就不附上代码了。
  1. 将内容分成一个个的DOM写死,这种适合不需要填充内容的需求。
  2. 先遍历内容里的每一个DOM,存放在临时的DOM里,计算高度超出页面后存放在新的临时DOM里。最后再遍历临时DOM进行输出。这种方法虽然麻烦但是能实现我的需求。
  • 最终输出是图片型pdf,而不是文字型pdf,需要高清晰度导致最终文件很大,大约1页1M左右, 所以如果是生成后传到后台的同猿们放弃吧,不仅慢而且很麻烦。不如直接后台生成文字型pdf来的快和方便。(重点是不用计算分页的情况哈哈)

最后感谢观看~ 我是@一只有趣的程序猿 我叫大友~

© 2020 peal.cc 粤ICP备2020133024号