feat: add background fill functionality for layers
- Implemented FillLayerBackgroundCommand to fill the background of layers with a specified color. - Introduced BackgroundFillManager to manage background fill operations. - Updated LayerManager to include fillLayerBackground method. - Enhanced LayersPanel with a color picker for filling layer backgrounds. - Modified RasterizeLayerCommand to reflect changes in terminology and functionality. - Adjusted LayerSort to ensure filled layers are rendered at the correct z-index. - Updated relevant utility functions and components to support new fill feature.
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { FillLayerBackgroundCommand } from "../commands/FillLayerBackgroundCommand";
|
||||
|
||||
export class BackgroundFillManager {
|
||||
constructor({ canvas, layers, commandManager, canvasManager }) {
|
||||
this.canvas = canvas;
|
||||
this.layers = layers;
|
||||
this.commandManager = commandManager;
|
||||
this.canvasManager = canvasManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充指定图层背景
|
||||
* @param {string} layerId 图层ID
|
||||
* @param {string} fillColor 填充颜色
|
||||
*/
|
||||
async fillLayerBackground(layerId, fillColor) {
|
||||
const command = new FillLayerBackgroundCommand({
|
||||
canvas: this.canvas,
|
||||
layers: this.layers,
|
||||
layerId,
|
||||
fillColor,
|
||||
canvasManager: this.canvasManager,
|
||||
});
|
||||
if (this.commandManager) {
|
||||
await this.commandManager.execute(command);
|
||||
} else {
|
||||
await command.execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,6 +486,23 @@ export class CanvasManager {
|
||||
obj.setCoords(); // 更新对象的控制点坐标
|
||||
});
|
||||
|
||||
let isMaskLayer = false;
|
||||
// 更新蒙层位置
|
||||
this.layers.value.forEach((layer) => {
|
||||
if (layer.clippingMask) {
|
||||
isMaskLayer = true;
|
||||
// 如果图层有遮罩,更新遮罩位置
|
||||
layer.clippingMask.left += deltaX;
|
||||
layer.clippingMask.top += deltaY;
|
||||
}
|
||||
});
|
||||
|
||||
if (isMaskLayer) {
|
||||
requestAnimationFrame(() => {
|
||||
this.layerManager?.updateLayersObjectsInteractivity?.();
|
||||
});
|
||||
}
|
||||
|
||||
// 如果有背景层,更新蒙层位置
|
||||
if (backgroundObject && CanvasConfig.isCropBackground) {
|
||||
this.updateMaskPosition(backgroundObject);
|
||||
@@ -906,7 +923,7 @@ export class CanvasManager {
|
||||
"eraserable",
|
||||
"erasable",
|
||||
]),
|
||||
layers: JSON.stringify(simplifyLayersData), // 简化图层数据
|
||||
layers: simplifyLayersData, // 简化图层数据
|
||||
// layers: JSON.stringify(JSON.parse(JSON.stringify(this.layers.value))), // 全数据
|
||||
version: "1.0", // 添加版本信息
|
||||
timestamp: new Date().toISOString(), // 添加时间戳
|
||||
@@ -939,7 +956,7 @@ export class CanvasManager {
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const tempLayers = JSON.parse(parsedJson?.layers) || [];
|
||||
const tempLayers = parsedJson?.layers || [];
|
||||
const canvasData = parsedJson?.canvas;
|
||||
|
||||
if (!tempLayers) {
|
||||
@@ -999,14 +1016,14 @@ export class CanvasManager {
|
||||
// 重置画布数据
|
||||
this.setCanvasSize(this.canvas.width, this.canvas.height);
|
||||
// 重新构建对象关系
|
||||
restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
|
||||
// restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
|
||||
// 验证图层关联关系 - 稳定后可以注释
|
||||
const isValidate = validateLayerAssociations(
|
||||
this.layers.value,
|
||||
this.canvas.getObjects()
|
||||
);
|
||||
// const isValidate = validateLayerAssociations(
|
||||
// this.layers.value,
|
||||
// this.canvas.getObjects()
|
||||
// );
|
||||
|
||||
console.log("图层关联验证结果:", isValidate);
|
||||
// console.log("图层关联验证结果:", isValidate);
|
||||
// 排序
|
||||
// 使用LayerSort工具重新排列画布对象(如果可用)
|
||||
await this?.layerManager?.layerSort?.rearrangeObjects();
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { BackgroundFillManager } from "./BackgroundFillManager";
|
||||
import {
|
||||
AddLayerCommand,
|
||||
PasteLayerCommand,
|
||||
@@ -89,6 +90,13 @@ export class LayerManager {
|
||||
this.commandManager = options.commandManager;
|
||||
this.canvasManager = options.canvasManager || null;
|
||||
|
||||
this.backgroundFillManager = new BackgroundFillManager({
|
||||
canvas: this.canvas,
|
||||
layers: this.layers,
|
||||
commandManager: this.commandManager,
|
||||
canvasManager: this.canvasManager,
|
||||
});
|
||||
|
||||
// 编辑器模式:draw(绘画)、select(选择)、pan(拖拽)
|
||||
this.editorMode = options.editorMode || CanvasConfig.defaultTool;
|
||||
|
||||
@@ -332,6 +340,14 @@ export class LayerManager {
|
||||
}
|
||||
// 如果是组图层 则给所有子对象设置裁剪对象
|
||||
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
|
||||
if (layer.fill) {
|
||||
const fabricObject = this.canvas.getObjects().find((o) => o.id === layer.fill.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.clipPath = clippingMaskFabricObject;
|
||||
fabricObject.dirty = true; // 标记为脏对象
|
||||
fabricObject.setCoords();
|
||||
}
|
||||
}
|
||||
layer.children.forEach((childLayer) => {
|
||||
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
|
||||
if (childObj) {
|
||||
@@ -339,6 +355,15 @@ export class LayerManager {
|
||||
childObj.dirty = true; // 标记为脏对象
|
||||
childObj.setCoords();
|
||||
}
|
||||
|
||||
if (childLayer.fill) {
|
||||
const fabricObject = this.canvas.getObjects().find((o) => o.id === childLayer.fill.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.clipPath = clippingMaskFabricObject;
|
||||
fabricObject.dirty = true; // 标记为脏对象
|
||||
fabricObject.setCoords();
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
layer.fabricObjects?.forEach((obj) => {
|
||||
@@ -349,9 +374,28 @@ export class LayerManager {
|
||||
fabricObject.setCoords();
|
||||
}
|
||||
});
|
||||
|
||||
if (layer.fill) {
|
||||
const fabricObject = this.canvas.getObjects().find((o) => o.id === layer.fill.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.clipPath = clippingMaskFabricObject;
|
||||
fabricObject.dirty = true; // 标记为脏对象
|
||||
fabricObject.setCoords();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 填充图层背景
|
||||
* @param {string} layerId 图层ID
|
||||
* @param {string} fillColor 填充颜色
|
||||
*/
|
||||
async fillLayerBackground(layerId, fillColor) {
|
||||
await this.backgroundFillManager.fillLayerBackground(layerId, fillColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新图层
|
||||
* @param {string} name 图层名称
|
||||
@@ -908,9 +952,25 @@ export class LayerManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (child.fill) {
|
||||
// 如果图层有填充颜色,设置所有对象的填充颜色
|
||||
const { object } = findObjectById(this.canvas, child.fill.id);
|
||||
if (object) {
|
||||
acc.push(object);
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (layer.fill) {
|
||||
// 如果图层有填充颜色,设置所有对象的填充颜色
|
||||
const { object } = findObjectById(this.canvas, layer.fill.id);
|
||||
if (object) {
|
||||
allObjects.push(object);
|
||||
}
|
||||
}
|
||||
|
||||
if (allObjects.length) {
|
||||
// 切换到选择模式
|
||||
this?.toolManager?.setTool(OperationType.SELECT);
|
||||
@@ -2742,7 +2802,7 @@ export class LayerManager {
|
||||
* @returns {boolean} 是否可以栅格化
|
||||
*/
|
||||
canRasterizeLayer(layerId) {
|
||||
const layer = this.getLayerById(layerId);
|
||||
const layer = findLayerRecursively(this.layers.value, layerId)?.layer;
|
||||
if (!layer) return false;
|
||||
|
||||
// 不允许栅格化背景图层和固定图层
|
||||
@@ -2751,13 +2811,9 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
// 检查图层是否有内容可以栅格化
|
||||
if (layer.type === "group") {
|
||||
if (layer.type === "group" || layer.children.length > 0) {
|
||||
// 组图层:检查是否有子图层且子图层有内容
|
||||
return (
|
||||
layer.children &&
|
||||
layer.children.length > 0 &&
|
||||
layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0)
|
||||
);
|
||||
return layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0);
|
||||
} else {
|
||||
// 普通图层:检查是否有对象
|
||||
return layer.fabricObjects && layer.fabricObjects.length > 0;
|
||||
@@ -2835,7 +2891,7 @@ export class LayerManager {
|
||||
let isUpdating = false;
|
||||
let lastUpdateTime = 0;
|
||||
let hasMoved = false; // 追踪是否实际发生了移动
|
||||
const UPDATE_THRESHOLD = 16; // 约60fps
|
||||
const UPDATE_THRESHOLD = 32; // 约60fps
|
||||
|
||||
// 移动开始事件处理
|
||||
const handleMovingStart = (e) => {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
* - https://mrdoob.com/projects/harmony/
|
||||
* - http://perfectionkills.com/exploring-canvas-drawing-techniques/
|
||||
*/
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
(function (fabric) {
|
||||
|
||||
Reference in New Issue
Block a user