本文介绍在纯前端环境下,利用html2canvas和jsPDF将HTML页面导出为PDF的完整方案。重点解决SVG图片渲染、Glyphicons字体图标转换及页面样式保留问题,通过Canvas中转生成高清PDF,提供可直接运行的JavaScript代码示例。
HTML转PDF需求背景与方案筛选
在Web项目开发过程中,常遇到需要将整个网页页面导出为PDF格式的需求,且必须完整保留页面上的表格、SVG矢量图以及所有CSS样式。
核心诉求类似于执行一次全屏截图,并将截取的页面内容保存为PDF文件。
此类需求往往较为复杂。
目前实现HTML转PDF的技术方案主要有以下几种:
第一种是利用浏览器自带的打印或导出功能。但这无法满足系统内部主动触发导出PDF的功能要求,因此该方案不可行。
第二种方案是使用第三方工具,例如wkhtmltopdf。经测试,该工具在项目中表现不佳,且对SVG图片的支持存在缺陷,故排除。
第三种方案是通过iText等后端库生成Java文件。由于目标页面为动态页面,直接传输至后端会导致大量CSS样式丢失,因此该方案也不适用。
鉴于上述方案均不理想,最终确定采用“HTML转图片,再由图片生成PDF”的折中策略。为满足用户导出下载且保留样式的需求,必须采用纯前端JavaScript实现。
HTML转Canvas环节,选用html2canvas库,该库应用广泛,此处不再赘述。
核心实现流程:SVG与字体图标处理
难点在于SVG图片的处理,html2canvas无法直接渲染SVG标签。经调研,选用canvg库(源自Google)将SVG内容转换为Canvas。此外,还需解决glyphicons等字体图标在不同浏览器下渲染不一致的问题,最终方案是通过字符编码(char code)替换字体图标,并在Canvas中重新绘制。生成图片后,利用jsPDF库生成最终PDF文件。经过大半天的调试,整个流程已打通,后续将分步展示代码。
第一步:遍历目标DOM节点,将所有SVG元素替换为Canvas元素。
SVG元素转Canvas实现
svg2canvas: function(targetElem) {
var svgElem = targetElem.find('svg');
svgElem.each(function(index, node) {
var parentNode = node.parentNode;
//由于现在的IE不支持直接对svg标签node取内容,所以需要在当前标签外面套一层div,通过外层div的innerHTML属性来获取
var tempNode = document.createElement('div');
tempNode.appendChild(node);
var svg = tempNode.innerHTML;
var canvas = document.createElement('canvas');
//转换
canvg(canvas, svg);
parentNode.appendChild(canvas);
});
}
第二步:将Glyphicons字体图标转换为Canvas。若项目中未使用Glyphicons字体,可跳过此步骤。
Glyphicons字体图标转换逻辑
glyphicons2canvas: function(targetElem, fontClassName, fontFamilyName) {
var iconElems = targetElem.find('.' + fontClassName);
iconElems.each(function(index, inconNode) {
var fontSize = $(inconNode).css("font-size");
var iconColor = $(inconNode).css("color");
var styleContent = $(inconNode).attr('style');
//去掉"px"
fontSize = fontSize.replace("px", "");
var charCode = getCharCodeByGlyphiconsName(iconName);
var myCanvas = document.createElement('canvas');
//把canva宽高各增加2是为了显示图标完整
myCanvas.width = parseInt(fontSize) + 2;
myCanvas.height = parseInt(fontSize) + 2;
myCanvas.style = styleContent;
var ctx = myCanvas.getContext('2d');
//设置绘图内容的颜色
ctx.fillStyle = iconColor;
//设置绘图的字体大小以及font-family的名字
ctx.font = fontSize + 'px ' + fontFamilyName;
ctx.fillText(String.fromCharCode(charCode), 1, parseInt(fontSize) + 1);
$(inconNode).replaceWith(myCanvas);
});
}
//根据glyphicons/glyphicon图标的类名获取到对应的char code
getCharCodeByGlyphiconsName: function(iconName) {
switch (iconName) {
case("glyphicons-resize-full"):
return "0xE216";
case ("glyphicons-chevron-left"):
return "0xE225";
default:
return "";
}
}
第三步:执行HTML转Canvas、转图片,最后生成PDF的完整流程。
HTML至PDF的最终生成代码
html2canvas($("#myExportArea"), {
onrendered: function(canvas) {
var imgData = canvas.toDataURL('image/jpeg');
var img = new Image();
img.src = imgData;
//根据图片的尺寸设置pdf的规格,要在图片加载成功时执行,之所以要*0.225是因为比例问题
img.onload = function() {
//此处需要注意,pdf横置和竖置两个属性,需要根据宽高的比例来调整,不然会出现显示不完全的问题
if (this.width > this.height) {
var doc = new jsPDF('l', 'mm', [this.width * 0.225, this.height * 0.225]);
} else {
var doc = new jsPDF('p', 'mm', [this.width * 0.225, this.height * 0.225]);
}
doc.addImage(imgData, 'jpeg', 0, 0, this.width * 0.225, this.height * 0.225);
//根据下载保存成不同的文件名
doc.save('report_pdf_' + new Date().getTime() + '.pdf');
}
},
background: "#fff",
//这里给生成的图片默认背景,不然的话,如果你的html根节点没设置背景的话,会用黑色填充。
allowTaint: true //避免一些不识别的图片干扰,默认为false,遇到不识别的图片干扰则会停止处理html2canvas
});
尽管最终勉强满足了客户需求,但生成的PDF清晰度略低于原生截图。受限于当前技术水平,暂仅能采用此方法,如有更优方案,欢迎交流。
以上纯JS实现HTML转PDF的实例分享完毕,希望能为开发者提供参考,感谢支持。