合并画布代码

This commit is contained in:
X1627315083
2025-06-18 11:05:23 +08:00
parent 903c0ebdf5
commit 9c7fae36eb
118 changed files with 23633 additions and 8201 deletions

View File

@@ -0,0 +1,820 @@
import { ReorderChildLayersCommand } from "../commands/LayerCommands";
import { optimizeCanvasRendering } from "./helper";
import { findLayerRecursively, LayerType } from "./layerHelper";
/**
* 图层排序工具类
* 提供图层排序、重新排列画布对象等功能
* 基于fabric.js 5.3.1版本开发
*/
export class LayerSort {
/**
* 构造函数
* @param {Object} canvas fabric.js画布实例
* @param {Object} layers 图层数组响应式引用
*/
constructor(canvas, layers, options = {}) {
this.canvas = canvas;
this.layers = layers;
this.commandManager = options.commandManager || null; // 命令管理器(可选)
}
/**
* 重新排列画布上的对象以匹配图层顺序
* 使用 fabric.js 的 moveTo 方法直接调整对象层级,无需清空画布
*/
async rearrangeObjects() {
if (!this.canvas) return;
const canvasObjects = this.canvas.getObjects();
if (canvasObjects.length === 0) return;
// 使用画布渲染优化
await optimizeCanvasRendering(this.canvas, () => {
// 计算每个对象应该在的 z-index 位置
const objectZIndexMap = this.calculateObjectZIndexes();
// 按照新的 z-index 排序对象
const sortedObjects = canvasObjects
.map((obj) => ({
object: obj,
targetZIndex: objectZIndexMap.get(obj.id) ?? -1,
}))
.filter((item) => item.targetZIndex >= 0) // 过滤掉无效对象
.sort((a, b) => a.targetZIndex - b.targetZIndex);
// 使用 fabric.js 的 moveTo 方法重新排序
sortedObjects.forEach((item, index) => {
const currentIndex = this.canvas.getObjects().indexOf(item.object);
if (currentIndex !== index && currentIndex !== -1) {
// 将对象移动到正确的位置
this.canvas.moveTo(item.object, index);
}
});
});
}
/**
* 计算每个对象在画布中应该的 z-index
* 考虑图层类型、组结构和子图层
* @returns {Map} 对象ID到z-index的映射
*/
calculateObjectZIndexes() {
const zIndexMap = new Map();
let currentZIndex = 0;
// 按照图层在数组中的顺序从后往前遍历(数组末尾 = 画布底层)
for (let i = this.layers.value.length - 1; i >= 0; i--) {
const layer = this.layers.value[i];
// 跳过不可见图层
if (!layer.visible) {
continue;
}
// 处理不同类型的图层
if (layer.isBackground && layer.fabricObject) {
// 背景图层对象放在最底层
zIndexMap.set(layer.fabricObject.id, currentZIndex++);
} else if (layer.isFixed && layer.fabricObjects) {
// 固定图层对象
layer.fabricObjects.forEach((obj) => {
if (obj?.id) {
zIndexMap.set(obj.id, currentZIndex++);
}
});
} else if (!layer.isBackground && !layer.isFixed) {
// 普通图层
currentZIndex = this.processLayerObjects(
layer,
currentZIndex,
zIndexMap
);
}
}
return zIndexMap;
}
/**
* 处理图层对象,包括组和子图层的情况
* @param {Object} layer 图层对象
* @param {number} currentZIndex 当前z-index
* @param {Map} zIndexMap z-index映射表
* @returns {number} 更新后的z-index
*/
processLayerObjects(layer, currentZIndex, zIndexMap) {
// 检查是否有子图层(组图层)
if (layer.children?.length > 0) {
// 处理每个子图层
// 按照图层在数组中的顺序从后往前遍历(数组末尾 = 画布底层)
for (let i = layer.children.length - 1; i >= 0; i--) {
const childLayer = layer.children[i];
// 跳过不可见图层
if (!childLayer.visible) {
continue;
}
for (let j = childLayer.fabricObjects.length - 1; j >= 0; j--) {
const obj = childLayer.fabricObjects[j];
if (obj?.id) {
zIndexMap.set(obj.id, currentZIndex++);
}
}
}
}
// 处理图层本身的对象
if (Array.isArray(layer.fabricObjects)) {
for (let j = layer.fabricObjects.length - 1; j >= 0; j--) {
const obj = layer.fabricObjects[j];
if (obj?.id) {
zIndexMap.set(obj.id, currentZIndex++);
}
}
}
return currentZIndex;
}
/**
* 获取指定图层的子图层,按照正确顺序排列
* @param {string} parentLayerId 父图层ID
* @returns {Array} 子图层数组
*/
getChildLayersInOrder(parentLayerId) {
// 获取所有子图层
const childLayers =
this.layers.value.filter((layer) => layer.id === parentLayerId)
?.children || [];
return childLayers;
}
/**
* 根据ID获取图层
* @param {string} layerId 图层ID
* @returns {Object|null} 图层对象或null
*/
getLayerById(layerId) {
if (!layerId || !this.layers.value) return null;
return this.layers.value.find((layer) => layer.id === layerId) ?? null;
}
/**
* 批量重新排列对象(异步版本,适用于大量对象)
* @returns {Promise<void>}
*/
async rearrangeObjectsAsync() {
if (!this.canvas) return;
const canvasObjects = this.canvas.getObjects();
if (canvasObjects.length === 0) return;
return new Promise((resolve) => {
// 使用 requestAnimationFrame 进行异步处理
requestAnimationFrame(() => {
this.canvas.renderOnAddRemove = false;
try {
const objectZIndexMap = this.calculateObjectZIndexes();
const sortedObjects = canvasObjects
.map((obj) => ({
object: obj,
targetZIndex: objectZIndexMap.get(obj.id) ?? -1,
}))
.filter((item) => item.targetZIndex >= 0)
.sort((a, b) => a.targetZIndex - b.targetZIndex);
// 分批处理,避免一次性处理太多对象
const batchSize = LayerSortConstants.BATCH_SIZE;
let currentBatch = 0;
const processBatch = () => {
const start = currentBatch * batchSize;
const end = Math.min(start + batchSize, sortedObjects.length);
for (let i = start; i < end; i++) {
const item = sortedObjects[i];
const currentIndex = this.canvas
.getObjects()
.indexOf(item.object);
if (currentIndex !== i && currentIndex !== -1) {
this.canvas.moveTo(item.object, i);
}
}
currentBatch++;
if (end < sortedObjects.length) {
// 继续处理下一批
requestAnimationFrame(processBatch);
} else {
// 所有批次处理完成
this.canvas.renderOnAddRemove = true;
this.canvas.renderAll();
resolve();
}
};
processBatch();
} catch (error) {
this.canvas.renderOnAddRemove = true;
this.canvas.renderAll();
console.error("重新排列对象时出错:", error);
resolve();
}
});
});
}
/**
* 验证对象顺序是否正确
* @returns {boolean} 顺序是否正确
*/
validateObjectOrder() {
const canvasObjects = this.canvas.getObjects();
const objectZIndexMap = this.calculateObjectZIndexes();
for (let i = 0; i < canvasObjects.length - 1; i++) {
const currentObj = canvasObjects[i];
const nextObj = canvasObjects[i + 1];
const currentZIndex = objectZIndexMap.get(currentObj.id);
const nextZIndex = objectZIndexMap.get(nextObj.id);
if (currentZIndex !== undefined && nextZIndex !== undefined) {
if (currentZIndex > nextZIndex) {
return false; // 顺序不正确
}
}
}
return true; // 顺序正确
}
/**
* 图层排序规则:背景图层 > 固定图层 > 普通图层
* @param {Array} layers 图层数组
* @returns {Array} 排序后的图层数组
*/
sortLayers(layers = null) {
const targetLayers = layers ?? this.layers.value;
return [...targetLayers].sort((a, b) => {
// 如果a是背景图层它应该排在后面最底层
if (a.isBackground) return 1;
// 如果b是背景图层它应该排在后面最底层
if (b.isBackground) return -1;
// 如果a是固定图层而b不是固定图层a应该排在后面固定图层在普通图层下方
if (a.isFixed && !b.isFixed) return 1;
// 如果b是固定图层而a不是固定图层b应该排在后面固定图层在普通图层下方
if (b.isFixed && !a.isFixed) return -1;
// 其他情况保持原有顺序
return 0;
});
}
/**
* 获取图层在排序后的正确插入位置
* @param {Object} newLayer 新图层
* @param {string} targetLayerId 目标图层ID插入到该图层之前
* @returns {number} 插入位置索引
*/
getInsertIndex(newLayer, targetLayerId = null) {
if (!targetLayerId) {
// 如果没有指定目标图层,根据图层类型决定插入位置
if (newLayer.isBackground) {
return this.layers.value.length; // 背景图层插入到最后
} else if (newLayer.isFixed) {
// 固定图层插入到背景图层之前
const bgIndex = this.layers.value.findIndex(
(layer) => layer.isBackground
);
return bgIndex !== -1 ? bgIndex : this.layers.value.length;
} else {
// 普通图层插入到固定图层之前
const fixedIndex = this.layers.value.findIndex(
(layer) => layer.isFixed
);
return fixedIndex !== -1 ? fixedIndex : this.layers.value.length;
}
}
// 如果指定了目标图层,插入到目标图层之前
const targetIndex = this.layers.value.findIndex(
(layer) => layer.id === targetLayerId
);
return targetIndex !== -1 ? targetIndex : this.layers.value.length;
}
/**
* 移动图层到指定位置
* @param {string} layerId 要移动的图层ID
* @param {number} newIndex 新位置索引
* @returns {boolean} 是否移动成功
*/
async moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) {
// 检查父图层是否存在
// const parentLayer = this.getLayerById(parentId);
const { layer: childLayer, parent: parentLayer } = findLayerRecursively(
this.layers.value,
layerId,
parentId
);
if (!parentLayer) {
console.warn(`父图层 ${parentId} 不存在`);
return false;
}
// 获取所有子图层
const childLayers = parentLayer?.children || [];
// 检查索引有效性
if (
oldIndex < 0 ||
newIndex < 0 ||
oldIndex >= childLayers.length ||
newIndex >= childLayers.length
) {
console.warn("子图层排序索引无效");
return false;
}
// 检查是否是同一位置
if (oldIndex === newIndex) {
return true;
}
// 验证图层ID
const layer = childLayers[oldIndex];
if (!layer || layer.id !== layerId) {
console.warn("子图层ID与索引不匹配");
return false;
}
// 更新父图层的children数组 - 执行命令
const command = new ReorderChildLayersCommand({
parentId,
oldIndex,
newIndex,
layerId,
layers: this.layers,
canvas: this.canvas,
layerSort: this, // 传入当前实例
});
if (this.commandManager) {
await this.commandManager?.execute(command);
} else {
await command?.execute?.();
}
return true;
}
/**
* 获取图层的有效移动范围
* @param {string} layerId 图层ID
* @returns {Object} 包含最小和最大索引的对象
*/
getLayerMoveRange(layerId) {
const layer = this.getLayerById(layerId);
if (!layer || layer.isBackground || layer.isFixed) {
return { minIndex: -1, maxIndex: -1 };
}
// 普通图层只能在普通图层范围内移动
const normalLayers = this.layers.value
.map((layer, index) => ({ layer, index }))
.filter((item) => !item.layer.isBackground && !item.layer.isFixed);
if (normalLayers.length === 0) {
return { minIndex: -1, maxIndex: -1 };
}
return {
minIndex: normalLayers[0].index,
maxIndex: normalLayers[normalLayers.length - 1].index,
};
}
/**
* 拖拽排序图层
* @param {number} oldIndex 原索引
* @param {number} newIndex 新索引
* @param {string} layerId 图层ID
* @returns {boolean} 是否排序成功
*/
async reorderLayers(oldIndex, newIndex, layerId) {
// 检查索引有效性
if (
oldIndex < 0 ||
newIndex < 0 ||
oldIndex >= this.layers.value.length ||
newIndex >= this.layers.value.length
) {
console.warn("图层排序索引无效");
return false;
}
// 检查是否是同一位置
if (oldIndex === newIndex) {
return true;
}
// 获取要移动的图层
const layer = this.layers.value[oldIndex];
if (!layer || layer.id !== layerId) {
console.warn("图层ID与索引不匹配");
return false;
}
// 检查是否是背景层或固定层(不允许排序)
if (layer.isBackground || layer.isFixed) {
console.warn("背景层和固定层不能参与排序");
return false;
}
// 检查目标位置是否合法(不能移到背景层或固定层的位置)
const targetLayer = this.layers.value[newIndex];
if (targetLayer && (targetLayer.isBackground || targetLayer.isFixed)) {
console.warn("不能移动到背景层或固定层的位置");
return false;
}
// 执行排序
const layersArray = [...this.layers.value];
const [movedLayer] = layersArray.splice(oldIndex, 1);
layersArray.splice(newIndex, 0, movedLayer);
this.layers.value = layersArray;
// 重新排列画布对象
await this.rearrangeObjects();
return true;
}
/**
* 子图层排序
* @param {string} parentId 父图层ID
* @param {number} oldIndex 原索引
* @param {number} newIndex 新索引
* @param {string} layerId 子图层ID
* @returns {boolean} 是否排序成功
*/
async reorderChildLayers(parentId, oldIndex, newIndex, layerId) {
// 检查父图层是否存在
// const parentLayer = this.getLayerById(parentId);
const { layer: childLayer, parent: parentLayer } = findLayerRecursively(
this.layers.value,
layerId,
parentId
);
if (!parentLayer) {
console.warn(`父图层 ${parentId} 不存在`);
return false;
}
// 获取所有子图层
const childLayers = parentLayer?.children || [];
// 检查索引有效性
if (
oldIndex < 0 ||
newIndex < 0 ||
oldIndex >= childLayers.length ||
newIndex >= childLayers.length
) {
console.warn("子图层排序索引无效");
return false;
}
// 检查是否是同一位置
if (oldIndex === newIndex) {
return true;
}
// 验证图层ID
const layer = childLayers[oldIndex];
if (!layer || layer.id !== layerId) {
console.warn("子图层ID与索引不匹配");
return false;
}
// 更新父图层的children数组 - 执行命令
const command = ReorderChildLayersCommand({
parentId,
oldIndex,
newIndex,
layerId,
layers: this.layers,
canvas: this.canvas,
layerSort: this, // 传入当前实例
});
if (this.commandManager) {
await this.commandManager?.execute(command);
} else {
await command?.execute?.();
}
return true;
}
/**
* 智能排序 - 根据对象类型和位置自动调整图层顺序
* @param {Array} targetLayerIds 要排序的图层ID数组
* @returns {boolean} 是否排序成功
*/
async smartSort(targetLayerIds = null) {
const layersToSort = targetLayerIds
? this.layers.value.filter((layer) => targetLayerIds.includes(layer.id))
: this.layers.value.filter(
(layer) => !layer.isBackground && !layer.isFixed
);
if (layersToSort.length <= 1) return true;
// 按照对象类型和位置进行智能排序
layersToSort.sort((a, b) => {
const aWeight = this.getLayerSortWeight(a);
const bWeight = this.getLayerSortWeight(b);
if (aWeight !== bWeight) {
return bWeight - aWeight; // 权重高的在上层
}
// 权重相同时按照Y坐标排序Y值小的在上层
const aY = this.getLayerAverageY(a);
const bY = this.getLayerAverageY(b);
return aY - bY;
});
// 更新图层顺序
const sortedLayerIds = layersToSort.map((layer) => layer.id);
const otherLayers = this.layers.value.filter(
(layer) => !sortedLayerIds.includes(layer.id)
);
// 重新组织图层数组:保持背景层和固定层的位置
const newLayers = [];
// 添加普通图层(已排序)
newLayers.push(...layersToSort);
// 添加其他普通图层
otherLayers.forEach((layer) => {
if (!layer.isBackground && !layer.isFixed) {
newLayers.push(layer);
}
});
// 添加固定图层
otherLayers.forEach((layer) => {
if (layer.isFixed) {
newLayers.push(layer);
}
});
// 添加背景图层
otherLayers.forEach((layer) => {
if (layer.isBackground) {
newLayers.push(layer);
}
});
this.layers.value = newLayers;
await this.rearrangeObjects();
return true;
}
/**
* 获取图层的排序权重
* @param {Object} layer 图层对象
* @returns {number} 排序权重
*/
getLayerSortWeight(layer) {
const weightMap = LayerSortConstants.LAYER_PRIORITY;
if (layer.isBackground) return weightMap[LayerType.BACKGROUND];
if (layer.isFixed) return weightMap[LayerType.FIXED];
if (layer.children?.length > 0) return weightMap[LayerType.GROUP];
// 根据对象类型调整权重
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
const firstObj = layer.fabricObjects[0];
if (firstObj.type === "text") return weightMap[LayerType.NORMAL] + 10;
if (firstObj.type === "image") return weightMap[LayerType.NORMAL] + 5;
}
return weightMap[LayerType.NORMAL];
}
/**
* 获取图层对象的平均Y坐标
* @param {Object} layer 图层对象
* @returns {number} 平均Y坐标
*/
getLayerAverageY(layer) {
let totalY = 0;
let count = 0;
if (layer.fabricObject) {
totalY += layer.fabricObject.top || 0;
count++;
}
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
layer.fabricObjects.forEach((obj) => {
totalY += obj.top || 0;
count++;
});
}
return count > 0 ? totalY / count : 0;
}
/**
* 优化图层结构 - 清理空图层、合并相邻相似图层等
* @returns {Object} 优化结果统计
*/
async optimizeLayerStructure() {
const stats = {
removedEmptyLayers: 0,
mergedLayers: 0,
reorderedLayers: 0,
};
// 清理空图层
const emptyLayers = this.layers.value.filter(
(layer) =>
!layer.isBackground &&
!layer.isFixed &&
(!layer.fabricObjects || layer.fabricObjects.length === 0) &&
(!layer.children || layer.children.length === 0)
);
emptyLayers.forEach((layer) => {
const index = this.layers.value.findIndex((l) => l.id === layer.id);
if (index !== -1) {
this.layers.value.splice(index, 1);
stats.removedEmptyLayers++;
}
});
// 重新排序以确保正确的层级关系
const wasReordered = this.sortLayers();
if (wasReordered) {
stats.reorderedLayers = this.layers.value.length;
await this.rearrangeObjects();
}
return stats;
}
}
/**
* 创建图层排序工具实例
* @param {Object} canvas fabric.js画布实例
* @param {Object} layers 图层数组响应式引用
* @returns {LayerSort} 图层排序工具实例
*/
export const createLayerSort = (canvas, layers, options = {}) => {
return new LayerSort(canvas, layers, options);
};
/**
* 图层排序相关的常量
*/
export const LayerSortConstants = {
// 图层类型优先级(数值越大优先级越高,在画布中越靠上)
LAYER_PRIORITY: {
[LayerType.BACKGROUND]: 0,
[LayerType.FIXED]: 1,
[LayerType.NORMAL]: 2,
[LayerType.GROUP]: 2,
},
// 批处理大小
BATCH_SIZE: 50,
// 性能阈值
PERFORMANCE_THRESHOLD: {
OBJECTS_COUNT: 100, // 超过此数量使用异步处理
LAYERS_COUNT: 20, // 超过此数量使用分批处理
},
};
/**
* 图层排序辅助函数
*/
export const LayerSortUtils = {
/**
* 检查是否需要异步处理
* @param {number} objectsCount 对象数量
* @param {number} layersCount 图层数量
* @returns {boolean} 是否需要异步处理
*/
shouldUseAsyncProcessing(objectsCount, layersCount) {
return (
objectsCount > LayerSortConstants.PERFORMANCE_THRESHOLD.OBJECTS_COUNT ||
layersCount > LayerSortConstants.PERFORMANCE_THRESHOLD.LAYERS_COUNT
);
},
/**
* 获取图层的排序权重
* @param {Object} layer 图层对象
* @returns {number} 排序权重
*/
getLayerSortWeight(layer) {
if (layer.isBackground)
return LayerSortConstants.LAYER_PRIORITY[LayerType.BACKGROUND];
if (layer.isFixed)
return LayerSortConstants.LAYER_PRIORITY[LayerType.FIXED];
if (layer.children?.length > 0)
return LayerSortConstants.LAYER_PRIORITY[LayerType.GROUP];
return LayerSortConstants.LAYER_PRIORITY[LayerType.NORMAL];
},
/**
* 比较两个图层的排序优先级
* @param {Object} layerA 图层A
* @param {Object} layerB 图层B
* @returns {number} 比较结果
*/
compareLayerPriority(layerA, layerB) {
const weightA = this.getLayerSortWeight(layerA);
const weightB = this.getLayerSortWeight(layerB);
return weightB - weightA; // 权重高的排在前面(画布上层)
},
};
/**
* 图层排序混入方法 - 用于在 LayerManager 中使用
*/
export const LayerSortMixin = {
/**
* 初始化图层排序工具
*/
initLayerSort() {
this.layerSort = new LayerSort(this.canvas, this.layers);
},
/**
* 重新排列画布对象
*/
rearrangeCanvasObjects() {
if (this.layerSort) {
// 检查是否需要异步处理
const objectsCount = this.canvas?.getObjects()?.length || 0;
const layersCount = this.layers?.value?.length || 0;
if (LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount)) {
return this.layerSort.rearrangeObjectsAsync();
} else {
this.layerSort.rearrangeObjects();
return Promise.resolve();
}
}
},
/**
* 排序图层
*/
sortLayersWithTool() {
if (this.layerSort) {
this.layers.value = this.layerSort.sortLayers();
return this.layerSort.rearrangeObjects();
}
},
/**
* 智能排序图层
*/
smartSortLayers(targetLayerIds = null) {
if (this.layerSort) {
return this.layerSort.smartSort(targetLayerIds);
}
return false;
},
/**
* 优化图层结构
*/
optimizeLayerStructure() {
if (this.layerSort) {
return this.layerSort.optimizeLayerStructure();
}
return { removedEmptyLayers: 0, mergedLayers: 0, reorderedLayers: 0 };
},
};

View File

@@ -1,4 +1,4 @@
//import { fabric } from "fabric-with-all";
import { fabric } from "fabric-with-all";
import { canvasConfig } from "../config/canvasConfig";
/**

View File

@@ -434,25 +434,324 @@ export function createCancellablePromise(executor) {
};
}
// 导出所有工具函数
export default {
deepCompare,
deepClone,
applyDiff,
throttle,
debounce,
generateId,
formatFileSize,
formatDuration,
isValidCommand,
isPromise,
safeJSONParse,
safeJSONStringify,
getObjectDepth,
getObjectSize,
checkBrowserSupport,
delay,
retry,
batchProcess,
createCancellablePromise,
/**
* 增强版检查对象是否在画布中(包括组内对象)
* @param {fabric.Canvas} canvas 画布实例
* @param {fabric.Object} targetObj 目标对象
* @returns {Object} { flag: boolean, object: fabric.Object, parent: fabric.Group|null }
*/
export function objectIsInCanvas(canvas, targetObj) {
if (!canvas || !targetObj) {
return { flag: false, object: null, parent: null };
}
const targetId = targetObj.id;
if (!targetId) {
return { flag: false, object: null, parent: null };
}
// 首先检查顶层对象
const topLevelObjects = canvas.getObjects();
// 直接在顶层查找
const directMatch = topLevelObjects.find((obj) => obj.id === targetId);
if (directMatch) {
return { flag: true, object: directMatch, parent: null };
}
// 递归检查组内对象
for (const obj of topLevelObjects) {
if (obj.type === "group") {
const result = findObjectInGroup(obj, targetId);
if (result.found) {
return { flag: true, object: result.object, parent: obj };
}
}
}
return { flag: false, object: null, parent: null };
}
/**
* 在组中递归查找对象
* @param {fabric.Group} group 组对象
* @param {string} targetId 目标对象ID
* @returns {Object} { found: boolean, object: fabric.Object|null }
*/
function findObjectInGroup(group, targetId) {
if (!group || group.type !== "group" || !group.getObjects) {
return { found: false, object: null };
}
const groupObjects = group.getObjects();
for (const obj of groupObjects) {
if (obj.id === targetId) {
return { found: true, object: obj };
}
// 递归检查嵌套组
if (obj.type === "group") {
const nestedResult = findObjectInGroup(obj, targetId);
if (nestedResult.found) {
return nestedResult;
}
}
}
return { found: false, object: null };
}
/**
* 通过ID查找对象增强版
* @param {fabric.Canvas} canvas 画布实例
* @param {string} objectId 对象ID
* @returns {Object} { object: fabric.Object|null, parent: fabric.Group|null }
*/
export function findObjectById(canvas, objectId) {
if (!canvas || !objectId) {
return { object: null, parent: null };
}
const result = objectIsInCanvas(canvas, { id: objectId });
return { object: result.object, parent: result.parent };
}
/**
* 安全移除画布对象(包括组内对象)
* @param {fabric.Canvas} canvas 画布实例
* @param {fabric.Object} targetObj 目标对象
* @returns {boolean} 是否成功移除
*/
export function removeCanvasObjectByObject(canvas, targetObj) {
if (!canvas || !targetObj) {
return false;
}
const result = objectIsInCanvas(canvas, targetObj);
if (!result.flag) {
return false;
}
try {
if (result.parent) {
// 对象在组中,从组中移除
result.parent.removeWithUpdate(result.object);
} else {
// 对象在顶层,直接从画布移除
canvas.remove(result.object);
}
return true;
} catch (error) {
console.error("移除对象时发生错误:", error);
return false;
}
}
/**
* 优化画布渲染 统一渲染完成后才执行
* @param {fabric.Canvas} canvas 画布实例
* @param {Function} callback 渲染执行函数
*/
export const optimizeCanvasRendering = async (canvas, callback) => {
return new Promise((resolve) => {
if (!canvas || typeof callback !== "function") {
resolve();
return;
}
// 暂停渲染以提高性能
canvas.skipTargetFind = true;
// 开始渲染
// 暂停实时渲染和对象查找
const wasRenderOnAddRemove = canvas.renderOnAddRemove;
canvas.renderOnAddRemove = false; // 禁用自动渲染
// 等待下一帧渲染完成
requestAnimationFrame(async () => {
// 恢复渲染设置
canvas.skipTargetFind = false;
canvas.renderOnAddRemove = wasRenderOnAddRemove;
if (isPromise(callback)) {
await callback?.();
} else {
callback?.();
}
canvas.renderAll(); // 确保画布重新渲染 - 同步渲染
resolve();
});
});
};
/**
* 获取对象在画布中的z-index位置
* @param {fabric.Canvas} canvas 画布实例
* @param {fabric.Object} targetObj 目标对象
* @returns {number} z-index位置-1表示未找到
*/
export function getObjectZIndex(canvas, targetObj) {
if (!canvas || !targetObj) {
return -1;
}
const result = objectIsInCanvas(canvas, targetObj);
if (!result.flag) {
return -1;
}
if (result.parent) {
// 对象在组中,返回组在画布中的位置
const allObjects = canvas.getObjects();
return allObjects.indexOf(result.parent);
} else {
// 对象在顶层,直接返回在画布中的位置
const allObjects = canvas.getObjects();
return allObjects.indexOf(result.object);
}
}
/**
* 在指定的z-index位置插入对象
* @param {fabric.Canvas} canvas 画布实例
* @param {fabric.Object} object 要插入的对象
* @param {number} zIndex z-index位置
* @param {boolean} renderAll 是否立即渲染默认true
* @returns {boolean} 是否成功插入
*/
export function insertObjectAtZIndex(canvas, object, zIndex, renderAll = true) {
if (!canvas || !object || zIndex < 0) {
return false;
}
try {
// 确保z-index不超过当前对象数量
const maxIndex = canvas.getObjects().length;
const safeZIndex = Math.min(zIndex, maxIndex);
canvas.insertAt(object, safeZIndex, false);
if (renderAll) {
canvas.renderAll();
}
return true;
} catch (error) {
console.error("插入对象到指定z-index位置失败:", error);
return false;
}
}
/**
* 移动对象到指定的z-index位置
* @param {fabric.Canvas} canvas 画布实例
* @param {fabric.Object} object 要移动的对象
* @param {number} zIndex 目标z-index位置
* @param {boolean} renderAll 是否立即渲染默认true
* @returns {boolean} 是否成功移动
*/
export function moveObjectToZIndex(canvas, object, zIndex, renderAll = true) {
if (!canvas || !object || zIndex < 0) {
return false;
}
const result = objectIsInCanvas(canvas, object);
if (!result.flag) {
console.warn("对象不在画布中,无法移动");
return false;
}
try {
// 确保z-index不超过当前对象数量-1因为当前对象也在其中
const maxIndex = canvas.getObjects().length - 1;
const safeZIndex = Math.min(zIndex, maxIndex);
result.object.moveTo(safeZIndex);
if (renderAll) {
canvas.renderAll();
}
return true;
} catch (error) {
console.error("移动对象到指定z-index位置失败:", error);
return false;
}
}
/**
* 交换两个对象的z-index位置
* @param {fabric.Canvas} canvas 画布实例
* @param {fabric.Object} obj1 第一个对象
* @param {fabric.Object} obj2 第二个对象
* @param {boolean} renderAll 是否立即渲染默认true
* @returns {boolean} 是否成功交换
*/
export function swapObjectsZIndex(canvas, obj1, obj2, renderAll = true) {
if (!canvas || !obj1 || !obj2) {
return false;
}
const zIndex1 = getObjectZIndex(canvas, obj1);
const zIndex2 = getObjectZIndex(canvas, obj2);
if (zIndex1 === -1 || zIndex2 === -1) {
console.warn("其中一个或两个对象不在画布中");
return false;
}
try {
// 先移动z-index较大的对象避免索引冲突
if (zIndex1 > zIndex2) {
moveObjectToZIndex(canvas, obj2, zIndex1, false);
moveObjectToZIndex(canvas, obj1, zIndex2, false);
} else {
moveObjectToZIndex(canvas, obj1, zIndex2, false);
moveObjectToZIndex(canvas, obj2, zIndex1, false);
}
if (renderAll) {
canvas.renderAll();
}
return true;
} catch (error) {
console.error("交换对象z-index位置失败:", error);
return false;
}
}
/**
* 获取画布中所有对象的z-index信息
* @param {fabric.Canvas} canvas 画布实例
* @returns {Array} 包含对象ID和z-index的数组
*/
export function getAllObjectsZIndex(canvas) {
if (!canvas) {
return [];
}
const objects = canvas.getObjects();
return objects.map((obj, index) => ({
id: obj.id || `unnamed_${index}`,
zIndex: index,
type: obj.type,
layerId: obj.layerId,
object: obj,
}));
}
/**
* 按图层ID获取对象的z-index信息
* @param {fabric.Canvas} canvas 画布实例
* @param {string} layerId 图层ID
* @returns {Array} 属于指定图层的对象z-index信息
*/
export function getLayerObjectsZIndex(canvas, layerId) {
if (!canvas || !layerId) {
return [];
}
const allInfo = getAllObjectsZIndex(canvas);
return allInfo.filter((info) => info.layerId === layerId);
}

View File

@@ -1,4 +1,4 @@
//import { fabric } from "fabric-with-all";
import { fabric } from "fabric-with-all";
import { LayerType, OperationType, createBitmapLayer } from "./layerHelper";
// 导入新的复合命令
import { CreateImageLayerCommand } from "../commands/LayerCommands";
@@ -7,6 +7,7 @@ import {
ChangeFixedImageCommand,
AddImageToLayerCommand,
} from "../commands/LayerCommands";
import { generateId } from "./helper";
/**
* 加载并处理图片
@@ -44,6 +45,7 @@ export function loadImage(imageSource, options = {}) {
// 设置图片位置 - 默认居中
if (options.centerOnCanvas !== false) {
fabricImage.set({
id: generateId("fabricImage"),
left: (options.canvasWidth || 800) / 2,
top: (options.canvasHeight || 600) / 2,
originX: "center",

View File

@@ -13,6 +13,7 @@ export const LayerType = {
VIDEO: "video", // 视频图层 (预留)
AUDIO: "audio", // 音频图层 (预留)
FIXED: "fixed", // 固定图层 - 位于背景图层之上,普通图层之下
BACKGROUND: "background", // 背景图层 - 位于固定图层之、普通图层之下
};
/**
@@ -461,3 +462,161 @@ export function cloneLayer(layer) {
return clonedLayer;
}
/**
* 递归查找图层(包括子图层)
* @param {Array} layers 图层数组
* @param {string} layerId 要查找的图层ID
* @param {Object} parent 父图层(可选,用于内部递归)
* @returns {Object|null} 包含layer和parent的对象如果未找到返回null
*/
export function findLayerRecursively(layers, layerId, parent = null) {
if (!layers || !Array.isArray(layers) || !layerId) {
return null;
}
// 在当前图层列表中查找
for (const layer of layers) {
if (layer && layer.id === layerId) {
return { layer, parent };
}
// 如果是组图层,递归查找子图层
if (
layer &&
(layer.type === "group" ||
layer.type === LayerType.GROUP ||
(layer.children && Array.isArray(layer.children)))
) {
const result = findInChildLayers(layer.children, layerId, layer);
if (result) {
return result;
}
}
}
return null;
}
/**
* 在子图层中递归查找
* @param {Array} children 子图层数组
* @param {string} layerId 要查找的图层ID
* @param {Object} parent 父图层
* @returns {Object|null} 包含layer和parent的对象如果未找到返回null
*/
export function findInChildLayers(children, layerId, parent) {
if (!children || !Array.isArray(children) || !layerId) {
return null;
}
for (const child of children) {
if (child && child.id === layerId) {
return { layer: child, parent };
}
// 如果子图层也是组,继续递归查找
if (
child &&
(child.type === "group" || child.type === LayerType.GROUP) &&
child.children &&
Array.isArray(child.children)
) {
const result = findInChildLayers(child.children, layerId, child);
if (result) {
return result;
}
}
}
return null;
}
/**
* 简单查找图层(仅在顶级图层中查找,不递归子图层)
* @param {Array} layers 图层数组
* @param {string} layerId 要查找的图层ID
* @returns {Object|null} 找到的图层对象如果未找到返回null
*/
export function findLayer(layers, layerId) {
if (!layers || !Array.isArray(layers) || !layerId) {
return null;
}
return layers.find((layer) => layer && layer.id === layerId) || null;
}
/**
* 根据图层名称查找图层
* @param {Array} layers 图层数组
* @param {string} layerName 要查找的图层名称
* @param {boolean} recursive 是否递归查找子图层默认false
* @returns {Object|null} 找到的图层对象如果未找到返回null
*/
export function findLayerByName(layers, layerName, recursive = false) {
if (!layers || !Array.isArray(layers) || !layerName) {
return null;
}
for (const layer of layers) {
if (layer && layer.name === layerName) {
return layer;
}
// 如果需要递归查找且是组图层
if (
recursive &&
layer &&
(layer.type === "group" ||
layer.type === LayerType.GROUP ||
(layer.children && Array.isArray(layer.children)))
) {
const found = findLayerByName(layer.children, layerName, true);
if (found) {
return found;
}
}
}
return null;
}
/**
* 获取图层的完整路径(包含父图层信息)
* @param {Array} layers 图层数组
* @param {string} layerId 要查找的图层ID
* @returns {Array} 图层路径数组,从根图层到目标图层
*/
export function getLayerPath(layers, layerId) {
if (!layers || !Array.isArray(layers) || !layerId) {
return [];
}
function findPath(currentLayers, targetId, currentPath = []) {
for (const layer of currentLayers) {
if (!layer) continue;
const newPath = [...currentPath, layer];
if (layer.id === targetId) {
return newPath;
}
// 如果是组图层,递归查找
if (
layer.type === "group" ||
layer.type === LayerType.GROUP ||
(layer.children && Array.isArray(layer.children))
) {
const foundPath = findPath(layer.children, targetId, newPath);
if (foundPath.length > 0) {
return foundPath;
}
}
}
return [];
}
return findPath(layers, layerId);
}

View File

@@ -0,0 +1,204 @@
import { isArray } from "lodash-es";
/**
* 图层关联工具类
* 提供图层与画布对象关联管理的通用方法
*/
/**
* 构建单个图层与画布对象的关联关系
* @param {Object} layer 图层对象
* @param {Array} canvasObjects 画布对象数组
*/
export function buildLayerAssociations(layer, canvasObjects) {
if (!layer || !canvasObjects || !isArray(canvasObjects)) return;
// 处理单个fabricObject关联
if (layer.fabricObject) {
// 如果图层已经有关联的fabricObject确保它的layerId和layerName正确
layer.fabricObject =
canvasObjects.find((obj) => obj.id === layer.fabricObject.id) || null;
}
if (layer.clippingMask) {
// clippingMask 可能是一个fabricObject或组
layer.clippingMask =
canvasObjects.find((obj) => obj.id === layer.clippingMask.id) || null;
}
// 处理多个fabricObjects关联
if (layer.fabricObjects && isArray(layer.fabricObjects)) {
layer.fabricObjects = layer.fabricObjects
.map((fabricObject) => {
// 确保每个fabricObject的layerId和layerName正确
const obj = canvasObjects.find((obj) => obj.id === fabricObject.id);
if (obj) {
obj.layerId = layer.id; // 确保对象的layerId正确
obj.layerName = layer.name; // 确保对象的layerName正确
return obj;
}
return null; // 如果没有找到对象返回null
})
.filter((obj) => obj !== null); // 过滤掉null值
}
}
/**
* 恢复对象与图层的关联关系
* @param {Object} layerManager 图层管理器实例
* @param {Array} canvasObjects 画布对象数组
*/
export function restoreObjectLayerAssociations(layers, canvasObjects) {
if (!layers || !canvasObjects || !isArray(canvasObjects)) return;
layers.forEach((layer) => {
buildLayerAssociations(layer, canvasObjects);
// 处理子图层
if (layer?.children?.length) {
restoreObjectLayerAssociations(layer.children, canvasObjects);
}
});
}
/**
* 为画布对象设置图层信息
* @param {Object} fabricObject 画布对象
* @param {Object} layer 图层对象
*/
export function setObjectLayerInfo(fabricObject, layer) {
if (!fabricObject || !layer) return;
fabricObject.layerId = layer.id;
fabricObject.layerName = layer.name;
}
/**
* 清除画布对象的图层信息
* @param {Object} fabricObject 画布对象
*/
export function clearObjectLayerInfo(fabricObject) {
if (!fabricObject) return;
delete fabricObject.layerId;
delete fabricObject.layerName;
}
/**
* 验证图层关联关系的完整性
* @param {Object} layerManager 图层管理器实例
* @param {fabric.Canvas} canvas 画布实例
* @returns {Object} 验证结果 { valid: boolean, issues: Array }
*/
export function validateLayerAssociations(layers, canvasObjects) {
const issues = [];
// 检查画布对象是否都有对应的图层
canvasObjects.forEach((obj) => {
if (obj.layerId) {
const layer = layers.find((l) => l.id === obj.layerId);
if (!layer) {
issues.push({
type: "orphaned_object",
objectId: obj.id,
layerId: obj.layerId,
message: `对象 ${obj.id} 关联的图层 ${obj.layerId} 不存在`,
});
}
} else {
issues.push({
type: "missing_layer_id",
objectId: obj.id,
message: `对象 ${obj.id} 缺少图层ID关联`,
});
}
});
// 检查图层是否都有对应的画布对象
layers.forEach((layer) => {
if (layer.fabricObject && layer.fabricObject.id) {
const obj = canvasObjects.find((o) => o.id === layer.fabricObject.id);
if (!obj) {
issues.push({
type: "missing_object",
layerId: layer.id,
objectId: layer.fabricObject.id,
message: `图层 ${layer.id} 关联的对象 ${layer.fabricObject.id} 不存在于画布中`,
});
}
}
if (layer.fabricObjects && isArray(layer.fabricObjects)) {
layer.fabricObjects.forEach((fabricObj) => {
if (fabricObj.id) {
const obj = canvasObjects.find((o) => o.id === fabricObj.id);
if (!obj) {
issues.push({
type: "missing_object",
layerId: layer.id,
objectId: fabricObj.id,
message: `图层 ${layer.id} 关联的对象 ${fabricObj.id} 不存在于画布中`,
});
}
}
});
}
});
return {
valid: issues.length === 0,
issues,
};
}
/**
* 简化layers对象属性只保留必要的属性
* @param {Array} layers 图层数组 simplifyLayers(JSON.parse(JSON.stringify(this.layers.value)))
*/
export function simplifyLayers(layers) {
if (!layers || !isArray(layers)) {
console.warn("simplifyLayers 请传入有效的图层数组:", layers);
return [];
}
layers.forEach((layer) => {
// 处理图层遮罩
// 如果clippingMask是一个fabricObject或组确保它的id正确 // 因为是fabric对象所以没办法直接获取id只能通过序列化获取
if (layer.clippingMask) {
layer.clippingMask = layer.clippingMask?.id || null;
}
// 处理单个fabricObject
if (layer.fabricObject) {
layer.fabricObject = layer.fabricObject?.id;
}
// 处理多个fabricObjects
if (layer.fabricObjects && isArray(layer.fabricObjects)) {
layer.fabricObjects = layer.fabricObjects
.map((fabricObject) => {
return fabricObject?.id || null; // 确保每个fabricObject都能转换为对象
})
.filter((obj) => obj !== null);
}
// 处理子图层
if (layer.children && isArray(layer.children)) {
layer.children = simplifyLayers(layer.children);
}
// 只保留必要的属性
layer = {
id: layer.id,
name: layer.name,
visible: layer.visible,
locked: layer.locked,
opacity: layer.opacity,
clippingMask: layer.clippingMask || null,
fabricObject: layer.fabricObject || null,
fabricObjects: layer.fabricObjects || [],
children: layer.children || [],
isBackground: layer.isBackground || false,
ifFixed: layer.ifFixed || false,
};
});
return layers;
}