一次 fabric + customiseControls 制作涂鸦canvas画板实践
制作图片涂鸦画板(微信浏览器)
fabric+customiseControls简介
fabric是一个canvas库,其核心就是将canvasAPI转换为对象模型,让我们更加便捷的使用。
customiseControls则是基于fabric的插件,可为新建的fabric对象设置角标,并为其提供一系列事件:drag,
scale,rotate,remove,或者自定义事件。
fabric:https://github.com/fabricjs/fabric.js
customiseControls:https://github.com/pixolith/fabricjs-customise-controls-extension
效果说明(模糊的图片是故意的)
涂鸦的构成 = 1.城市背景图 + 贴纸图片素材 + 文字图片素材
1点击缩略图切换,切换时2,3不变化。 2,3是可选中后可拖拽,拖拽时不影响其他图片素材,选中之后出现四个角的角标,角标的事件为放大,缩小,旋转,移除。
根据用户涂鸦的结果,生成一张带有其他信息的海报(长按保存到手机相册),比如: 用户地理位置信息,当前参与人数,二维码,昵称,头像等等
开撸
开撸之前还是梳理一下重点和优化点:
A.涂鸦画板的交互(重点)
B.精准生成海报(重点)和海报的清晰度(优化点)
A部分: 用户选择对应背景或者涂鸦素材后有正确相应,设置边界值
1.初始化fabric插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| fabric.Object.prototype.setControlsVisibility({ ml: false, mr: false, mtr: false, mt: false, mb: false });
fabric.Canvas.prototype.customiseControls({ br: { action: "rotate" }, bl: { action: 'scale' }, tl: { action: function action(e, target) { var canvas = target.canvas; canvas.remove(target).renderAll(); } }, tr: { action: 'scale' } });
fabric.Object.prototype.transparentCorners = false;
fabric.Object.prototype.customiseCornerIcons({ settings: { borderColor: '#fff', cornerSize: 20, cornerBackgroundColor: 'transparent', cornerPadding: 5 }, br: { action: "rotate", icon: './imgs/icons/rotate.svg' }, bl: { action: 'scale', icon: './imgs/icons/resize.svg' }, tl: { icon: './imgs/icons/remove.svg', action: function action(e, target) { var canvas = target.canvas; canvas.remove(target).renderAll(); } }, tr: { action: 'scale', icon: './imgs/icons/resize.svg' } }, function () { board.renderAll(); }); var ww = $(window).width(); var wh = $(window).height(); var board = new fabric.Canvas('drawBoard', { backgroundColor: 'transparent', width: (ww * 0.92 height: (wh * 0.67 });
|
2.添加用户点击交互(click -> 新增对应素材至画板 或者切换城市背景)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| var fabricItemList = [];
function setImagebg(imgSrc) { fabric.Image.fromURL(imgSrc, function (img) { img.set({ scaleX: board.width / img.width, scaleY: board.height / img.height }); board.setBackgroundImage(img, board.renderAll.bind(board)); board.requestRenderAll(); }, { crossOrigin: 'anonymous' }); }
function setPicture(obj) { obj.customiseCornerIcons(function () { board.renderAll(); }); fabricItemList.push(obj); board.add(obj); board.setActiveObject(obj); }
function addText(ctext) { var color = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "#fff"; var textObj = new fabric.Text(ctext, { fontFamily: "PingFang SC, Verdana, Helvetica Neue, Microsoft Yahei, Hiragino Sans GB, Microsoft Sans Serif, WenQuanYi Micro Hei, sans-serif", fontSize: 30, top: 150, left: 150, originX: 'center', originY: 'center', fill: color }); setPicture(textObj); }
function featImg(imgSrc, scale) { fabric.Image.fromURL(imgSrc, function (img) { img.set({ scaleX: scale, scaleY: scale, angle: 0, left: Math.random() < 0.5 ? Math.random() * 50 + 110 : -Math.random() * 50 + 110, top: Math.random() < 0.5 ? Math.random() * 50 + 120 : -Math.random() * 50 + 120, hasControls: true, borderColor: '#fff' }); setPicture(img); }, { crossOrigin: 'anonymous' }); }
|
3.生成画板图
1 2 3 4 5 6 7
| let drawImg = board.toDataURL();
fabricItemList.forEach(function (item) { return board.remove(item); }); fabricItemList = [];
|
B部分: 如何根据设计稿精准绘制海报(重点)?
1.使用现有成熟的方案: canvas2Image + html2canvas(高质量前端快照方案:来自页面的「自拍」)
详见网易云音乐团队的文章:https://juejin.im/post/5df2e8ab6fb9a0163770816d#heading-15
2.自己瞎折腾
最终选择:*自己瞎折腾 *
心路历程:
????作者是不是脑子有病(小声BB)…
为什么我要做费力不讨好的事情? 现成的库他不香吗?
原因在于ios手机在授权后的:微信浏览器的白条问题
白条会使得整个可视区的DOM被向上顶一部分距离…
整个网上的方案都在现有资源下都不能好好解决,https://developers.weixin.qq.com/community/develop/doc/0000a09f840910386ee69390251c00
白条问题使得在使用前面的「自拍」方案时,生成的海报会错位…结果惨不忍睹!
html2canvas是根据遍历DOM样式信息来生成的,而且在遍历过程中,它会重新加载所有资源(在network自己看),如果遇到质量较高或者像这样的数量的图片,会出现绘制空白的可能。
自己瞎折腾的结果:
根据canvas的drawimage写了个生成图的js,写的垃圾,大佬勿喷…
https://github.com/tsunamiGG/utils/blob/master/makePoster.js。
大概思路:
canvas图层堆叠是有顺序的,所以按照图层顺序进行传参。
为保证生成资源的完整性,必须先确保onload之后进入合成过程。
以左上角为原点,然后使用性能不错的drawimage(x,y,w,h)离屏渲染生成,从而避开了生成图被白条影响。
b部分:海报的清晰度(优化点)
可以看到上面的效果图清晰度是非常不理想的,需要优化。
为什么我们的canvas绘制的东西看上去总是比用DOM合成的东西糊一些?
真实物理设备的像素和css的像素是有差距的,window.devicePixelRatio的值就反应了其比例。它告诉浏览器应使用多少屏幕实际像素来绘制单个CSS像素。
同一个东西,你使用一个css像素来表示,假设在不同手机上分别浏览器本需要4个物理像素或8个物理像素。而canvas只用一个位图像素来填充4个物理像素或8个物理像素,不糊才怪呢..
如果想深入了解,参考:https://juejin.im/post/5cbdda7bf265da036504fb46
1.js设置放大devicePixelRatio
倍数宽高的canvas,css设置缩小devicePixelRatio
倍数的canvas。扩大实际像素数量,视觉上保持跟原有尺寸一致,在API生成图片后会清晰很多。
这里需要注意的是,使用css缩小视觉画布时,由于fabirc会在html上生成两个canvas画布,分别是outer和inner,你需要把两个画布的css都缩小。
2.关闭canvas抗锯齿
1 2 3 4 5 6 7 8 9 10 11 12
| const canvas = document.createElement("canvas"); const scale = window.devicePixelRatio; canvas.width = width * scale; canvas.height = height * scale; const context = canvas.getContext("2d").scale(scale, scale)
context.mozImageSmoothingEnabled = false; context.webkitImageSmoothingEnabled = false; context.msImageSmoothingEnabled = false; context.imageSmoothingEnabled = false;
|
优化后效果如下,手机保存图实际质量比这个高:
4.总结
对十分陌生的canvas稍微有了一些认识,用canvas做动画的库非常多,但有个致命的缺点就是没办法进行调试…
其中还有很多微信浏览器的很多玄学问题就不展开了,白条问题确实是很头疼的。fabric可以整出很多惊艳的效果,但其乱七八糟的文档和糟糕的示例会带来很多麻烦,影响发挥…