5,618

最近一年接了许多关于调研、盘点、评估类的系统项目,但凡涉及到数据的项目,总是要有报告、报表类的文件产出。那么必定会有要把这些数据转换成PDF或者打印成纸质的需求提出。关于把HTML转换成PDF的方法有很多,在这个过程中前端能做的有哪些呢?下面就为大家解惑!

报表、报告、简历等页面,实现HTML转成PDF,并且支持PDF下载,可能还要支持批量下载......

目前在项目中实践过的方法有:

1、样式控制输出html转PDF:CSS媒体查询控制打印

2、生成图片再转PDF:html2canvas + 分页判断

3、借用工具:wkhtmlhopdf(后端)

情况一:浏览器预览打印、下载PDF

浏览器打印预览生成PDF可以说是最简单直接的方法,任一浏览器打开页面,鼠标右击选择打印,都会调出打印预览页面,选择生成pdf保存即可。

以谷歌浏览器为例,操作如下图:

当点击“打印”按钮时,浏览器会唤起打印预览,用户可设置打印或保存为pdf。如果需要代码控制打印,可以在页面里进行如下操作:

<button class="btn">打印</button>
<script>
    $('.btn').click(function() {
          window.print(); 
</script>

当然以上所说的方法,显然简单粗暴了些,必定不能满足业务方的定制化需求......那么就需要借助样式和工具了。

情况二:页面打印样式、内容、分页处理

调用浏览器打印的方法固然方便,但是页面没有经过打印样式处理,整个页面会被完全打印,一些不需要的元素也会被打印。没有边距、缩放比、分页处理的打印内容也会有显示错乱、被截断的问题出现。所以这里需要使用到CSS的媒体查询打印来控制打印的样式。

示例:CSS媒体查询打印

<head> <style type="text/css">/* 打印样式设置 */ @media print { .noprint {display:none; } .print {margin:0; min-width: 1300px; } .pagebreak{ page-break-after:always; } /*分页控制 */ .infos-item{page-break-inside: avoid; } /*内容不分页*/ /*打印外边距设置*/ @page {size: auto; /*设置打印纸张大小,如A4*/ margin: 0; } /*设置打印纸边距*/ </style></head> <body> <div class="container print"> <div class="section pagebreak"> <div class="title">标题</div> <div class="infos-item">内容</div> </div> <div class="section"> <div class="title">标题</div> <div class="infos-item">内容</div> </div> </div> <button class="print-btn noprint ">打印</button> </body> </html>

从上边示例中可以看出,在做PDF导出的页面时,需要注意

1、重构时CSS与HTML要放在同一个页面中,以免生成PDF页面时外部引用样式会不生效。

2、运用媒体查询@media print 编辑打印样式时样式名建议新建,以免影响正常页面布局。

3、可以运用媒体查询控制页面内容是否显示,如 ‘.noprint’,不需要打印的内容可添加该样式名。

4、运用打印分页属性:page-break-before,page-break-after,page-break-inside 控制元素之前、之后或之中是否分页。

(Ps.文字截断、内容截断问题推荐用page-break-inside:avoid来处理,亲测有效!)

灵活运用CSS控制打印,CSS打印知识可参阅:CSS打印相关知识

以京东招聘系统中打印简历为例,观察教育经历每一子项增加了避免内容截断 .infos-item{page-break-inside:avoid} 的前后变化:

图中左侧未加避免截断page-break-inside:avoid时内容被分割开,右侧加了之后,整个子项另起一页显示。

这种方法还很适用于表格类页面,能很好的避免行内文字被截断的问题。不过是否需要做内容分页处理还是要根据自己项目的实际需求,有的业务方就喜欢自然排布的,有的就喜欢模块不截断的,总之要灵活应用。

情况三:将页面截取成图片再转pdf

有些业务方可能会提出将页面截取成图片再转PDF打印、下载等要求,这种情况我们处理的方式是借助canvas,这里我们要用到一个插件html2canvas。

html2canvas通过遍历页面目标对象的DOM结构,收集它所有元素信息及相应样式,渲染出canvas image,生成截图。整个过程都可由前端在客户端浏览器生成,操作方便,如果直接前端处理生成pdf的话会有跨域问题,最好将生成图片传回给后端处理。

示例:html引用

<div class="container print" id="container">需截取内容</div> <button id="print-btn" class="noprint">打印</button> <canvas width="1200" height="400" id="pdfcon"></canvas><!--截取图片存放容器--> <!--JS引入--><script src="../js/lib/html2canvas.min.js"></script> <script type="text/javascript" src="../js/lib/jquery.min.js"></script> <script type="text/javascript" src="../js/lib/sea.js"></script><script type="text/javascript"> seajs.use('../js/test-print.js'); </script> </body>

在示例中,我们需要引入html2canvas.js,并且设置一个canvas作为截取页面图片的容器。

JS代码如下:

var canvas = document.getElementById("pdfcon");
$("#print-btn").click(function() {
       $("#print-btn").hide();
       html2canvas(document.getElementById('container'), {canvas: canvas}).then(function(canvas) {
            var createImg = canvas.toDataURL('image/jpeg', 0.8);
            //createImg请求接口传回后台
            $("#print-btn").show();

点击打印按钮后,打印按钮隐藏,打印所选区域“container”的内容,生成的canvas图片设置了打印比例为0.8,这根据实际情况设置,需要多次调试。

使用这种方法CSS可以不用写在html中,页面直接生成图片,没有分页控制,所以适合固定内容的页面,也可用样式控制页面内容不被截断。

如果需要分页可以用JS计算页面高度和内容高度进行比较,如果内容超出一页即插入分页。代码如下:

function insertEmptys(pageHeight) {   //页面内容需要分页处理的操作
    if($('.section').length) {
         $('.section').each(function(i) {
              var curr = $('.section').eq(i);
              var oTop = curr.offset().top;
              if(oTop%pageHeight + curr.outerHeight() > pageHeight) {
                  curr.before('<div class="empty-block" style="height: '+(pageHeight+100 - oTop%pageHeight)+'px;"></div>');
$("#print-btn").click(function() {
       $("#print-btn").hide();//隐藏打印按钮
       insertEmptys(2545);//内容分页处理
       $('.empty-block').show();//占位符显示
       var pdf = $('#pdfcon');
       pdf.append('<div class="empty-block" style="display:block;height: '+(pageHeight - (pdf.height()%pageHeight))+'px;"></div>');//补齐最后一页占位
       html2canvas(document.getElementById('container'), {canvas: canvas}).then(function(canvas) {
            var createImg = canvas.toDataURL('image/jpeg', 0.8);
          //createImg请求接口传回后台进行操作 或者前端控制展示//.........
            $("#print-btn").show();//显示打印按钮
            $('.empty-block').hide();//占位符隐藏

如例子所示,因为是转成图片,分页必须要在截取屏幕前执行完成,在调用html2canvas之前,我们对页面中需要分页的‘section’进行处理,当它的高大于一页高度我们进行分页,页面插入空白占位,如函数insertEmptys()中的处理方法。处理完分页,用html2canvas进行页面截取,将截取的canvas image 转成base64格式的图片,请求后台接口传参,将图片传回后台处理。

这里用一个打印简历的例子演示下html2canvas的使用效果,如下图:

在图中左侧初始页面中我们有一个打印按钮,为了演示方便页面下方放了一个不可见的canvas画布,用来存放截取图片。

当点击打印时,截取页面,生成右侧效果。右侧canvas画布中出现页面图片,并且截取的图片没有打印按钮,相对完整。

这里只展示了HTML生成图片的过程,拿到图片后唤起打印即可执行打印功能。当然也可将图片传回后台进行操作,为了演示方便这里就直接在页面输出了。

注意:将HTML转成图片再生成PDF的方法不适用文档中需要存在跳转链接的需求,在选择这种方法的时候要考虑到这个问题。

无论前端侧对页面进行怎样的重构操作,输出的文件是HTML还是一张图片,建议最后转成PDF的工作还是交给后台去处理。一是因为他们有专门的工具可以批量处理;二是这样可避免一些不必要的问题出现,如跨域问题、页面分页问题等。

在后台部分,研发们要实现HTML转PDF功能的工具有很多,最常用的工具是wkhtmltopdf,它是一个“命令行工具”,能如chrome打印预览的功能一样,实现页面边距设置、页眉页脚设置、分页设置,还可设置背景图片是否打印等功能。

使用wkhtmltopdf,需要先下载安装,下载地址:wkhtmltopdf.org/downloads.h…

下载安装完毕后,在系统环境变量变量名为”Path”的后面添加安装的路径,如:D:\wkhtmltopdf\bin 。配置好环境变量后可以用cmd测试转换pdf的效果如下操作:

直接在cmd里输入:wkhtmltopdf www.baidu.com/ D:website1.pdf

如图所示,wkhtmltopdf是要运行的软件名,后边空一格跟着的是要转变成pdf的HTML页面地址,这里用的是百度网址。在网页地址后面输入要生成的路径及名称。回车之后可在D盘目录下看到所生成的PDF。

在php项目中执行转换的命令也是非常简单,方法如下:

//将网页内容转换为 PDF exec("wkhtmltopdf http://www.google.com google.pdf"); //将本地 HTML 文件转为 PDF exec("wkhtmltopdf my.html my.pdf");

除了调用执行命令外,当然还要配置转成PDF需要的参数,以及字体包的安装,参数详见: HTML 转 PDF 之 wkhtmltopdf 工具精讲。因为没有具体使用过wkhtmltopdf,这里就不多做赘述,感兴趣的同学可以上网查询相关资料。

前端侧处理HTML转PDF的基本实现方法,目前所实践的,大致就如以上所说。当然网上还有许多第三方插件可以实现如jsPDF、iText等,但不论引用了怎样的工具,都是需要前端样式的配合以及后台的支持。如果还有其他方法,欢迎留言补充,希望此篇文章对你在HTML转PDF上有所帮助。