我正在参加「掘金·启航计划」
背景故事是这样的,老板要求我做一个so much高端的画布,要求除了简单的添加删除修改元素之外,还要求可以自定义文本,并可以动态调整文本字体大小,动态修改元素的背景颜色等等 起初是用拖拽组件的方式去实现,这是相关的demo链接woai3c.github.io/visual-drag… 原理是每次缩放和移动元素,都是重新获得元素的位置进行更新,画布元素都是以对象的形式存放,通过自定义的组件渲染元素,组件可以是视频,文本,图片等等,开放性还是很强的,但随着业务的扩展,维护成本相当的高,原因是很多功能都需要自己的扩展和维护。
接着就是无尽的冲浪,寻寻觅觅,发现了一个国外的好东西----fabric.js 。 Fabric 是一个强大而简单的 JS Canvas 库,我们能通过使用它实现在 Canvas 上创建、填充图形、给图形填充渐变颜色。 组合图形(包括组合图形、图形文字、图片等)等一系列功能。简单来说我们可以通过使用 Fabric 从而以较为简单的方式实现较为复杂的 Canvas 功能。而且它本身集成了生成图片以及导入图片的功能,总体来讲功能还是很强大的,唯一的缺点就是文档是纯英文,非常晦涩。在下通过几周锲而不舍的努力,最终还是没把文档啃完。
本文主要是介绍使用fabric.js 解决某些特殊场景的方案
引入fabric以及初始化画布
和原生canvas相似,首先都要有一个挂载的容器
<canvas
id="canvas"
style="border: 1px solid #ccc"
></canvas>
import { fabric } from 'fabric';
this.canvas = new fabric.Canvas(
'canvas',
{
width: 790, // 画布宽度
height: 445, // 画布高度
backgroundColor: '#eee', // 画布背景色
}
);
场景列表
1. 控制点及其样式
控制点的开放性还是很强大的,不仅可以控制某些点的显示和隐藏还可以做到圆环形的点状,默认的控制点是有9个
通过一些控制点显隐以及一些配置
fabric.Object.prototype.setControlsVisibility(
{
mr: false,
mt: false,
mb: false,
ml: false,
mtr: false,
}
);
const rect = new fabric.Rect({
top: 30, // 距离容器顶部 30px
left: 30, // 距离容器左侧 30px
width: 100, // 宽 100px
height: 60, // 高 60px
fill: 'red',
enableRetinaScaling: false,
lockScalingFlip: true, // 控制缩放翻转
transparentCorners: false,
borderColor: '#24D7FB', // 选中时,边框颜色
stroke: 'transparent',
strokeWidth: 1,
borderScaleFactor: 1, // 选中时,边的粗细:
// borderDashArray: [5, 5, 5, 5], // 选中时,虚线边的规则
cornerColor: '#24D7FB', // 选中时,角的颜色是
cornerStrokeColor: '#fff', // 选中时,角的边框的颜色是
cornerStyle: 'circle', // 选中时,叫的属性。默认rect 矩形;circle 圆形
cornerSize: 14, // 选中时,角的大小
cornerDashArray: [
325, 325, 325, 325,
], // 选中时,虚线角的规则
// padding: 2, // 选中时,选择框离元素的内边距:4px
borderOpacityWhenMoving: 1, // 当对象活动和移动时,对象控制边界的不透明度
});
this.canvas.add(rect);
可见其开放性很强,可以支持高强度的自定义
2. 限制最大以及最小缩放
fabric本身可通过配置 minScaleLimit 设置最小缩放,最大缩放则需要在缩放过程中去锁定,然后再解锁则可完美解决
this.canvas.on(
'object:scaling',
(ev) => {
if (
ev.target.scaleX >= 2.5
) {
ev.target.lockScalingX = true;
ev.target.scale(2.5);
}
}
);
rect.on('mouseover', (e) => {
if (e.target.lockScalingX) {
e.target.lockScalingX = false;
}
});
3. 画布元素不超出画布范围
要使画布元素不超出画布范围,则需要在监听元素移动的事件上做出一些计算,以左边为例 不超出的情况下,(元素的left+元素本身的宽度)<0时锁定移动并修改元素的位置,最后刷新画布。
this.canvas.on(
'object:moving',
(ev) => {
const obj = ev.target;
const objRect =
obj.getBoundingRect();
// 边界控制
if (
objRect.left +
objRect.width <
0
) {
obj.borderColor = 'red';
ev.target.lockMovementX = true;
ev.target.left = 0;
ev.target.lockMovementY = true;
}
this.canvas.renderAll();
}
);
4. 元素层级问题以及自定义层级
画布上堆积多个元素那都是家常便饭,所以控制元素的层级问题也是很重要的 fabric自身提供了bringToFront() 移到顶层 sendToBack() 移到底层bringForward() 往上一层 sendBackwards() 往下一层,而且还允许我们自定义层级,通过 rect.moveTo() 实现
5. 如何绘制圆形图片
绘制圆形图片苦恼了我几个小时,试过填充模板,裁剪图片的方式,但结果都不太理想,最后采用了group的方式,这里需要注意的是内圆的半径以及group的宽高要一致,图片要使用园进行裁剪半径保持一致
fabric.Image.fromURL(
'./logo.png',
(img) => {
const rect =
new fabric.Circle({
left: 0,
top: 0,
fill: 'red',
originX: 'center',
originY: 'center',
radius: 50,
hasControls: true,
});
const img1 = img
.scaleToHeight(50 * 2)
.set({
originX: 'center',
originY: 'center',
clipPath:
new fabric.Circle({
radius:
img.height / 2,
originX: 'center',
originY: 'center',
}),
});
const group =
new fabric.Group(
[rect, img1],
{
width: 50 * 2,
height: 50 * 2,
left: 150,
top: 400,
...this.options,
}
);
this.canvas.add(group);
}
);