一、画布对象
:::tips
const canvas = new fabric.Canvas(‘canvas’)
:::
初始化画布时,操作与canvas相同,创建画布并重新设置画布大小
fabric创建画布对象时,可以进行初始化设置,对画布进行多种功能设置
const canvas = new fabric.Canvas('canvas', {
selection: Boolean, //
fireRightClick: Boolean, // 启用右键,button的数字为3
stopContextMenu: Boolean, // 禁止默认右键菜单
moveCursor: String, // 在画布上移动对象时使用的默认光标值
...
})
在 fabric.js 中我们总共会创建两个画布,一个是上层画布(upper-canvas),一个是下层画布(lower-canvas),两个画布是一样大的,还有一个外层 div 将这两个 canvas 包起来。
Fabric.js 总共会创建两个画布
- upper-canvas (上层画布)
lower-canvas (下层画布)
两个画布大小、形状没有任何的区别,还有一个外层div将两个canvas包裹起来。upper-canvas主要用于交互事件的处理,如鼠标相关事件、左键框选等等。lower-canvas 主要用于物体的绘制。
当 upper-canvas 处理交互后,获取到物体、对象等属性改变,lower-canvas 会被清空并重新绘制所有内容,两个画布各司其职,上层画布单向指挥下层画布绘制,下层画布只管绘制物体,是典型的数据驱动视图。
fabric.js其实还创建了 cacheCanvasEI(缓冲画布层), 或叫做离屏canvas, 主要用于提供一个临时绘制环境,实现画布预渲染等对动画的性能提升,本文不涉及。
总结来说,fabric.js的三层画布大小等属性相同,但功能不同,故此实现画布操作与画布绘制解耦,逻辑清晰。class Canvas {
/** 画布宽度 */
public width: number;
/** 画布高度 */
public height: number;
/** 包围 canvas 的外层 div 容器 */
public wrapperEl: HTMLElement;
/** 下层 canvas 画布,主要用于绘制所有物体 */
public lowerCanvasEl: HTMLCanvasElement;
/** 上层 canvas,主要用于监听鼠标事件、涂鸦模式、左键点击拖蓝框选区域 */
public upperCanvasEl: HTMLCanvasElement;
/** 缓冲层画布 */
public cacheCanvasEl: HTMLCanvasElement;
/** 画布中所有添加的物体 */
private _objects: FabricObject[];
....
}
二、控制器
fabric.js 提供了默认的控制器对物体进行基础变化操作,下图为默认样式
图1
在物体被选中时,物体会显示出边框以及控制点,实现对物体进行控制。换而言之,控制器是fabric.js实现与物体进行变换交互的工具。
如图1所示,控制器主要包括边框和控制点两个组成部分。
其中,边框的含义很好理解,就是对物体的包围框,用以显示当前物体的边界。图1中的边框完美适应矩形形状的物体,且完美贴合大小,但是若将物体形状更换为圆形,三角形等,是否还能完美适配呢?
因此、fabric提供了多种类型的边框,常见的包括AABB、OBB、以及球模型等。其中的AABB全称为:‘Axis-aligned bounding box’,即 边平行于坐标轴的盒,取物体所有顶点 (离散点) 坐标的最大最小值形成边框。
AABB型边框模型相对来说,更容易理解控制器与图形关系,操作效率更高。尤其是在模型碰撞问题中,该类型的边框可以很方便,以及相对精确的替代图形进行碰撞计算,相对来说是一种 ‘性价比’ 很高的模型。
但是根据AABB型模型的特点,我们也可以发现,若出现顶点的横纵坐标差值越大,该模型的操作度、精确度便会大幅下降,下图展示对 ‘ 线端 ‘ 使用AABB型模型
可以发现,实际具有控制效果的仅是左下,右上两个控制点,整个控制器略显冗余。
OBB(Oriented Bounding Box)包围盒在此情景下,便会精确很多
三、自定义控制器
控制器作为fabric基础的图形操作工具,本身也可以进行大小、颜色、形状等自定义调整,我们根据自身画布内容、风格等,可以调整控制器属性,达到风格统一的目的
1. 基础属性调整
fabric.Object.prototype.set({
borderColor: 'black', // 边框颜色
cornerColor: 'white', // 控制点颜色
cornerSize: 10, // 控制点尺寸
cornerStyle: 'circle', // 控制点形状
cornerStrokeColor: 'gray', // 控制点边框颜色
padding: 3, // 间距
transparentCorners: false, // 启用旋转点
lockScalingFlip: true,
globalCompositeOperation: true
})
fabric.Object.prototype // 基类给定名称
// 控制点修改 所有类
fabric.Object.prototype.setControlsVisibility({
bl: true, // 左下
br: true, // 右下
mb: false, // 下中
ml: false, // 中左
mr: false, // 中右
mt: false, // 上中
tl: true, // 上左
tr: true, // 上右
mtr: false // 旋转控制键
})
// 单个图形(多边形)对象修改控制器
diamond.setControls({
bl: true, // 左下
...
})
利用Object类提供的方法和属性,我们可以对整个画布内的或单个图形对象的控制器进行修改,进行美化。
控制器的修改也存在特殊情况,Text类是继承于Object类的文本类,用于创建文字类对象
Text
Text的子类IText相较其父类,增加了文本输入功能
IText IText输入时
Textbox是IText的子类,相较于父类,Textbox固定了高度比例,文字不再伴随控制器放大,x方向控制器会增大单行文字数, y方向可以控制对象盒宽度,增加可容纳行数
由于Textbox对控制器的原生方法进行调整,Object基类的修改将无法作用于Textbox对象,需要利用 setControls 单独对其进行设置
2.自定义控制点贴图和功能
仔细观察上面自定义过的控制器,会发现左上角控制点略有不同,被替换成了一个叉号,同时这也显示出它的功能也变为类似取消、删除等。
replaceControl () {
//
const deleteURL = require('delete.svg')
const callbackTl = (image, isError) => {
if (!isError) {
// 自定义属性
const tl = new fabric.Control({
x: -0.5,
y: -0.5,
offsetY: 0,
actionName: 'delete',
cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
mouseUpHandler: () => this.deleteObj(),
// 渲染图标
render: this.renderIcon(image._element, 0),
// 设置控制点大小
cornerSize: 20
})
fabric.Object.prototype.controls.tl = tl
fabric.Textbox.prototype.controls.tl = tl
}
}
fabric.Image.fromURL(deleteURL, callbackTl) // 创建图标示例
},
fabric.Control声明的是一个控制点对象,其包含以下属性:
- x、y:表示控制点距离对象中心点的位置,坐标轴横轴左负右正,纵轴上负下正。
- cursorStyle:光标移动到控制点上时的样式。可选的值有w/e/s/n-resize、pointer等,即东西南北方向的箭头、手的图标等。
- actionHandler:用户拖动该控制点时的动作处理器,这里使用默认的scalingX,即水平方向缩放处理器。以此类推,可选的控制器还有scalingY、scalingEqually(同时缩放X和Y)、rotationWithSnapping(旋转)等。
- render:渲染方法,调用现成的方法即可。
- cornerSize:控制点大小,可以控制贴图的大小。
- 更多可选值见 源码 第7421行
实现自定义贴图和功能主要分为渲染图标和替换执行事件两个方面
- 渲染图标
render是控制器的渲染函数
renderIcon(image, initialAngle) {
return function(ctx, left, top, styleOverride, fabricObject) {
let size = this.cornerSize // 控制器大小设置
ctx.save()
ctx.translate(left, top) // 控制点位置调整 (左上)
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle + initialAngle)) // 调整角度
ctx.drawImage(image, -size / 2, -size / 2, size, size) // 绘制
ctx.restore()
}
},
最后利用 fabric.Image.fromURL(deleteURL, callbackTl) 绘制图标, 成功显示
- 替换执行事件
替换执行事件,主要通过控制鼠标点击事件实现的 :::tips mouseUpHandler: () => this.deleteObj() ::: 上述代码表示,该控制点被点击时会进行监听,当鼠标弹起时执行设置的动作处理方法。用我们自定义的方法如上述的deleteObj()方法,进行自定义控制点点击功能