技术博客-5 jsPlumb缩略图的生成
在查找缩略图生成的相关资料时,发现了两个比较有用的插件:html2canvas和canvas2image。顾名思义,前者是将html对象转换为canvas画布,而后者则是将canvas导出为不同格式的image图像。
由此,就得到了实现缩略图生成的方法:先使用html2canvas将选定的div转化为canvas,再使用canvas2image将canvas转化为图片形式保存到后端数据库中。保存成了图片的形式以后,前端的排版就好处理了。不过实际上canvas元素自带了一个toDataURL()方法,可以直接将canvas转换为可以直接保存到数据库中的base64图像,不需要第二个插件就能完成所需功能。
然而,理想很丰满,现实很骨感。在实现中遇到了两个难以解决的bug:
缩略图生成不全
原因:选定div的height过大,html2canvas默认截图当前显示的内容,屏幕之外未被显示的内容则不会被截进canvas中,自然也就不会生成到缩略图中。
解决方法:参考博客1和博客2,先将滚动条移动到顶,然后克隆一份备份的节点,对克隆的节点进行截图。代码如下:
window.pageYoffset = 0;
document.documentElement.scrollTop = 0;
document.body.scrollTop = 0;
//美观考虑,去除jsplumb元素选中效果
$("#" + window.sessionStorage.getItem("active_node")).removeClass("selected");
//暂时去除画布边框
$("#canvas").find("#border").attr("style","height:1000px;border:0px");
//获取节点高度,后面为克隆节点设置高度。
var height = $("#canvas").height()
//克隆节点,默认为false,不复制方法属性,为true是全部复制。
var cloneDom = $("#canvas").clone(true);
//设置克隆节点的css属性,因为之前的层级为0,我们只需要比被克隆的节点层级低即可。
cloneDom.css({
"background-color": "white",
"position": "absolute",
"top": "0px",
"z-index": "-1",
"height": height
});
$("body").append(cloneDom);
cloneDom.attr("id", "canvas1");
这样做有一个小小的副作用:运行时会在页面左上角“闪现”一下备份的画布节点(这个实现方法不允许隐藏这个备用节点),同时边框也会闪一下。
效果图:
之后就要解决jsplumb箭头不显示的问题了。
箭头不显示
箭头不显示的原因,是因为jsPlumb把其他元素都转化成了div,唯独把箭头转化成了不能用html2canvas处理的svg矢量图。为此,需要专门对svg进行处理。这里用到了另一个插件canvg,具体代码如下:
if (typeof html2canvas !== 'undefined') {
//以下是对svg的处理
var nodesToRecover = [];
var nodesToRemove = [];
var svgElem = $("#canvas1").find('svg');//divReport为需要截取成图片的dom的id
svgElem.each(function (index, node) {
var parentNode = node.parentNode;
var svg = node.outerHTML.trim();
var canvas = document.createElement('canvas');
canvg(canvas, svg);
if (node.style.position) {
canvas.style.position += node.style.position;
canvas.style.left += node.style.left;
canvas.style.top += node.style.top;
}
nodesToRecover.push({
parent: parentNode,
child: node
});
parentNode.removeChild(node);
nodesToRemove.push({
parent: parentNode,
child: canvas
});
parentNode.appendChild(canvas);
});
}
最终生成的图片效果如下,可以看到箭头也可以正常地进行显示了:
html2canvas的异步处理问题
到这里,缩略图的生成已经解决了,但还遇到了一个问题:如何将缩略图通过ajax上传。但这里还踩了一个异步函数的大坑:
在原来的程序中,首先使用get_network()
函数获取当前的模型并加工成JSON数据,本来打算在这里加入截图函数canvas_gen()
,然后将得到的dataURL作为JSON的一个属性一并上传,但这里遇到一个问题:html2canvas是异步执行,如果用同步的方式执行canvas_gen()
,get_network()
返回的JSON属性一定是undefined
。上网查找了控制函数异步执行顺序的一些方式,但都没找到很好的解决方式。于是改在canvas_gen()
中html2canvas的回调函数里执行get_network()
,虽然不是很漂亮的解决方式,但至少能够正确地将缩略图作为一个属性,插入到JSON数据中了。代码如下:
var dataURL;
html2canvas(document.querySelector("#canvas1"),{
//Whether to allow cross-origin images to taint the canvas
allowTaint: true,
//Whether to test each image if it taints the canvas before drawing them
taintTest: false,
}
).then(canvas => {
$("#canvas1").remove();
document.body.appendChild(canvas);
$("canvas").attr("id", "cvs");
var cvs = document.getElementById("cvs");
dataURL = cvs.toDataURL();
//Canvas2Image.saveAsImage(canvas, 760, 1000, 'png');
$("#canvas").find("#border").attr("style","height:1000px;border:0.5px solid black");
$("canvas").remove();
var data = get_network();
data["graph"] = dataURL;
//上一版本中的save_network
//如果可能的话,希望使用异步回调更漂亮地解决
save_network_origin(data);
});
这里save_network_origin(data)
函数中有ajax的POST操作,这样至少可以保证这几个异步函数确实是按照期望的顺序执行的。