Fabric.js 是一個(gè)強(qiáng)大的、靈活的 HTML5 畫布庫。
1. Fabric
在 Fabric.js 中,preserveObjectStacking 屬性是一個(gè)布爾值,用于控制當(dāng)對(duì)象被修改(例如移動(dòng)、旋轉(zhuǎn)、縮放等)時(shí),它們?cè)诙褩V械奈恢檬欠癖3植蛔儭?/p>
設(shè)置 preserveObjectStacking 屬性,你可以在創(chuàng)建 fabric.Canvas 實(shí)例時(shí)指定它:
var canvas = new fabric.Canvas('c', {
preserveObjectStacking: true
});
或者,你可以在任何時(shí)候通過以下方式更改現(xiàn)有 canvas 的 preserveObjectStacking 屬性:
canvas.preserveObjectStacking = true;
canvas.renderAll(); // 重新渲染畫布以應(yīng)用更改
常用方法如下:
- add(obj): 向畫布添加一個(gè)或多個(gè)對(duì)象(如矩形、圓形、文本、圖像等)。
- remove(obj): 從畫布中移除一個(gè)對(duì)象。
- getObjects(): 返回畫布上所有對(duì)象的數(shù)組。
- setActiveObject(obj): 設(shè)置當(dāng)前活動(dòng)的(即選中的)對(duì)象。
- getActiveObject(): 獲取當(dāng)前活動(dòng)的(選中的)對(duì)象。
- clear(): 清除畫布上的所有對(duì)象,但不銷毀它們。
- renderAll(): 重新渲染畫布上的所有對(duì)象。
- toDataURL([options]): 將畫布上的內(nèi)容導(dǎo)出為一個(gè)數(shù)據(jù) URL。
- toJSON([options]): 將畫布上的對(duì)象序列化為 JSON 字符串。
- loadFromJSON(json, callback): 從 JSON 字符串加載對(duì)象到畫布。
- getActiveObjects(): 獲取當(dāng)前選中的一組對(duì)象(fabric.ActiveSelection 實(shí)例)。
- deactivateAll(): 取消所有對(duì)象的活動(dòng)狀態(tài)。
- discardActiveObject(): 銷毀當(dāng)前活動(dòng)的(選中的)對(duì)象。
- sendToBack(obj): 將對(duì)象發(fā)送到畫布的最后層。
- bringToFront(obj): 將對(duì)象帶到畫布的最前層。
- sendBackwards(obj): 將對(duì)象向后發(fā)送一層。
- bringForward(obj): 將對(duì)象向前移動(dòng)一層。
- getSelectionStyles(): 獲取當(dāng)前選中對(duì)象的樣式。
- setSelectionStyles(styles): 設(shè)置當(dāng)前選中對(duì)象的樣式。
- setDimensions(width, height): 設(shè)置畫布的尺寸。
- getCenter(): 獲取畫布的中心點(diǎn)坐標(biāo)。
- calcOffset(): 計(jì)算畫布的偏移量。
- on(type, listener): 為畫布綁定事件監(jiān)聽器。
- off(type, listener): 移除畫布上的事件監(jiān)聽器。
- fire(type, options): 觸發(fā)畫布上的事件。
- dispose(): 銷毀畫布實(shí)例,釋放資源。
- clone(): 克隆畫布實(shí)例。
- toDatalessJSON([options]): 序列化畫布對(duì)象,但不包括事件監(jiān)聽器和非序列化屬性。
- setBackground(color, callback): 設(shè)置畫布的背景顏色。
- getBackgroundColor(): 獲取畫布的背景顏色。
2. 圖形
fabric.image屬性列表如下:
- element: null - 一個(gè) Image 元素或其實(shí)例,用作圖像的源。
- src: null - 圖像的源 URL。
- width: 根據(jù)圖像或 element 確定 - 圖像的寬度。
- height: 根據(jù)圖像或 element 確定 - 圖像的高度。
- selectable: true - 表示圖像是否可以被選擇。
- hasControls: true - 表示是否顯示控制點(diǎn)以便進(jìn)行變換操作。
- hasRotatingPoint: false - 表示是否顯示旋轉(zhuǎn)控制點(diǎn)。
- lockRotation: false - 阻止圖像旋轉(zhuǎn)。
- lockScalingX: false - 阻止沿 X 軸縮放。
- lockScalingY: false - 阻止沿 Y 軸縮放。
- lockMovement: false - 阻止圖像移動(dòng)。
- scaleX: 1 - 沿 X 軸的縮放因子。
- scaleY: 1 - 沿 Y 軸的縮放因子。
- flipX: false - 是否沿 X 軸翻轉(zhuǎn)圖像。
- flipY: false - 是否沿 Y 軸翻轉(zhuǎn)圖像。
- opacity: 1 - 圖像的不透明度。
- angle: 0 - 圖像的旋轉(zhuǎn)角度。
- originX: 'left' - 縮放和旋轉(zhuǎn)的原點(diǎn)在 X 軸上的位置。
- originY: 'top' - 縮放和旋轉(zhuǎn)的原點(diǎn)在 Y 軸上的位置。
- crossOrigin: false - 是否設(shè)置跨域資源共享。
- alignX: 根據(jù)圖像寬度確定 - 水平對(duì)齊方式。
- alignY: 根據(jù)圖像高度確定 - 垂直對(duì)齊方式。
- meetOrSlice: false - 是否裁剪圖像以適應(yīng) width 和 height。
常用方法:
- getSrc(): 返回圖像的源 URL。
- setSrc(src): 設(shè)置圖像的新源 URL 并重新加載圖像。
- getElement(): 返回圖像的 DOM 元素。
- setImage(imageUrl, [callback]): 設(shè)置圖像的源,并在加載完成后可選地調(diào)用回調(diào)函數(shù)。
- crossOrigin: 設(shè)置為 true 以啟用跨域圖像加載。
- toDataURL([options]): 將圖像轉(zhuǎn)換為數(shù)據(jù) URL。
- toDataURLWithMultiplier([multiplier], [callback]): 將圖像轉(zhuǎn)換為數(shù)據(jù) URL,并允許指定縮放因子。
- clone(): 創(chuàng)建并返回當(dāng)前圖像對(duì)象的深拷貝。
- toJSON(): 序列化圖像對(duì)象為 JSON 字符串。
- setAngle(angle): 設(shè)置圖像的旋轉(zhuǎn)角度。
- setOpacity(opacity): 設(shè)置圖像的不透明度。
- setFlipX(flipX): 沿 X 軸翻轉(zhuǎn)圖像。
- setFlipY(flipY): 沿 Y 軸翻轉(zhuǎn)圖像。
- scale(scaleX, scaleY): 縮放圖像。
- skew(skewX, skewY): 對(duì)圖像進(jìn)行傾斜變換。
- rotate(angle): 旋轉(zhuǎn)圖像。
- scaleToWidth(width): 根據(jù)給定的寬度縮放圖像。
- scaleToHeight(height): 根據(jù)給定的高度縮放圖像。
- setWidth(width): 設(shè)置圖像的寬度。
- setHeight(height): 設(shè)置圖像的高度。
- setPosition({ left, top }): 設(shè)置圖像的位置。
- setSelectabe(selectable): 設(shè)置圖像是否可選中。
- setHasControls(hasControls): 設(shè)置圖像是否顯示控制點(diǎn)。
- setOriginX(originX): 設(shè)置圖像旋轉(zhuǎn)和縮放的 X 軸原點(diǎn)。
- setOriginY(originY): 設(shè)置圖像旋轉(zhuǎn)和縮放的 Y 軸原點(diǎn)。
- setStroke(stroke): 設(shè)置圖像邊框的顏色。
- setStrokeWidth(strokeWidth): 設(shè)置圖像邊框的寬度。
- setBackgroundColor(backgroundColor): 設(shè)置圖像的背景顏色。
- setFill(fill): 設(shè)置圖像的填充顏色。
- setStrokeDashArray(strokeDashArray): 設(shè)置圖像邊框的虛線樣式。
- setTransformMatrix(matrix): 應(yīng)用一個(gè)變換矩陣到圖像上。
- setVisible(visible): 設(shè)置圖像是否可見。
- bringToFront(): 將圖像移動(dòng)到畫布的最上層。
- sendToBack(): 將圖像移動(dòng)到畫布的最下層。
- moveUp(): 將圖像向上移動(dòng)一層。
- moveDown(): 將圖像向下移動(dòng)一層。
- remove(): 從畫布上移除圖像。
3. 問題記錄
- EraserBrush不是 Fabric 默認(rèn)構(gòu)建的一部分
4. 涂抹
設(shè)置擦除,圖形的erasable為true時(shí)可擦除:
// 鉛筆筆刷
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);
/* 設(shè)置鼠標(biāo)樣式 */
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);
/* 設(shè)置鼠標(biāo)樣式 */
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';
/**
* 實(shí)際場(chǎng)景: 在進(jìn)行某個(gè)對(duì)象縮放的時(shí)候,由于fabricjs默認(rèn)精度使用的是toFixed(2)。
* 此處為了縮放的精度更準(zhǔn)確一些,因此將NUM_FRACTION_DIGITS默認(rèn)值改為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;
// 繪制圖像函數(shù)
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,
});
}
// 頂點(diǎn)
function peakControl() {
function renderIconEdge(ctx, left, top, styleOverride, fabricObject) {
drawImg(ctx, left, top, edgeImgIcon, 25, 25, fabricObject.angle);
}
// 四角圖標(biāo)
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,
});
}
// 旋轉(zhuǎn)
function rotationControl() {
function renderIconRotate(ctx, left, top, styleOverride, fabricObject) {
drawImg(ctx, left, top, rotateImgIcon, 40, 40, fabricObject.angle);
}
// 旋轉(zhuǎn)圖標(biāo)
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() {
// 頂點(diǎn)圖標(biāo)
peakControl();
// 中間橫杠圖標(biāo)
intervalControl();
// 旋轉(zhuǎn)圖標(biāo)
rotationControl();
fabric.Object.prototype.set({
transparentCorners: false,
borderColor: '#51B9F9',
cornerColor: '#FFF',
borderScaleFactor: 2.5,
cornerStyle: 'circle',
cornerStrokeColor: '#0E98FC',
borderOpacityWhenMoving: 1,
});
}