Fabric.js 学习笔记,快速上手

Fabric.js官网:http://fabricjs.com/

Fabric.js自定义构建:http://fabricjs.com/build/

Fabric.js

Fabric.js 是一个强大的、灵活的 HTML5 画布库。

1. Fabric

在 Fabric.js 中,preserveObjectStacking 属性是一个布尔值,用于控制当对象被修改(例如移动、旋转、缩放等)时,它们在堆栈中的位置是否保持不变。

设置 preserveObjectStacking 属性,你可以在创建 fabric.Canvas 实例时指定它:

var canvas = new fabric.Canvas('c', {
   preserveObjectStacking: true
});

或者,你可以在任何时候通过以下方式更改现有 canvas 的 preserveObjectStacking 属性:

canvas.preserveObjectStacking = true;
canvas.renderAll(); // 重新渲染画布以应用更改

常用方法如下:

  • add(obj): 向画布添加一个或多个对象(如矩形、圆形、文本、图像等)。
  • remove(obj): 从画布中移除一个对象。
  • getObjects(): 返回画布上所有对象的数组。
  • setActiveObject(obj): 设置当前活动的(即选中的)对象。
  • getActiveObject(): 获取当前活动的(选中的)对象。
  • clear(): 清除画布上的所有对象,但不销毁它们。
  • renderAll(): 重新渲染画布上的所有对象。
  • toDataURL([options]): 将画布上的内容导出为一个数据 URL。
  • toJSON([options]): 将画布上的对象序列化为 JSON 字符串。
  • loadFromJSON(json, callback): 从 JSON 字符串加载对象到画布。
  • getActiveObjects(): 获取当前选中的一组对象(fabric.ActiveSelection 实例)。
  • deactivateAll(): 取消所有对象的活动状态。
  • discardActiveObject(): 销毁当前活动的(选中的)对象。
  • sendToBack(obj): 将对象发送到画布的最后层。
  • bringToFront(obj): 将对象带到画布的最前层。
  • sendBackwards(obj): 将对象向后发送一层。
  • bringForward(obj): 将对象向前移动一层。
  • getSelectionStyles(): 获取当前选中对象的样式。
  • setSelectionStyles(styles): 设置当前选中对象的样式。
  • setDimensions(width, height): 设置画布的尺寸。
  • getCenter(): 获取画布的中心点坐标。
  • calcOffset(): 计算画布的偏移量。
  • on(type, listener): 为画布绑定事件监听器。
  • off(type, listener): 移除画布上的事件监听器。
  • fire(type, options): 触发画布上的事件。
  • dispose(): 销毁画布实例,释放资源。
  • clone(): 克隆画布实例。
  • toDatalessJSON([options]): 序列化画布对象,但不包括事件监听器和非序列化属性。
  • setBackground(color, callback): 设置画布的背景颜色。
  • getBackgroundColor(): 获取画布的背景颜色。

2. 图形

fabric.image属性列表如下:

  • element: null - 一个 Image 元素或其实例,用作图像的源。
  • src: null - 图像的源 URL。
  • width: 根据图像或 element 确定 - 图像的宽度。
  • height: 根据图像或 element 确定 - 图像的高度。
  • selectable: true - 表示图像是否可以被选择。
  • hasControls: true - 表示是否显示控制点以便进行变换操作。
  • hasRotatingPoint: false - 表示是否显示旋转控制点。
  • lockRotation: false - 阻止图像旋转。
  • lockScalingX: false - 阻止沿 X 轴缩放。
  • lockScalingY: false - 阻止沿 Y 轴缩放。
  • lockMovement: false - 阻止图像移动。
  • scaleX: 1 - 沿 X 轴的缩放因子。
  • scaleY: 1 - 沿 Y 轴的缩放因子。
  • flipX: false - 是否沿 X 轴翻转图像。
  • flipY: false - 是否沿 Y 轴翻转图像。
  • opacity: 1 - 图像的不透明度。
  • angle: 0 - 图像的旋转角度。
  • originX: 'left' - 缩放和旋转的原点在 X 轴上的位置。
  • originY: 'top' - 缩放和旋转的原点在 Y 轴上的位置。
  • crossOrigin: false - 是否设置跨域资源共享。
  • alignX: 根据图像宽度确定 - 水平对齐方式。
  • alignY: 根据图像高度确定 - 垂直对齐方式。
  • meetOrSlice: false - 是否裁剪图像以适应 width 和 height。

常用方法:

  • getSrc(): 返回图像的源 URL。
  • setSrc(src): 设置图像的新源 URL 并重新加载图像。
  • getElement(): 返回图像的 DOM 元素。
  • setImage(imageUrl, [callback]): 设置图像的源,并在加载完成后可选地调用回调函数。
  • crossOrigin: 设置为 true 以启用跨域图像加载。
  • toDataURL([options]): 将图像转换为数据 URL。
  • toDataURLWithMultiplier([multiplier], [callback]): 将图像转换为数据 URL,并允许指定缩放因子。
  • clone(): 创建并返回当前图像对象的深拷贝。
  • toJSON(): 序列化图像对象为 JSON 字符串。
  • setAngle(angle): 设置图像的旋转角度。
  • setOpacity(opacity): 设置图像的不透明度。
  • setFlipX(flipX): 沿 X 轴翻转图像。
  • setFlipY(flipY): 沿 Y 轴翻转图像。
  • scale(scaleX, scaleY): 缩放图像。
  • skew(skewX, skewY): 对图像进行倾斜变换。
  • rotate(angle): 旋转图像。
  • scaleToWidth(width): 根据给定的宽度缩放图像。
  • scaleToHeight(height): 根据给定的高度缩放图像。
  • setWidth(width): 设置图像的宽度。
  • setHeight(height): 设置图像的高度。
  • setPosition({ left, top }): 设置图像的位置。
  • setSelectabe(selectable): 设置图像是否可选中。
  • setHasControls(hasControls): 设置图像是否显示控制点。
  • setOriginX(originX): 设置图像旋转和缩放的 X 轴原点。
  • setOriginY(originY): 设置图像旋转和缩放的 Y 轴原点。
  • setStroke(stroke): 设置图像边框的颜色。
  • setStrokeWidth(strokeWidth): 设置图像边框的宽度。
  • setBackgroundColor(backgroundColor): 设置图像的背景颜色。
  • setFill(fill): 设置图像的填充颜色。
  • setStrokeDashArray(strokeDashArray): 设置图像边框的虚线样式。
  • setTransformMatrix(matrix): 应用一个变换矩阵到图像上。
  • setVisible(visible): 设置图像是否可见。
  • bringToFront(): 将图像移动到画布的最上层。
  • sendToBack(): 将图像移动到画布的最下层。
  • moveUp(): 将图像向上移动一层。
  • moveDown(): 将图像向下移动一层。
  • remove(): 从画布上移除图像。

3. 问题记录

  1. EraserBrush不是 Fabric 默认构建的一部分

4. 涂抹

设置擦除,图形的erasable为true时可擦除:

// 铅笔笔刷
canvas.freeDrawingBrush = new fabric.PencilBrush(canvas);
canvas.freeDrawingBrush.width = 30;
canvas.freeDrawingBrush.color = 'rgb(255, 0, 0, 0.3)';
canvas.isDrawingMode = true;
canvas.freeDrawingCursor = 'none';

const cursor = new fabric.Circle({
  radius: 0,
  fill: 'rgb(255, 0, 0, 0.3)',
  left: 0,
  top: 0,
  erasable: false,
  selectable: false,
  evented: false,
  visible: false,
});

canvas.add(cursor);
canvas.bringToFront(cursor);

/* 设置鼠标样式 */
cursor.set('fill', 'rgb(255, 0, 0, 0.3)');
cursor.set({strokeWidth: 0.1, stroke: 'rgba(255, 0, 0, 0.3)'});
cursor.set('radius', 15);

/* 每次涂抹处理路径控制属性 */
canvas.on('path:created', (e) => {
  cursor.toolbarChecked = val;
  cursor.toolbarCheckedValue = 15;
  e.path.set('selectable', false);
  e.path.set('evented', false);
  canvas.renderAll();
});

5. 擦除

// 铅笔笔刷
canvas.freeDrawingBrush = new fabric.EraserBrush(canvas);
canvas.freeDrawingBrush.width = 30;
canvas.freeDrawingBrush.color = 'rgb(255, 0, 0, 0.3)';
canvas.isDrawingMode = true;
canvas.freeDrawingCursor = 'none';

const cursor = new fabric.Circle({
  radius: 0,
  fill: 'rgb(255, 0, 0, 0.3)',
  left: 0,
  top: 0,
  erasable: false,
  selectable: false,
  evented: false,
  visible: false,
});

canvas.add(cursor);
canvas.bringToFront(cursor);

/* 设置鼠标样式 */
cursor.set('fill', 'rgb(255, 0, 0, 0.3)');
cursor.set({strokeWidth: 0.1, stroke: 'rgba(255, 0, 0, 0.3)'});
cursor.set('radius', 15);

/* 每次涂抹处理路径控制属性 */
canvas.on('path:created', (e) => {
  cursor.toolbarChecked = val;
  cursor.toolbarCheckedValue = 15;
  e.path.set('selectable', false);
  e.path.set('evented', false);
  canvas.renderAll();
});

5. 自定义Control的样式

import { fabric } from 'fabric';
import verticalImg from '@/assets/middlecontrol.svg?url';
import horizontalImg from '@/assets/middlecontrolhoz.svg?url';
import edgeImg from '@/assets/edgecontrol.svg?url';
import rotateImg from '@/assets/rotateicon.svg?url';

/**
 * 实际场景: 在进行某个对象缩放的时候,由于fabricjs默认精度使用的是toFixed(2)。
 * 此处为了缩放的精度更准确一些,因此将NUM_FRACTION_DIGITS默认值改为4,即toFixed(4).
 */
fabric.Object.NUM_FRACTION_DIGITS = 4;

const verticalImgIcon = document.createElement('img');
verticalImgIcon.src = verticalImg;

const horizontalImgIcon = document.createElement('img');
horizontalImgIcon.src = horizontalImg;

const edgeImgIcon = document.createElement('img');
edgeImgIcon.src = edgeImg;

const rotateImgIcon = document.createElement('img');
rotateImgIcon.src = rotateImg;

// 绘制图像函数
function drawImg(ctx, left, top, img, wSize, hSize, angle) {
  if (angle === undefined) return;
  ctx.save();
  ctx.translate(left, top);
  ctx.rotate(fabric.util.degreesToRadians(angle));
  ctx.drawImage(img, -wSize / 2, -hSize / 2, wSize, hSize);
  ctx.restore();
}

// 中间横杠
function intervalControl() {
  function renderIcon(ctx, left, top, styleOverride, fabricObject) {
    drawImg(ctx, left, top, verticalImgIcon, 20, 25, fabricObject.angle);
  }

  function renderIconHoz(ctx, left, top, styleOverride, fabricObject) {
    drawImg(ctx, left, top, horizontalImgIcon, 25, 20, fabricObject.angle);
  }

  // 中间横杠
  fabric.Object.prototype.controls.ml = new fabric.Control({
    x: -0.5,
    y: 0,
    offsetX: -1,
    cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler,
    actionHandler: fabric.controlsUtils.scalingXOrSkewingY,
    getActionName: fabric.controlsUtils.scaleOrSkewActionName,
    render: renderIcon,
  });

  fabric.Object.prototype.controls.mr = new fabric.Control({
    x: 0.5,
    y: 0,
    offsetX: 1,
    cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler,
    actionHandler: fabric.controlsUtils.scalingXOrSkewingY,
    getActionName: fabric.controlsUtils.scaleOrSkewActionName,
    render: renderIcon,
  });

  fabric.Object.prototype.controls.mb = new fabric.Control({
    x: 0,
    y: 0.5,
    offsetY: 1,
    cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler,
    actionHandler: fabric.controlsUtils.scalingYOrSkewingX,
    getActionName: fabric.controlsUtils.scaleOrSkewActionName,
    render: renderIconHoz,
  });

  fabric.Object.prototype.controls.mt = new fabric.Control({
    x: 0,
    y: -0.5,
    offsetY: -1,
    cursorStyleHandler: fabric.controlsUtils.scaleSkewCursorStyleHandler,
    actionHandler: fabric.controlsUtils.scalingYOrSkewingX,
    getActionName: fabric.controlsUtils.scaleOrSkewActionName,
    render: renderIconHoz,
  });
}

// 顶点
function peakControl() {
  function renderIconEdge(ctx, left, top, styleOverride, fabricObject) {
    drawImg(ctx, left, top, edgeImgIcon, 25, 25, fabricObject.angle);
  }

  // 四角图标
  fabric.Object.prototype.controls.tl = new fabric.Control({
    x: -0.5,
    y: -0.5,
    cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
    actionHandler: fabric.controlsUtils.scalingEqually,
    render: renderIconEdge,
  });
  fabric.Object.prototype.controls.bl = new fabric.Control({
    x: -0.5,
    y: 0.5,
    cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
    actionHandler: fabric.controlsUtils.scalingEqually,
    render: renderIconEdge,
  });
  fabric.Object.prototype.controls.tr = new fabric.Control({
    x: 0.5,
    y: -0.5,
    cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
    actionHandler: fabric.controlsUtils.scalingEqually,
    render: renderIconEdge,
  });
  fabric.Object.prototype.controls.br = new fabric.Control({
    x: 0.5,
    y: 0.5,
    cursorStyleHandler: fabric.controlsUtils.scaleCursorStyleHandler,
    actionHandler: fabric.controlsUtils.scalingEqually,
    render: renderIconEdge,
  });
}

// 旋转
function rotationControl() {
  function renderIconRotate(ctx, left, top, styleOverride, fabricObject) {
    drawImg(ctx, left, top, rotateImgIcon, 40, 40, fabricObject.angle);
  }

  // 旋转图标
  fabric.Object.prototype.controls.mtr = new fabric.Control({
    x: 0,
    y: 0.5,
    cursorStyleHandler: fabric.controlsUtils.rotationStyleHandler,
    actionHandler: fabric.controlsUtils.rotationWithSnapping,
    offsetY: 30,
    // withConnecton: false,
    actionName: 'rotate',
    render: renderIconRotate,
  });
}

export default function initControl() {
  // 顶点图标
  peakControl();
  // 中间横杠图标
  intervalControl();
  // 旋转图标
  rotationControl();

  fabric.Object.prototype.set({
    transparentCorners: false,
    borderColor: '#51B9F9',
    cornerColor: '#FFF',
    borderScaleFactor: 2.5,
    cornerStyle: 'circle',
    cornerStrokeColor: '#0E98FC',
    borderOpacityWhenMoving: 1,
  });
}