一、画布对象

:::tips const canvas = new fabric.Canvas(‘canvas’) ::: 初始化画布时,操作与canvas相同,创建画布并重新设置画布大小
fabric创建画布对象时,可以进行初始化设置,对画布进行多种功能设置

  1. const canvas = new fabric.Canvas('canvas', {
  2. selection: Boolean, //
  3. fireRightClick: Boolean, // 启用右键,button的数字为3
  4. stopContextMenu: Boolean, // 禁止默认右键菜单
  5. moveCursor: String, // 在画布上移动对象时使用的默认光标值
  6. ...
  7. })

在 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的三层画布大小等属性相同,但功能不同,故此实现画布操作与画布绘制解耦,逻辑清晰。

    1. class Canvas {
    2. /** 画布宽度 */
    3. public width: number;
    4. /** 画布高度 */
    5. public height: number;
    6. /** 包围 canvas 的外层 div 容器 */
    7. public wrapperEl: HTMLElement;
    8. /** 下层 canvas 画布,主要用于绘制所有物体 */
    9. public lowerCanvasEl: HTMLCanvasElement;
    10. /** 上层 canvas,主要用于监听鼠标事件、涂鸦模式、左键点击拖蓝框选区域 */
    11. public upperCanvasEl: HTMLCanvasElement;
    12. /** 缓冲层画布 */
    13. public cacheCanvasEl: HTMLCanvasElement;
    14. /** 画布中所有添加的物体 */
    15. private _objects: FabricObject[];
    16. ....
    17. }

二、控制器

fabric.js 提供了默认的控制器对物体进行基础变化操作,下图为默认样式
image.png
图1
在物体被选中时,物体会显示出边框以及控制点,实现对物体进行控制。换而言之,控制器是fabric.js实现与物体进行变换交互的工具。
如图1所示,控制器主要包括边框控制点两个组成部分。
其中,边框的含义很好理解,就是对物体的包围框,用以显示当前物体的边界。图1中的边框完美适应矩形形状的物体,且完美贴合大小,但是若将物体形状更换为圆形,三角形等,是否还能完美适配呢?
因此、fabric提供了多种类型的边框,常见的包括AABB、OBB、以及球模型等。其中的AABB全称为:‘Axis-aligned bounding box’,即 边平行于坐标轴的盒,取物体所有顶点 (离散点) 坐标的最大最小值形成边框。
AABB型边框模型相对来说,更容易理解控制器与图形关系,操作效率更高。尤其是在模型碰撞问题中,该类型的边框可以很方便,以及相对精确替代图形进行碰撞计算,相对来说是一种 ‘性价比’ 很高的模型。
但是根据AABB型模型的特点,我们也可以发现,若出现顶点的横纵坐标差值越大,该模型的操作度、精确度便会大幅下降,下图展示对 ‘ 线端 ‘ 使用AABB型模型
image.png
可以发现,实际具有控制效果的仅是左下,右上两个控制点,整个控制器略显冗余。
OBB(Oriented Bounding Box)包围盒在此情景下,便会精确很多

三、自定义控制器

控制器作为fabric基础的图形操作工具,本身也可以进行大小、颜色、形状等自定义调整,我们根据自身画布内容、风格等,可以调整控制器属性,达到风格统一的目的

1. 基础属性调整

  1. fabric.Object.prototype.set({
  2. borderColor: 'black', // 边框颜色
  3. cornerColor: 'white', // 控制点颜色
  4. cornerSize: 10, // 控制点尺寸
  5. cornerStyle: 'circle', // 控制点形状
  6. cornerStrokeColor: 'gray', // 控制点边框颜色
  7. padding: 3, // 间距
  8. transparentCorners: false, // 启用旋转点
  9. lockScalingFlip: true,
  10. globalCompositeOperation: true
  11. })
  1. fabric.Object.prototype // 基类给定名称
  2. // 控制点修改 所有类
  3. fabric.Object.prototype.setControlsVisibility({
  4. bl: true, // 左下
  5. br: true, // 右下
  6. mb: false, // 下中
  7. ml: false, // 中左
  8. mr: false, // 中右
  9. mt: false, // 上中
  10. tl: true, // 上左
  11. tr: true, // 上右
  12. mtr: false // 旋转控制键
  13. })
  14. // 单个图形(多边形)对象修改控制器
  15. diamond.setControls({
  16. bl: true, // 左下
  17. ...
  18. })

image.png
利用Object类提供的方法和属性,我们可以对整个画布内的或单个图形对象的控制器进行修改,进行美化。
控制器的修改也存在特殊情况,Text类是继承于Object类的文本类,用于创建文字类对象
image.png
Text
Text的子类IText相较其父类,增加了文本输入功能
image.png image.png
IText IText输入时
Textbox是IText的子类,相较于父类,Textbox固定了高度比例,文字不再伴随控制器放大,x方向控制器会增大单行文字数, y方向可以控制对象盒宽度,增加可容纳行数
image.png
由于Textbox对控制器的原生方法进行调整,Object基类的修改将无法作用于Textbox对象,需要利用 setControls 单独对其进行设置

2.自定义控制点贴图和功能

仔细观察上面自定义过的控制器,会发现左上角控制点略有不同,被替换成了一个叉号,同时这也显示出它的功能也变为类似取消、删除等。

  1. replaceControl () {
  2. //
  3. const deleteURL = require('delete.svg')
  4. const callbackTl = (image, isError) => {
  5. if (!isError) {
  6. // 自定义属性
  7. const tl = new fabric.Control({
  8. x: -0.5,
  9. y: -0.5,
  10. offsetY: 0,
  11. actionName: 'delete',
  12. cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
  13. mouseUpHandler: () => this.deleteObj(),
  14. // 渲染图标
  15. render: this.renderIcon(image._element, 0),
  16. // 设置控制点大小
  17. cornerSize: 20
  18. })
  19. fabric.Object.prototype.controls.tl = tl
  20. fabric.Textbox.prototype.controls.tl = tl
  21. }
  22. }
  23. fabric.Image.fromURL(deleteURL, callbackTl) // 创建图标示例
  24. },

fabric.Control声明的是一个控制点对象,其包含以下属性:

  • x、y:表示控制点距离对象中心点的位置,坐标轴横轴左负右正,纵轴上负下正。
  • cursorStyle:光标移动到控制点上时的样式。可选的值有w/e/s/n-resize、pointer等,即东西南北方向的箭头、手的图标等。
  • actionHandler:用户拖动该控制点时的动作处理器,这里使用默认的scalingX,即水平方向缩放处理器。以此类推,可选的控制器还有scalingY、scalingEqually(同时缩放X和Y)、rotationWithSnapping(旋转)等。
  • render:渲染方法,调用现成的方法即可。
  • cornerSize:控制点大小,可以控制贴图的大小。
  • 更多可选值见 源码 第7421行

实现自定义贴图和功能主要分为渲染图标和替换执行事件两个方面

  1. 渲染图标

render是控制器的渲染函数

  1. renderIcon(image, initialAngle) {
  2. return function(ctx, left, top, styleOverride, fabricObject) {
  3. let size = this.cornerSize // 控制器大小设置
  4. ctx.save()
  5. ctx.translate(left, top) // 控制点位置调整 (左上)
  6. ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle + initialAngle)) // 调整角度
  7. ctx.drawImage(image, -size / 2, -size / 2, size, size) // 绘制
  8. ctx.restore()
  9. }
  10. },

最后利用 fabric.Image.fromURL(deleteURL, callbackTl) 绘制图标, 成功显示

  1. 替换执行事件

替换执行事件,主要通过控制鼠标点击事件实现的 :::tips mouseUpHandler: () => this.deleteObj() ::: 上述代码表示,该控制点被点击时会进行监听,当鼠标弹起时执行设置的动作处理方法。用我们自定义的方法如上述的deleteObj()方法,进行自定义控制点点击功能