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. 问题记录
- 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,
});
}