From a10f07a772dcee8d05d32e1effeb97ad253b3ee6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E=E5=BF=97=E9=B9=8F?= <2916022834@qq.com>
Date: Fri, 7 Nov 2025 16:52:42 +0800
Subject: [PATCH] =?UTF-8?q?=E7=94=BB=E5=B8=83=E5=BC=80=E5=8F=91=E5=90=88?=
=?UTF-8?q?=E5=B9=B6=EF=BC=9A=E9=80=89=E6=8B=A9=E7=8A=B6=E6=80=81=E6=B6=88?=
=?UTF-8?q?=E5=A4=B1=EF=BC=8C=E5=A1=AB=E5=85=85=E8=83=8C=E6=99=AF=E5=AE=9E?=
=?UTF-8?q?=E6=97=B6=E6=9B=B4=E6=96=B0=EF=BC=8C=E5=9B=BE=E5=B1=82=E5=B1=82?=
=?UTF-8?q?=E7=BA=A7=E5=85=B3=E7=B3=BB=EF=BC=8C=E6=96=B0=E5=A2=9E=E7=94=BB?=
=?UTF-8?q?=E5=B8=83=E5=90=8D=E5=AD=97=E9=80=92=E5=A2=9E?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../CanvasEditor/commands/LayerCommands.js | 4 +-
.../CanvasEditor/commands/LiquifyCommands.js | 242 ++++---
.../commands/ObjectLayerCommands.js | 2 +
.../commands/RasterizeLayerCommand.js | 3 +-
.../CanvasEditor/commands/TextCommands.js | 3 +
.../components/LayersPanel/LayersPanel.vue | 4 +-
.../components/SelectMenuPanel.vue | 609 ++++++++++++++++++
.../CanvasEditor/components/ToolsSidebar.vue | 9 +-
.../CanvasEditor/fabric-canvas-events.text | 23 +
src/component/Canvas/CanvasEditor/index.vue | 28 +-
.../CanvasEditor/managers/CanvasManager.js | 4 +-
.../CanvasEditor/managers/LayerManager.js | 19 +
.../managers/events/CanvasEventManager.js | 7 +-
13 files changed, 850 insertions(+), 107 deletions(-)
create mode 100644 src/component/Canvas/CanvasEditor/components/SelectMenuPanel.vue
create mode 100644 src/component/Canvas/CanvasEditor/fabric-canvas-events.text
diff --git a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js
index b6ff2272..66880184 100644
--- a/src/component/Canvas/CanvasEditor/commands/LayerCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/LayerCommands.js
@@ -2058,7 +2058,7 @@ export class LayerObjectsToGroupCommand extends Command {
}
try {
- await optimizeCanvasRendering(this.canvas, () => {
+ await optimizeCanvasRendering(this.canvas, async () => {
if (existingGroup) {
// 向现有组添加对象
this._addObjectsToExistingGroup(existingGroup, newObjectsToAdd);
@@ -2069,8 +2069,8 @@ export class LayerObjectsToGroupCommand extends Command {
this._createNewGroupWithAllObjects(newObjectsToAdd);
this.groupObjectId = this.newGroupId;
this.wasGroupCreated = true;
+ await this.layerManager?.layerSort?.rearrangeObjects();
}
-
// 更新交互性
this.layerManager?.updateLayersObjectsInteractivity?.(false).then(()=>{
// 更新缩略图
diff --git a/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js b/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js
index d6106c8b..2ecda7b1 100644
--- a/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/LiquifyCommands.js
@@ -1,5 +1,7 @@
import { Command, FunctionCommand } from "./Command";
import { getLiquifyReferenceManager } from "../managers/LiquifyReferenceManager";
+import { optimizeCanvasRendering } from "../utils/helper";
+import { fabric } from "fabric-with-all";
/**
* 液化命令基类
@@ -59,9 +61,7 @@ export class LiquifyCommand extends Command {
// 更新画布上的对象
await this._updateObjectWithResult();
-
- // 刷新Canvas
- this.canvas.renderAll();
+ // 注意:_updateObjectWithResult 内部已使用 optimizeCanvasRendering,会自动渲染
return this.resultData;
}
@@ -130,6 +130,7 @@ export class LiquifyCommand extends Command {
/**
* 使用变形结果更新对象
+ * 优化:直接使用 setElement 更新现有对象,避免创建新对象和替换操作
* @private
*/
async _updateObjectWithResult() {
@@ -142,48 +143,103 @@ export class LiquifyCommand extends Command {
const tempCtx = tempCanvas.getContext("2d");
tempCtx.putImageData(this.resultData, 0, 0);
console.log("临时Canvas创建成功 _updateObjectWithResult", this.resultData);
- // 更新Fabric图像
- await new Promise((resolve) => {
- fabric.Image.fromURL(tempCanvas.toDataURL(), (img) => {
- // 保留原对象的属性
- img.set({
- left: this.targetObject.left,
- top: this.targetObject.top,
- scaleX: this.targetObject.scaleX,
- scaleY: this.targetObject.scaleY,
- angle: this.targetObject.angle,
- flipX: this.targetObject.flipX,
- flipY: this.targetObject.flipY,
- opacity: this.targetObject.opacity,
- });
-
- // 替换Canvas上的对象
- const index = this.canvas.getObjects().indexOf(this.targetObject);
- if (index !== -1) {
- this.canvas.remove(this.targetObject);
- this.canvas.insertAt(img, index);
- this.targetObject = img;
- }
-
- // 确保图层引用更新
- const layer = this.layerManager.getLayerById(this.targetLayerId);
- if (layer) {
- if (layer.type === "background" && layer.fabricObject === this.targetObject) {
- layer.fabricObject = img;
- } else if (layer.fabricObjects) {
- const objIndex = layer.fabricObjects.indexOf(this.targetObject);
- if (objIndex !== -1) {
- layer.fabricObjects[objIndex] = img;
- }
- }
- }
-
- resolve();
- });
+
+ // 使用优化渲染工具,避免图层闪烁
+ await optimizeCanvasRendering(this.canvas, async () => {
+ // 预加载图像元素,确保完全加载后再更新
+ await this._updateObjectElementDirectly(tempCanvas.toDataURL());
});
return true;
}
+
+ /**
+ * 直接更新对象的图像元素,避免对象替换造成的图层闪烁
+ * @param {String} imageDataURL 图像数据的 DataURL
+ * @private
+ */
+ async _updateObjectElementDirectly(imageDataURL) {
+ return new Promise((resolve, reject) => {
+ // 创建 HTMLImageElement 并预加载
+ const imgElement = new Image();
+
+ imgElement.onload = () => {
+ try {
+ // 保存当前对象状态(非图像属性)
+ const currentState = {
+ left: this.targetObject.left,
+ top: this.targetObject.top,
+ scaleX: this.targetObject.scaleX,
+ scaleY: this.targetObject.scaleY,
+ angle: this.targetObject.angle,
+ flipX: this.targetObject.flipX,
+ flipY: this.targetObject.flipY,
+ opacity: this.targetObject.opacity,
+ };
+
+ // 直接更新现有对象的图像元素,保持对象引用不变
+ if (this.targetObject.setElement) {
+ this.targetObject.setElement(imgElement);
+ } else if (this.targetObject._element) {
+ this.targetObject._element = imgElement;
+ this.targetObject._originalElement = imgElement;
+ }
+
+ // 恢复对象属性(setElement 可能会重置一些属性)
+ this.targetObject.set(currentState);
+
+ // 标记对象需要重新渲染
+ this.targetObject.dirty = true;
+ this.targetObject.setCoords();
+
+ resolve();
+ } catch (error) {
+ console.error("更新对象元素失败:", error);
+ reject(error);
+ }
+ };
+
+ imgElement.onerror = () => {
+ reject(new Error("图像加载失败"));
+ };
+
+ // 开始加载图像
+ imgElement.src = imageDataURL;
+ });
+ }
+
+ /**
+ * 安全地替换对象,避免图层闪烁(保留作为备用方法)
+ * @param {Object} newImg 新的fabric图像对象
+ * @private
+ */
+ _replaceObjectSafely(newImg) {
+ // 保存旧对象引用,用于更新图层引用
+ const oldObject = this.targetObject;
+
+ // 替换Canvas上的对象
+ const index = this.canvas.getObjects().indexOf(oldObject);
+ if (index !== -1) {
+ // 在禁用自动渲染的情况下,先插入新对象,再移除旧对象
+ // 这样可以避免中间出现空白(因为renderOnAddRemove已被禁用)
+ this.canvas.insertAt(newImg, index);
+ this.canvas.remove(oldObject);
+ this.targetObject = newImg;
+ }
+
+ // 确保图层引用更新
+ const layer = this.layerManager.getLayerById(this.targetLayerId);
+ if (layer) {
+ if (layer.type === "background" && layer.fabricObject === oldObject) {
+ layer.fabricObject = newImg;
+ } else if (layer.fabricObjects) {
+ const objIndex = layer.fabricObjects.indexOf(oldObject);
+ if (objIndex !== -1) {
+ layer.fabricObjects[objIndex] = newImg;
+ }
+ }
+ }
+ }
}
/**
@@ -271,8 +327,8 @@ export class LiquifyDeformCommand extends LiquifyCommand {
// 应用变形结果
await this._updateObjectWithImageData(this.afterData);
+ // 注意:_updateObjectWithImageData 内部已使用 optimizeCanvasRendering,会自动渲染
- this.canvas.renderAll();
return this.afterData;
}
@@ -283,70 +339,70 @@ export class LiquifyDeformCommand extends LiquifyCommand {
// 恢复到变形前的状态
await this._updateObjectWithImageData(this.beforeData);
+ // 注意:_updateObjectWithImageData 内部已使用 optimizeCanvasRendering,会自动渲染
- this.canvas.renderAll();
return true;
}
/**
* 使用图像数据更新对象
+ * 优化:直接使用 setElement 更新现有对象,避免创建新对象和替换操作
* @param {ImageData} imageData 图像数据
* @private
*/
async _updateObjectWithImageData(imageData) {
- return new Promise((resolve) => {
- // 创建临时canvas
- const tempCanvas = document.createElement("canvas");
- tempCanvas.width = imageData.width;
- tempCanvas.height = imageData.height;
- const tempCtx = tempCanvas.getContext("2d");
- tempCtx.putImageData(imageData, 0, 0);
+ // 创建临时canvas
+ const tempCanvas = document.createElement("canvas");
+ tempCanvas.width = imageData.width;
+ tempCanvas.height = imageData.height;
+ const tempCtx = tempCanvas.getContext("2d");
+ tempCtx.putImageData(imageData, 0, 0);
- // 从canvas创建新的fabric图像
- fabric.Image.fromURL(tempCanvas.toDataURL(), (img) => {
- // 保留原对象的变换属性
- img.set({
- left: this.targetObject.left,
- top: this.targetObject.top,
- scaleX: this.targetObject.scaleX,
- scaleY: this.targetObject.scaleY,
- angle: this.targetObject.angle,
- flipX: this.targetObject.flipX,
- flipY: this.targetObject.flipY,
- opacity: this.targetObject.opacity,
- });
-
- // 替换canvas上的对象
- const index = this.canvas.getObjects().indexOf(this.targetObject);
- if (index !== -1) {
- this.canvas.remove(this.targetObject);
- this.canvas.insertAt(img, index);
- this.targetObject = img;
-
- // 更新图层引用
- const layer = this.layerManager.getLayerById(this.targetLayerId);
- if (layer) {
- if (
- layer.type === "background" &&
- (layer.fabricObject === this.savedState?.originalObject ||
- layer.fabricObject === this.targetObject)
- ) {
- layer.fabricObject = img;
- } else if (layer.fabricObjects) {
- const objIndex = layer.fabricObjects.findIndex(
- (obj) => obj === this.savedState?.originalObject || obj === this.targetObject
- );
- if (objIndex !== -1) {
- layer.fabricObjects[objIndex] = img;
- }
- }
- }
- }
-
- resolve();
- });
+ // 使用优化渲染工具,避免图层闪烁
+ await optimizeCanvasRendering(this.canvas, async () => {
+ // 直接更新对象元素,避免对象替换
+ await this._updateObjectElementDirectly(tempCanvas.toDataURL());
});
}
+
+ /**
+ * 安全地替换变形对象,避免图层闪烁(保留作为备用方法)
+ * @param {Object} newImg 新的fabric图像对象
+ * @private
+ */
+ _replaceDeformObjectSafely(newImg) {
+ // 保存旧对象引用,用于更新图层引用
+ const oldObject = this.targetObject;
+
+ // 替换Canvas上的对象
+ const index = this.canvas.getObjects().indexOf(oldObject);
+ if (index !== -1) {
+ // 在禁用自动渲染的情况下,先插入新对象,再移除旧对象
+ // 这样可以避免中间出现空白
+ this.canvas.insertAt(newImg, index);
+ this.canvas.remove(oldObject);
+ this.targetObject = newImg;
+ }
+
+ // 更新图层引用
+ const layer = this.layerManager.getLayerById(this.targetLayerId);
+ if (layer) {
+ if (
+ layer.type === "background" &&
+ (layer.fabricObject === this.savedState?.originalObject ||
+ layer.fabricObject === oldObject)
+ ) {
+ layer.fabricObject = newImg;
+ } else if (layer.fabricObjects) {
+ const objIndex = layer.fabricObjects.findIndex(
+ (obj) => obj === this.savedState?.originalObject || obj === oldObject
+ );
+ if (objIndex !== -1) {
+ layer.fabricObjects[objIndex] = newImg;
+ }
+ }
+ }
+ }
}
/**
diff --git a/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js b/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js
index d9b60f4a..1243295a 100644
--- a/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/ObjectLayerCommands.js
@@ -255,6 +255,8 @@ export class AddObjectToLayerCommand extends Command {
);
// 标记为非首次执行
this.isFirstExecution = false;
+ // 重新排序图层对象
+ await this.layerManager?.layerSort?.rearrangeObjects();
console.log(
`✅ 对象已添加到图层 "${layer.name}",位置: (${this.fabricObject.left}, ${this.fabricObject.top})`
);
diff --git a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js
index 6b180729..7088029a 100644
--- a/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js
+++ b/src/component/Canvas/CanvasEditor/commands/RasterizeLayerCommand.js
@@ -359,7 +359,8 @@ export class RasterizeLayerCommand extends Command {
// 设置为活动图层
this.activeLayerId.value = this.rasterizedLayerId;
-
+ // 重新排序图层对象
+ await this.layerManager?.layerSort?.rearrangeObjects();
await this.layerManager?.updateLayersObjectsInteractivity(false);
console.log(`🎨 组合图层 ${this.rasterizedLayer.name} 创建完成`);
diff --git a/src/component/Canvas/CanvasEditor/commands/TextCommands.js b/src/component/Canvas/CanvasEditor/commands/TextCommands.js
index 4b02f510..c9c464ef 100644
--- a/src/component/Canvas/CanvasEditor/commands/TextCommands.js
+++ b/src/component/Canvas/CanvasEditor/commands/TextCommands.js
@@ -409,6 +409,9 @@ export class CreateTextCommand extends Command {
// 现在可以安全地设置为活动图层
this.layerManager.setActiveLayer(this.layerId);
+ // 重新排序图层对象
+ await this.layerManager?.layerSort?.rearrangeObjects();
+
// 更新对象交互性
await this.layerManager?.updateLayersObjectsInteractivity?.(false);
diff --git a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue
index 5d05111c..b6971bb3 100644
--- a/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue
+++ b/src/component/Canvas/CanvasEditor/components/LayersPanel/LayersPanel.vue
@@ -589,11 +589,11 @@ function handleLayerClick(layer, event) {
layerManager?.setAllActiveGroupLayerCanvasObject?.(layer);
setActiveLayer(layer.children[0].id, { parentId: layer.id });
} else {
+ // 选中画布中的图层对象
+ layerManager?.selectLayerObjects(layer.id);
// 否则直接设置当前图层为活动图层
setActiveLayer(layer.id);
layerManager?.updateLayersObjectsInteractivity();
- // 选中画布中的图层对象
- layerManager?.selectLayerObjects(layer.id);
}
}
lastSelectedIndex.value = sortableRootLayers.value.findIndex((l) => l.id === layer.id);
diff --git a/src/component/Canvas/CanvasEditor/components/SelectMenuPanel.vue b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel.vue
new file mode 100644
index 00000000..a340bde0
--- /dev/null
+++ b/src/component/Canvas/CanvasEditor/components/SelectMenuPanel.vue
@@ -0,0 +1,609 @@
+
+