最近一年接了许多关于调研、盘点、评估类的系统项目,但凡涉及到数据的项目,总是要有报告、报表类的文件产出。那么必定会有要把这些数据转换成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上有所帮助。