feat: 裁剪组裁剪跟随选择组移动

This commit is contained in:
bighuixiang
2025-07-14 01:00:23 +08:00
parent 96e13cb22a
commit 24e9ba8ae5
80 changed files with 2052 additions and 4292 deletions

View File

@@ -51,24 +51,16 @@ import {
} from "../commands/RasterizeLayerCommand";
// 导入图层排序相关类和混入
import {
LayerSort,
createLayerSort,
LayerSortMixin,
LayerSortUtils,
} from "../utils/LayerSort";
import { LayerSort, createLayerSort, LayerSortMixin, LayerSortUtils } from "../utils/LayerSort";
import CanvasConfig from "../config/canvasConfig";
import { isBoolean, template } from "lodash-es";
import {
findObjectById,
generateId,
optimizeCanvasRendering,
} from "../utils/helper";
import { findObjectById, generateId, optimizeCanvasRendering } from "../utils/helper";
import { message } from "ant-design-vue";
import { fabric } from "fabric-with-all";
import { getOriginObjectInfo } from "../utils/layerUtils";
import { restoreFabricObject } from "../utils/objectHelper";
import { UpdateGroupMaskPositionCommand } from "../commands/UpdateGroupMaskPositionCommand";
/**
* 图层管理器 - 负责管理画布上的所有图层
@@ -219,11 +211,7 @@ export class LayerManager {
// 基于 fabric-with-erasing 库的 erasable 属性设置擦除权限
// 只有活动图层、可见、非锁定、非背景、非固定图层的对象才可擦除
obj.erasable =
isInActiveLayer &&
layer.visible &&
!layer.locked &&
!layer.isBackground &&
!layer.isFixed;
isInActiveLayer && layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
// 图层状态决定交互性
if (layer.isBackground || obj.isBackground || layer.isFixed) {
@@ -270,11 +258,7 @@ export class LayerManager {
if (this.isRedGreenMode) {
// 红绿图模式下 所有普通图层都可擦除
obj.erasable =
layer.visible &&
!layer.locked &&
!layer.isBackground &&
!layer.isFixed;
obj.erasable = layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
}
// 平移模式下,禁用多选和擦除
@@ -325,9 +309,7 @@ export class LayerManager {
// 设置子图层对象的交互性
layer?.childLayer?.forEach(async (childLayer) => {
const childObj = this.canvas
.getObjects()
.find((o) => o.layerId === childLayer.id);
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
if (childObj) {
await this._setObjectInteractivity(childObj, childLayer, editorMode);
}
@@ -339,10 +321,7 @@ export class LayerManager {
let clippingMaskFabricObject = null;
if (layer.clippingMask) {
// 反序列化 clippingMask
clippingMaskFabricObject = await restoreFabricObject(
layer.clippingMask,
this.canvas
);
clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas);
clippingMaskFabricObject.clipPath = null;
clippingMaskFabricObject.set({
@@ -354,9 +333,7 @@ export class LayerManager {
// 如果是组图层 则给所有子对象设置裁剪对象
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
layer.children.forEach((childLayer) => {
const childObj = this.canvas
.getObjects()
.find((o) => o.layerId === childLayer.id);
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
if (childObj) {
childObj.clipPath = clippingMaskFabricObject;
childObj.dirty = true; // 标记为脏对象
@@ -365,9 +342,7 @@ export class LayerManager {
});
} else {
layer.fabricObjects?.forEach((obj) => {
const fabricObject = this.canvas
.getObjects()
.find((o) => o.id === obj.id);
const fabricObject = this.canvas.getObjects().find((o) => o.id === obj.id);
if (fabricObject) {
fabricObject.clipPath = clippingMaskFabricObject;
fabricObject.dirty = true; // 标记为脏对象
@@ -447,9 +422,7 @@ export class LayerManager {
*/
createBackgroundLayer(name = "背景") {
// 检查是否已有背景图层
const hasBackgroundLayer = this.layers.value.some(
(layer) => layer.isBackground
);
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
if (hasBackgroundLayer) {
console.warn("已存在背景层,不再创建新的背景层");
@@ -502,9 +475,7 @@ export class LayerManager {
}
// 生成唯一ID
const layerId = `fixed_layer_${Date.now()}_${Math.floor(
Math.random() * 1000
)}`;
const layerId = `fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
// 创建固定图层
const fixedLayer = createFixedLayer({
@@ -564,9 +535,7 @@ export class LayerManager {
await this.createLayer("图层 1");
} else {
// 检查是否已有背景层
const hasBackgroundLayer = this.layers.value.some(
(layer) => layer.isBackground
);
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
if (!hasBackgroundLayer) {
this.createBackgroundLayer();
@@ -612,10 +581,7 @@ export class LayerManager {
}
// 验证目标图层是否存在
const { layer: targetLayer } = findLayerRecursively(
this.layers.value,
targetLayerId
);
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
if (!targetLayer) {
console.error(`目标图层 ${targetLayerId} 不存在`);
return null;
@@ -669,8 +635,7 @@ export class LayerManager {
*/
removeObjectFromLayer(objectOrId) {
// 获取对象ID
const objectId =
typeof objectOrId === "string" ? objectOrId : objectOrId.id;
const objectId = typeof objectOrId === "string" ? objectOrId : objectOrId.id;
if (!objectId) {
console.error("无效的对象ID");
@@ -729,9 +694,7 @@ export class LayerManager {
*/
getActiveLayer() {
if (!this.activeLayerId.value) {
console.warn(
"没有活动图层ID无法获取活动图层 ==== 默认设置第一个图层为活动图层"
);
console.warn("没有活动图层ID无法获取活动图层 ==== 默认设置第一个图层为活动图层");
this.activeLayerId.value = this.layers.value[0]?.id || null;
}
@@ -836,13 +799,8 @@ export class LayerManager {
}
const tempFabricObject =
fabricObject?.toObject?.([
"id",
"layerId",
"layerName",
"isBackgroud",
"isFixed",
]) || fabricObject;
fabricObject?.toObject?.(["id", "layerId", "layerName", "isBackgroud", "isFixed"]) ||
fabricObject;
if (layer.isFixed || layer.isBackground) {
layer.fabricObject = tempFabricObject;
} else {
@@ -868,9 +826,7 @@ export class LayerManager {
// 如果是背景层或固定层,不允许移动
if (layer && (layer.isBackground || layer.isFixed)) {
console.warn(
layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动")
);
console.warn(layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动"));
return false;
}
@@ -1000,6 +956,9 @@ export class LayerManager {
// 设置活动选择组的属性
this.canvas.setActiveObject(activeSelection);
// 为活动选择组添加移动事件监听器,用于同步更新遮罩位置
this._setupGroupMaskMovementSync(activeSelection, layer);
activeSelection = null; // 清理引用,避免内存泄漏
// 确保选择组正确渲染
// activeSelection.setCoords();
@@ -1234,11 +1193,7 @@ export class LayerManager {
async ungroupLayers(groupId) {
// 查找组图层
const groupLayer = this.layers.value.find((l) => l.id === groupId);
if (
!groupLayer ||
!groupLayer.children ||
groupLayer.children.length === 0
) {
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
console.error(`${groupId} 不是有效的组图层或不包含子图层`);
return null;
}
@@ -1295,9 +1250,7 @@ export class LayerManager {
*/
resizeCanvasWithScale(width, height, options = {}) {
// 检查是否有除背景层外的其他元素
const hasOtherElements = this.canvas
.getObjects()
.some((obj) => !obj.isBackground);
const hasOtherElements = this.canvas.getObjects().some((obj) => !obj.isBackground);
if (hasOtherElements) {
// 有其他元素时使用带缩放的命令
@@ -1366,9 +1319,7 @@ export class LayerManager {
if (!this.canvas) return;
// 获取画布上的所有对象
const canvasObjects = [
...this.canvas.getObjects(["id", "layerId", "layerName"]),
];
const canvasObjects = [...this.canvas.getObjects(["id", "layerId", "layerName"])];
// 清空画布
this.canvas.clear();
@@ -1383,19 +1334,13 @@ export class LayerManager {
if (layer.isBackground && layer.fabricObject) {
// 背景图层
const originalObj = canvasObjects.find(
(o) => o.id === layer.fabricObject.id
);
const originalObj = canvasObjects.find((o) => o.id === layer.fabricObject.id);
if (originalObj) {
this.canvas.add(originalObj);
} else {
this.canvas.add(layer.fabricObject);
}
} else if (
layer.isFixed &&
layer.fabricObjects &&
layer.fabricObjects.length > 0
) {
} else if (layer.isFixed && layer.fabricObjects && layer.fabricObjects.length > 0) {
// 固定图层
layer.fabricObjects.forEach((obj) => {
const originalObj = canvasObjects.find((o) => o.id === obj.id);
@@ -1405,10 +1350,7 @@ export class LayerManager {
this.canvas.add(obj);
}
});
} else if (
Array.isArray(layer.fabricObjects) &&
layer.fabricObjects.length > 0
) {
} else if (Array.isArray(layer.fabricObjects) && layer.fabricObjects.length > 0) {
// 普通图层添加所有fabricObjects
layer.fabricObjects.forEach((obj) => {
const originalObj = canvasObjects.find((o) => o.id === obj.id);
@@ -1437,9 +1379,7 @@ export class LayerManager {
if (layer.isBackground) {
// 背景图层处理
if (layer.fabricObject) {
const existsOnCanvas = canvasObjects.some(
(obj) => obj.id === layer.fabricObject.id
);
const existsOnCanvas = canvasObjects.some((obj) => obj.id === layer.fabricObject.id);
if (!existsOnCanvas) {
this.canvas.add(layer.fabricObject);
}
@@ -1549,9 +1489,7 @@ export class LayerManager {
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
layerCopy.serializedObjects = layer.fabricObjects
.map((obj) =>
typeof obj.toObject === "function"
? obj.toObject(["id", "layerId", "layerName"])
: null
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
)
.filter(Boolean);
}
@@ -1596,9 +1534,7 @@ export class LayerManager {
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
layerCopy.serializedObjects = layer.fabricObjects
.map((obj) =>
typeof obj.toObject === "function"
? obj.toObject(["id", "layerId", "layerName"])
: null
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
)
.filter(Boolean);
}
@@ -1719,8 +1655,7 @@ export class LayerManager {
// 查找第一个非背景、非锁定的图层,排除指定的图层
return (
this.layers.value.find(
(layer) =>
layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
(layer) => layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
) || null
);
}
@@ -1855,9 +1790,7 @@ export class LayerManager {
* @param {string} backgroundColor 背景颜色
*/
updateBackgroundColor(backgroundColor, options = {}) {
const backgroundLayer = this.layers.value.find(
(layer) => layer.isBackground
);
const backgroundLayer = this.layers.value.find((layer) => layer.isBackground);
if (!backgroundLayer) {
console.warn("没有找到背景图层");
@@ -2057,8 +1990,7 @@ export class LayerManager {
if (!this.canvas || !textObject) return null;
// 确保对象有ID
textObject.id =
textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
textObject.id = textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
// 创建文本图层
const layerName = options.name || "文本图层";
@@ -2075,9 +2007,7 @@ export class LayerManager {
overline: options.overline || textObject.overline || false,
fill: options.fill || textObject.fill || "#000000",
textBackgroundColor:
options.textBackgroundColor ||
textObject.textBackgroundColor ||
"transparent",
options.textBackgroundColor || textObject.textBackgroundColor || "transparent",
lineHeight: options.lineHeight || textObject.lineHeight || 1.16,
charSpacing: options.charSpacing || textObject.charSpacing || 0,
},
@@ -2088,9 +2018,7 @@ export class LayerManager {
textObject.layerName = layerName;
// 添加到画布,如果还未添加
const isOnCanvas = this.canvas
.getObjects()
.some((obj) => obj.id === textObject.id);
const isOnCanvas = this.canvas.getObjects().some((obj) => obj.id === textObject.id);
if (!isOnCanvas) {
this.canvas.add(textObject);
}
@@ -2099,9 +2027,7 @@ export class LayerManager {
const layer = this.getLayerById(layerId);
if (layer) {
layer.fabricObjects = layer.fabricObjects || [];
layer.fabricObjects.push(
textObject.toObject(["id", "layerId", "layerName"])
);
layer.fabricObjects.push(textObject.toObject(["id", "layerId", "layerName"]));
}
// 设置此图层为活动图层
@@ -2134,9 +2060,7 @@ export class LayerManager {
// 检查普通图层
if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) {
const foundObject = layer.fabricObjects.find(
(obj) => obj.id === fabricObject.id
);
const foundObject = layer.fabricObjects.find((obj) => obj.id === fabricObject.id);
if (foundObject) {
return layer;
}
@@ -2217,12 +2141,7 @@ export class LayerManager {
* @returns {boolean} 是否排序成功
*/
reorderChildLayers(parentId, oldIndex, newIndex, layerId) {
return this.layerSort?.reorderChildLayers(
parentId,
oldIndex,
newIndex,
layerId
);
return this.layerSort?.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
}
/**
@@ -2403,19 +2322,13 @@ export class LayerManager {
const layersCount = this.layers?.value?.length || 0;
const shouldUseAsync =
async !== null
? async
: LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
async !== null ? async : LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
if (shouldUseAsync) {
console.log(
`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
);
console.log(`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
return this.layerSort.rearrangeObjectsAsync();
} else {
console.log(
`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
);
console.log(`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
this.layerSort.rearrangeObjects();
}
}
@@ -2524,10 +2437,7 @@ export class LayerManager {
moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) {
if (!this.layerSort) {
console.warn("图层排序工具未初始化,使用基础移动方法");
return this.moveLayer(
layerId,
newIndex > this.getLayerIndex(layerId) ? "down" : "up"
);
return this.moveLayer(layerId, newIndex > this.getLayerIndex(layerId) ? "down" : "up");
}
const result = this.layerSort.moveLayerToIndex({
@@ -2538,9 +2448,7 @@ export class LayerManager {
});
if (result) {
console.log(
`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`
);
console.log(`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`);
// 更新对象交互性
// this.updateLayersObjectsInteractivity();
}
@@ -2584,9 +2492,7 @@ export class LayerManager {
return -1;
}
let layerIndex = this.layers.value.findIndex(
(layer) => layer.id === layerId
);
let layerIndex = this.layers.value.findIndex((layer) => layer.id === layerId);
if (layerIndex >= 0) return layerIndex;
// 如果未找到,尝试在子图层中查找
const { parent } = findLayerRecursively(this.layers.value, layerId);
@@ -2617,9 +2523,7 @@ export class LayerManager {
const result = this.layerSort.reorderLayers(oldIndex, newIndex, layerId);
if (result) {
console.log(
`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`
);
console.log(`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`);
// 更新对象交互性
this.updateLayersObjectsInteractivity();
}
@@ -2636,12 +2540,7 @@ export class LayerManager {
* @returns {boolean} 是否排序成功
*/
advancedReorderChildLayers(parentId, oldIndex, newIndex, layerId) {
const result = this.reorderChildLayers(
parentId,
oldIndex,
newIndex,
layerId
);
const result = this.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
if (result) {
console.log(
@@ -2758,11 +2657,7 @@ export class LayerManager {
async mergeGroupLayers(groupId) {
// 查找组图层
const groupLayer = this.layers.value.find((l) => l.id === groupId);
if (
!groupLayer ||
!groupLayer.children ||
groupLayer.children.length === 0
) {
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
console.warn($t("找不到有效的组图层或组图层为空"));
return [];
}
@@ -2803,10 +2698,7 @@ export class LayerManager {
// 查找目标图层
// const targetLayer = this.getLayerById(targetLayerId);
const { layer: targetLayer } = findLayerRecursively(
this.layers.value,
targetLayerId
);
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
if (!targetLayer) {
console.error($t("图层不存在", { layerId: targetLayerId }));
@@ -2864,9 +2756,7 @@ export class LayerManager {
return (
layer.children &&
layer.children.length > 0 &&
layer.children.some(
(child) => child.fabricObjects && child.fabricObjects.length > 0
)
layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0)
);
} else {
// 普通图层:检查是否有对象
@@ -2888,10 +2778,7 @@ export class LayerManager {
// 查找目标图层
// const targetLayer = this.getLayerById(targetLayerId);
const { layer: targetLayer } = findLayerRecursively(
this.layers.value,
targetLayerId
);
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
if (!targetLayer) {
console.error($t("图层不存在", { layerId: targetLayerId }));
@@ -2926,15 +2813,162 @@ export class LayerManager {
}
/**
* 更新图层缩略图
* @param {string} layerId 图层ID
* 为组图层的活动选择组设置遮罩移动同步(修复版)
* @param {fabric.ActiveSelection} activeSelection 活动选择组
* @param {Object} layer 组图层对象
* @private
*/
_updateLayerThumbnail(layerId) {
if (this.canvas && this.canvas.thumbnailManager) {
setTimeout(() => {
this.canvas.thumbnailManager.generateLayerThumbnail(layerId);
}, 100);
_setupGroupMaskMovementSync(activeSelection, layer) {
if (!activeSelection || !layer || !layer.clippingMask) {
return;
}
// 记录初始位置
let initialLeft = activeSelection.left;
let initialTop = activeSelection.top;
// 记录遮罩初始位置
let maskInitialLeft = layer.clippingMask.left || 0;
let maskInitialTop = layer.clippingMask.top || 0;
// 用于节流和状态管理的变量
let isUpdating = false;
let lastUpdateTime = 0;
let hasMoved = false; // 追踪是否实际发生了移动
const UPDATE_THRESHOLD = 16; // 约60fps
// 移动开始事件处理
const handleMovingStart = (e) => {
if (e.target === activeSelection) {
hasMoved = false; // 重置移动状态
console.log("🎯 开始移动组选择对象");
// 记录遮罩初始位置
console.log(
"🖼️ 记录遮罩初始位置",
`${layer.clippingMask.left || 0}, ${layer.clippingMask.top || 0}`
);
// 记录初始位置
initialLeft = activeSelection.left;
initialTop = activeSelection.top;
maskInitialLeft = layer.clippingMask.left || 0;
maskInitialTop = layer.clippingMask.top || 0;
}
};
// 移动中事件处理函数(带节流)
const handleMoving = (e) => {
const target = e.target;
if (target === activeSelection) {
hasMoved = true; // 标记发生了移动
const now = Date.now();
// 节流处理,避免过于频繁的更新
if (now - lastUpdateTime < UPDATE_THRESHOLD) {
return;
}
if (isUpdating) {
return;
}
isUpdating = true;
lastUpdateTime = now;
// 使用 requestAnimationFrame 优化渲染
requestAnimationFrame(() => {
try {
// 计算移动距离
const deltaX = target.left - initialLeft;
const deltaY = target.top - initialTop;
// 创建更新遮罩位置的命令
const command = new UpdateGroupMaskPositionCommand({
canvas: this.canvas,
layerManager: this,
layers: this.layers,
layerId: layer.id,
deltaX: deltaX,
deltaY: deltaY,
maskInitialLeft: maskInitialLeft,
maskInitialTop: maskInitialTop,
isExecuteRealtime: true,
});
// 执行实时更新
command.executeRealtime();
} finally {
isUpdating = false;
}
});
}
};
// 修改事件处理函数 - 使用 object:modified 替代 object:moved
const handleModified = (e) => {
const target = e.target;
if (target === activeSelection && hasMoved) {
console.log("✅ 组选择对象移动完成");
// 计算最终移动距离
const deltaX = target.left - initialLeft;
const deltaY = target.top - initialTop;
// 如果有实际移动,创建可撤销的命令
if (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1) {
const command = new UpdateGroupMaskPositionCommand({
canvas: this.canvas,
layers: this.layers,
layerManager: this,
layerId: layer.id,
deltaX: deltaX,
deltaY: deltaY,
maskInitialLeft: maskInitialLeft,
maskInitialTop: maskInitialTop,
activeSelection,
});
// 执行可撤销的命令
if (this.commandManager) {
this.commandManager.execute(command);
} else {
command.execute();
}
}
hasMoved = false; // 重置移动状态
}
};
// 鼠标抬起事件处理 - 备用方案
const handleMouseUp = (e) => {
if (hasMoved && this.canvas.getActiveObject() === activeSelection) {
console.log("🖱️ 鼠标抬起 - 备用移动完成处理");
handleModified(e);
}
};
// 清理事件监听器的函数
const cleanup = () => {
this.canvas.off("object:moving", handleMoving);
this.canvas.off("object:modified", handleModified);
this.canvas.off("mouse:down", handleMovingStart);
this.canvas.off("mouse:up", handleMouseUp);
this.canvas.off("selection:cleared", cleanup);
this.canvas.off("selection:updated", cleanup);
console.log("🧹 清理组遮罩移动同步事件监听器");
};
// 绑定事件监听器
this.canvas.on("mouse:down", handleMovingStart);
this.canvas.on("object:moving", handleMoving);
this.canvas.on("object:modified", handleModified); // 使用 modified 替代 moved
this.canvas.on("mouse:up", handleMouseUp); // 备用方案
// 当选择被清除或更新时清理事件监听器
this.canvas.on("selection:cleared", cleanup);
this.canvas.on("selection:updated", cleanup);
console.log("🎨 已设置组遮罩移动同步 - 使用 object:modified 事件");
}
}