一次 fabric + customiseControls 制作涂鸦canvas画板实践

制作图片涂鸦画板(微信浏览器)

  1. 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.png

涂鸦的构成 = 1.城市背景图 + 贴纸图片素材 + 文字图片素材

1点击缩略图切换,切换时2,3不变化。 2,3是可选中后可拖拽,拖拽时不影响其他图片素材,选中之后出现四个角的角标,角标的事件为放大,缩小,旋转,移除。

  • 生成环节

2.png

根据用户涂鸦的结果,生成一张带有其他信息的海报(长按保存到手机相册),比如: 用户地理位置信息,当前参与人数,二维码,昵称,头像等等

  1. 开撸

开撸之前还是梳理一下重点和优化点:

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();   

// 生成后复原fabric对象
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;

优化后效果如下,手机保存图实际质量比这个高:

3.png

4.总结

对十分陌生的canvas稍微有了一些认识,用canvas做动画的库非常多,但有个致命的缺点就是没办法进行调试…

其中还有很多微信浏览器的很多玄学问题就不展开了,白条问题确实是很头疼的。fabric可以整出很多惊艳的效果,但其乱七八糟的文档和糟糕的示例会带来很多麻烦,影响发挥…

评论