feat(CanvasManager): enhance image layer management and event handling
This commit is contained in:
@@ -3,6 +3,7 @@ import { fabric } from "fabric-with-all";
|
||||
/**
|
||||
* 笔刷指示器
|
||||
* 在画笔模式下显示当前笔刷大小的圆圈指示器
|
||||
* 使用独立的 fabric.StaticCanvas,完全不干扰主画布的绘制流程
|
||||
*/
|
||||
export class BrushIndicator {
|
||||
/**
|
||||
@@ -19,18 +20,129 @@ export class BrushIndicator {
|
||||
...options,
|
||||
};
|
||||
|
||||
// 指示器圆圈对象
|
||||
this.indicator = null;
|
||||
// 创建独立的静态画布
|
||||
this.indicatorCanvas = null;
|
||||
this.staticCanvas = null;
|
||||
this.indicatorCircle = null;
|
||||
this._createStaticCanvas();
|
||||
|
||||
// 事件处理器
|
||||
this._mouseEnterHandler = null;
|
||||
this._mouseLeaveHandler = null;
|
||||
this._mouseMoveHandler = null;
|
||||
this._zoomHandler = null;
|
||||
this._viewportHandler = null;
|
||||
|
||||
// 当前状态
|
||||
this.isVisible = false;
|
||||
this.currentSize = 10;
|
||||
this.isEnabled = false;
|
||||
this.currentPosition = { x: 0, y: 0 };
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建独立的 fabric.StaticCanvas 画布层
|
||||
* @private
|
||||
*/
|
||||
_createStaticCanvas() {
|
||||
if (!this.canvas.wrapperEl) return;
|
||||
|
||||
// 创建独立的 canvas 元素
|
||||
this.indicatorCanvas = document.createElement("canvas");
|
||||
this.indicatorCanvas.className = "brush-indicator-canvas";
|
||||
|
||||
// 设置样式,使其覆盖在主画布上
|
||||
Object.assign(this.indicatorCanvas.style, {
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
left: "0",
|
||||
pointerEvents: "none", // 不阻挡鼠标事件
|
||||
zIndex: "10", // 确保在最上层
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
});
|
||||
|
||||
// 将指示器画布添加到 fabric.js 画布容器中
|
||||
this.canvas.wrapperEl.appendChild(this.indicatorCanvas);
|
||||
|
||||
// 创建 fabric.StaticCanvas 实例
|
||||
this.staticCanvas = new fabric.StaticCanvas(this.indicatorCanvas, {
|
||||
enableRetinaScaling: this.canvas.enableRetinaScaling,
|
||||
allowTouchScrolling: false,
|
||||
selection: false,
|
||||
skipTargetFind: true,
|
||||
preserveObjectStacking: true,
|
||||
});
|
||||
|
||||
// 同步画布尺寸和变换
|
||||
this._syncCanvasProperties();
|
||||
|
||||
// 监听主画布变化
|
||||
this._observeCanvasChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步画布属性(尺寸、缩放、视口变换等)
|
||||
* @private
|
||||
*/
|
||||
_syncCanvasProperties() {
|
||||
if (!this.staticCanvas || !this.canvas) return;
|
||||
|
||||
// 同步画布尺寸
|
||||
this.staticCanvas.setWidth(this.canvas.width);
|
||||
this.staticCanvas.setHeight(this.canvas.height);
|
||||
|
||||
// 同步缩放
|
||||
this.staticCanvas.setZoom(this.canvas.getZoom());
|
||||
|
||||
// 同步视口变换
|
||||
const vpt = this.canvas.viewportTransform;
|
||||
if (vpt) {
|
||||
this.staticCanvas.setViewportTransform([...vpt]);
|
||||
}
|
||||
|
||||
// 同步背景色(可选,通常保持透明)
|
||||
// this.staticCanvas.backgroundColor = 'transparent';
|
||||
|
||||
// 重新渲染
|
||||
this.staticCanvas.renderAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听主画布变化
|
||||
* @private
|
||||
*/
|
||||
_observeCanvasChanges() {
|
||||
if (!this.canvas) return;
|
||||
|
||||
// 监听缩放变化
|
||||
this._zoomHandler = () => {
|
||||
this._syncCanvasProperties();
|
||||
};
|
||||
|
||||
// 监听视口变化
|
||||
this._viewportHandler = () => {
|
||||
this._syncCanvasProperties();
|
||||
};
|
||||
|
||||
// 监听画布尺寸变化
|
||||
this._resizeHandler = () => {
|
||||
this._syncCanvasProperties();
|
||||
};
|
||||
|
||||
// 绑定事件
|
||||
this.canvas.on("after:render", this._zoomHandler);
|
||||
this.canvas.on("canvas:zoomed", this._zoomHandler);
|
||||
this.canvas.on("viewport:changed", this._viewportHandler);
|
||||
this.canvas.on("canvas:resized", this._resizeHandler);
|
||||
|
||||
// 使用 ResizeObserver 监听 DOM 尺寸变化
|
||||
if (window.ResizeObserver && this.canvas.upperCanvasEl) {
|
||||
this.resizeObserver = new ResizeObserver(() => {
|
||||
this._syncCanvasProperties();
|
||||
});
|
||||
this.resizeObserver.observe(this.canvas.upperCanvasEl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,8 +186,11 @@ export class BrushIndicator {
|
||||
this.currentSize = size;
|
||||
|
||||
// 如果指示器正在显示,更新其大小
|
||||
if (this.isVisible && this.indicator) {
|
||||
this._updateIndicatorSize();
|
||||
if (this.isVisible && this.indicatorCircle) {
|
||||
this.indicatorCircle.set({
|
||||
radius: size / 2,
|
||||
});
|
||||
this.staticCanvas.renderAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,19 +202,15 @@ export class BrushIndicator {
|
||||
// 更新配置选项中的颜色
|
||||
if (color) {
|
||||
this.options.strokeColor = color;
|
||||
// 将颜色转换为半透明填充色
|
||||
// this.options.fillColor = this._convertToTransparentFill(color);
|
||||
}
|
||||
|
||||
// 如果指示器正在显示,更新其颜色
|
||||
if (this.isVisible && this.indicator) {
|
||||
this.indicator.set({
|
||||
if (this.isVisible && this.indicatorCircle) {
|
||||
this.indicatorCircle.set({
|
||||
stroke: this.options.strokeColor,
|
||||
fill: this.options.fillColor,
|
||||
});
|
||||
|
||||
// 重新渲染画布
|
||||
this.canvas.requestRenderAll();
|
||||
this.staticCanvas.renderAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,23 +224,26 @@ export class BrushIndicator {
|
||||
this.isVisible = true;
|
||||
|
||||
// 创建指示器圆圈
|
||||
this._createIndicator(pointer);
|
||||
this._createIndicatorCircle();
|
||||
|
||||
// 更新位置
|
||||
this.updatePosition(pointer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏指示器
|
||||
*/
|
||||
hide() {
|
||||
if (!this.isVisible || !this.indicator) return;
|
||||
if (!this.isVisible) return;
|
||||
|
||||
this.isVisible = false;
|
||||
|
||||
// 从画布移除指示器
|
||||
this.canvas.remove(this.indicator);
|
||||
this.indicator = null;
|
||||
|
||||
// 重新渲染画布
|
||||
this.canvas.renderAll();
|
||||
// 移除指示器圆圈
|
||||
if (this.indicatorCircle && this.staticCanvas) {
|
||||
this.staticCanvas.remove(this.indicatorCircle);
|
||||
this.indicatorCircle = null;
|
||||
this.staticCanvas.renderAll();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,22 +251,53 @@ export class BrushIndicator {
|
||||
* @param {Object} pointer 鼠标位置
|
||||
*/
|
||||
updatePosition(pointer) {
|
||||
if (!this.isVisible || !this.indicator) return;
|
||||
if (!this.isVisible || !this.indicatorCircle) return;
|
||||
|
||||
// 转换坐标考虑画布变换
|
||||
// 使用主画布的坐标转换
|
||||
const canvasPointer = this.canvas.getPointer(pointer);
|
||||
|
||||
// 批量更新位置属性
|
||||
this.indicator.set({
|
||||
// 更新当前位置
|
||||
this.currentPosition = {
|
||||
x: canvasPointer.x,
|
||||
y: canvasPointer.y,
|
||||
};
|
||||
|
||||
// 更新指示器位置
|
||||
this.indicatorCircle.set({
|
||||
left: canvasPointer.x,
|
||||
top: canvasPointer.y,
|
||||
});
|
||||
|
||||
// 更新坐标系
|
||||
this.indicator.setCoords();
|
||||
// 重新渲染静态画布
|
||||
// this.staticCanvas.renderAll();
|
||||
// 优化渲染
|
||||
this.staticCanvas.requestRenderAll();
|
||||
}
|
||||
|
||||
// 使用requestRenderAll优化渲染性能
|
||||
this.canvas.requestRenderAll();
|
||||
/**
|
||||
* 创建指示器圆圈对象
|
||||
* @private
|
||||
*/
|
||||
_createIndicatorCircle() {
|
||||
if (this.indicatorCircle || !this.staticCanvas) return;
|
||||
|
||||
// 创建圆圈对象
|
||||
this.indicatorCircle = new fabric.Circle({
|
||||
radius: this.currentSize / 2,
|
||||
fill: this.options.fillColor,
|
||||
stroke: this.options.strokeColor,
|
||||
strokeWidth: this.options.strokeWidth,
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
selectable: false,
|
||||
evented: false,
|
||||
excludeFromExport: true,
|
||||
isTemp: true,
|
||||
pointer: true,
|
||||
});
|
||||
|
||||
// 添加到静态画布
|
||||
this.staticCanvas.add(this.indicatorCircle);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +309,6 @@ export class BrushIndicator {
|
||||
|
||||
// 鼠标进入画布
|
||||
this._mouseEnterHandler = (e) => {
|
||||
// 只在画笔相关模式下显示
|
||||
if (this._shouldShowIndicator()) {
|
||||
this.show(e.e);
|
||||
}
|
||||
@@ -175,75 +319,29 @@ export class BrushIndicator {
|
||||
this.hide();
|
||||
};
|
||||
|
||||
// 鼠标在画布上移动 - 修改事件处理逻辑
|
||||
// 鼠标移动
|
||||
this._mouseMoveHandler = (e) => {
|
||||
// 如果正在绘图,不处理指示器更新,避免干扰绘图
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._shouldShowIndicator()) {
|
||||
if (!this.isVisible) {
|
||||
this.show(e.e);
|
||||
} else {
|
||||
this.updatePosition(e.e);
|
||||
// requestIdleCallback(() => {
|
||||
// this.updatePosition(e.e);
|
||||
// });
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
this.updatePosition(e.e);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
// 监听绘图开始事件,隐藏指示器并模拟微小移动
|
||||
this._drawingStartHandler = (e) => {
|
||||
if (this.isVisible) {
|
||||
this.hide();
|
||||
}
|
||||
|
||||
// 模拟1px的微小移动来确保笔刷能正常启动绘画
|
||||
this._simulateTinyMovement(e);
|
||||
};
|
||||
|
||||
// 监听绘图结束事件,重新显示指示器
|
||||
this._drawingEndHandler = () => {
|
||||
// 延迟一点重新显示,确保绘图完全结束
|
||||
setTimeout(() => {
|
||||
if (this._shouldShowIndicator() && !this.isVisible) {
|
||||
// 重新检查鼠标位置并显示指示器
|
||||
this._checkAndShowIndicator();
|
||||
}
|
||||
}, 50);
|
||||
};
|
||||
|
||||
// 画布缩放变化处理器
|
||||
this._zoomHandler = () => {
|
||||
if (this.isVisible && this.indicator) {
|
||||
// 立即更新指示器大小
|
||||
this._updateIndicatorSize();
|
||||
}
|
||||
};
|
||||
|
||||
// 画布视口变化处理器
|
||||
this._viewportHandler = () => {
|
||||
if (this.isVisible && this.indicator) {
|
||||
// 视口变化时也需要更新指示器
|
||||
this._updateIndicatorSize();
|
||||
}
|
||||
};
|
||||
|
||||
// 绑定事件
|
||||
this.canvas.on("mouse:over", this._mouseEnterHandler);
|
||||
this.canvas.on("mouse:out", this._mouseLeaveHandler);
|
||||
this.canvas.on("mouse:move", this._mouseMoveHandler);
|
||||
|
||||
// 监听绘图状态变化
|
||||
this.canvas.on("path:created", this._drawingEndHandler);
|
||||
this.canvas.on("mouse:down", this._drawingStartHandler);
|
||||
this.canvas.on("mouse:up", this._drawingEndHandler);
|
||||
|
||||
// 监听画布缩放和视口变化
|
||||
this.canvas.on("after:render", this._zoomHandler);
|
||||
this.canvas.on("canvas:zoomed", this._zoomHandler);
|
||||
this.canvas.on("viewport:changed", this._viewportHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,6 +351,7 @@ export class BrushIndicator {
|
||||
_unbindEvents() {
|
||||
if (!this.canvas) return;
|
||||
|
||||
// 解绑鼠标事件
|
||||
if (this._mouseEnterHandler) {
|
||||
this.canvas.off("mouse:over", this._mouseEnterHandler);
|
||||
this._mouseEnterHandler = null;
|
||||
@@ -268,16 +367,7 @@ export class BrushIndicator {
|
||||
this._mouseMoveHandler = null;
|
||||
}
|
||||
|
||||
// 清理绘图相关事件
|
||||
if (this._drawingStartHandler) {
|
||||
this.canvas.off("mouse:down", this._drawingStartHandler);
|
||||
this.canvas.off("path:created", this._drawingEndHandler);
|
||||
this.canvas.off("mouse:up", this._drawingEndHandler);
|
||||
this._drawingStartHandler = null;
|
||||
this._drawingEndHandler = null;
|
||||
}
|
||||
|
||||
// 清理缩放相关事件
|
||||
// 解绑画布变化事件
|
||||
if (this._zoomHandler) {
|
||||
this.canvas.off("after:render", this._zoomHandler);
|
||||
this.canvas.off("canvas:zoomed", this._zoomHandler);
|
||||
@@ -288,81 +378,11 @@ export class BrushIndicator {
|
||||
this.canvas.off("viewport:changed", this._viewportHandler);
|
||||
this._viewportHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建指示器圆圈
|
||||
* @private
|
||||
* @param {Object} pointer 鼠标位置
|
||||
*/
|
||||
_createIndicator(pointer) {
|
||||
// 转换坐标考虑画布变换
|
||||
const canvasPointer = this.canvas.getPointer(pointer);
|
||||
|
||||
// 计算考虑画布缩放的半径
|
||||
const radius = this._getIndicatorRadius();
|
||||
|
||||
// 创建圆圈
|
||||
this.indicator = new fabric.Circle({
|
||||
left: canvasPointer.x,
|
||||
top: canvasPointer.y,
|
||||
radius: radius,
|
||||
fill: this.options.fillColor,
|
||||
stroke: this.options.strokeColor,
|
||||
strokeWidth: this.options.strokeWidth / this.canvas.getZoom(), // 线宽不受缩放影响
|
||||
originX: "center",
|
||||
originY: "center",
|
||||
selectable: false,
|
||||
evented: false,
|
||||
excludeFromExport: true, // 导出时排除
|
||||
isTemp: true, // 标记为临时对象
|
||||
pointer: true, // 标记为鼠标指示器
|
||||
});
|
||||
|
||||
// 添加到画布
|
||||
this.canvas.add(this.indicator);
|
||||
|
||||
// 确保指示器在最顶层
|
||||
this.canvas.bringToFront(this.indicator);
|
||||
|
||||
// 重新渲染画布
|
||||
this.canvas.renderAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新指示器大小
|
||||
* @private
|
||||
*/
|
||||
_updateIndicatorSize() {
|
||||
if (!this.indicator) return;
|
||||
|
||||
// 获取当前画布缩放比例
|
||||
const zoom = this.canvas.getZoom();
|
||||
const radius = this._getIndicatorRadius();
|
||||
const strokeWidth = this.options.strokeWidth / zoom;
|
||||
|
||||
// 批量更新属性,减少重绘次数
|
||||
this.indicator.set({
|
||||
radius: radius,
|
||||
strokeWidth: strokeWidth,
|
||||
});
|
||||
|
||||
// 标记指示器需要重绘
|
||||
this.indicator.setCoords();
|
||||
|
||||
// 重新渲染画布
|
||||
this.canvas.requestRenderAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指示器半径(与笔刷实际绘制大小一致)
|
||||
* @private
|
||||
* @returns {Number} 半径值
|
||||
*/
|
||||
_getIndicatorRadius() {
|
||||
// 指示器大小应该与笔刷实际绘制大小一致
|
||||
// 笔刷的实际绘制大小不受画布缩放影响,所以指示器也不应该受缩放影响
|
||||
return this.currentSize / 2;
|
||||
if (this._resizeHandler) {
|
||||
this.canvas.off("canvas:resized", this._resizeHandler);
|
||||
this._resizeHandler = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,104 +400,35 @@ export class BrushIndicator {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查并显示指示器(如果鼠标在画布内)
|
||||
* @private
|
||||
*/
|
||||
_checkAndShowIndicator() {
|
||||
// 这个方法用于在绘图结束后重新显示指示器
|
||||
// 由于无法直接获取当前鼠标位置,我们依赖下一次鼠标移动事件
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟微小移动
|
||||
* @private
|
||||
* @param {Object} e 鼠标事件对象
|
||||
*/
|
||||
_simulateTinyMovement(e) {
|
||||
// 获取当前鼠标位置
|
||||
const pointer = this.canvas.getPointer(e.e);
|
||||
|
||||
// 模拟移动1px
|
||||
this.canvas.fire("mouse:move", {
|
||||
...e,
|
||||
e: {
|
||||
...e.e,
|
||||
clientX: pointer.x + 1,
|
||||
clientY: pointer.y + 1,
|
||||
pointer: {
|
||||
x: pointer.x + 1,
|
||||
y: pointer.y + 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 再模拟移动回原位置
|
||||
this.canvas.fire("mouse:move", {
|
||||
...e,
|
||||
e: {
|
||||
...e.e,
|
||||
clientX: pointer.x,
|
||||
clientY: pointer.y,
|
||||
pointer: {
|
||||
x: pointer.x,
|
||||
y: pointer.y,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 销毁指示器
|
||||
*/
|
||||
dispose() {
|
||||
this.disable();
|
||||
|
||||
// 解绑画布变化事件
|
||||
this._unbindEvents();
|
||||
|
||||
// 停止监听尺寸变化
|
||||
if (this.resizeObserver) {
|
||||
this.resizeObserver.disconnect();
|
||||
this.resizeObserver = null;
|
||||
}
|
||||
|
||||
// 销毁静态画布
|
||||
if (this.staticCanvas) {
|
||||
this.staticCanvas.dispose();
|
||||
this.staticCanvas = null;
|
||||
}
|
||||
|
||||
// 移除指示器画布
|
||||
if (this.indicatorCanvas && this.indicatorCanvas.parentNode) {
|
||||
this.indicatorCanvas.parentNode.removeChild(this.indicatorCanvas);
|
||||
}
|
||||
|
||||
this.canvas = null;
|
||||
this.indicator = null;
|
||||
this.indicatorCanvas = null;
|
||||
this.indicatorCircle = null;
|
||||
this.options = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将颜色转换为半透明填充色
|
||||
* @private
|
||||
* @param {String} color 原始颜色值
|
||||
* @returns {String} 半透明填充色
|
||||
*/
|
||||
_convertToTransparentFill(color) {
|
||||
// 如果已经是 rgba 格式,直接使用但降低透明度
|
||||
if (color.startsWith("rgba")) {
|
||||
return color.replace(/[\d\.]+\)$/, "0.1)");
|
||||
}
|
||||
|
||||
// 如果是 rgb 格式,转换为 rgba
|
||||
if (color.startsWith("rgb")) {
|
||||
return color.replace("rgb", "rgba").replace(")", ", 0.1)");
|
||||
}
|
||||
|
||||
// 如果是十六进制格式,转换为 rgba
|
||||
if (color.startsWith("#")) {
|
||||
const hex = color.slice(1);
|
||||
let r, g, b;
|
||||
|
||||
if (hex.length === 3) {
|
||||
// 处理 #RGB 格式
|
||||
r = parseInt(hex[0] + hex[0], 16);
|
||||
g = parseInt(hex[1] + hex[1], 16);
|
||||
b = parseInt(hex[2] + hex[2], 16);
|
||||
} else if (hex.length === 6) {
|
||||
// 处理 #RRGGBB 格式
|
||||
r = parseInt(hex.slice(0, 2), 16);
|
||||
g = parseInt(hex.slice(2, 4), 16);
|
||||
b = parseInt(hex.slice(4, 6), 16);
|
||||
} else {
|
||||
// 无法解析的格式,返回默认半透明黑色
|
||||
return "rgba(0, 0, 0, 0.1)";
|
||||
}
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, 0.1)`;
|
||||
}
|
||||
|
||||
// 处理命名颜色或其他格式,使用默认半透明效果
|
||||
return "rgba(0, 0, 0, 0.1)";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import initAligningGuidelines, {
|
||||
import { ThumbnailManager } from "./ThumbnailManager";
|
||||
import { ExportManager } from "./ExportManager";
|
||||
import {
|
||||
findLayerRecursively,
|
||||
isGroupLayer,
|
||||
OperationType,
|
||||
OperationTypes,
|
||||
@@ -15,7 +16,11 @@ import { CanvasEventManager } from "./events/CanvasEventManager";
|
||||
import CanvasConfig from "../config/canvasConfig";
|
||||
import { RedGreenModeManager } from "./RedGreenModeManager";
|
||||
import { EraserStateManager } from "./EraserStateManager";
|
||||
import { deepClone, optimizeCanvasRendering } from "../utils/helper";
|
||||
import {
|
||||
deepClone,
|
||||
generateId,
|
||||
optimizeCanvasRendering,
|
||||
} from "../utils/helper";
|
||||
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
|
||||
import { isFunction } from "lodash-es";
|
||||
import {
|
||||
@@ -39,7 +44,9 @@ export class CanvasManager {
|
||||
this.canvasHeight = options.canvasHeight || this.height; // 画布高度
|
||||
this.canvasColor = options.canvasColor || "#ffffff"; // 画布背景颜色
|
||||
this.enabledRedGreenMode = options.enabledRedGreenMode || false; // 是否启用红绿图模式
|
||||
this.isFixedErasable = options.isFixedErasable || false; // 是否允许擦除固定图层
|
||||
this.eraserStateManager = null; // 橡皮擦状态管理器引用
|
||||
this.handleCanvasInit = null; // 画布初始化回调函数
|
||||
// 初始化画布
|
||||
this.initializeCanvas();
|
||||
}
|
||||
@@ -73,16 +80,50 @@ export class CanvasManager {
|
||||
|
||||
this.canvas.thumbnailManager = this.thumbnailManager; // 将缩略图管理器绑定到画布
|
||||
|
||||
// 设置画布辅助线
|
||||
initAligningGuidelines(this.canvas);
|
||||
// // 设置画布辅助线
|
||||
// initAligningGuidelines(this.canvas);
|
||||
|
||||
// 设置画布中心线
|
||||
initCenteringGuidelines(this.canvas);
|
||||
// // 设置画布中心线
|
||||
// initCenteringGuidelines(this.canvas);
|
||||
|
||||
// 初始化画布事件监听器
|
||||
this._initCanvasEvents();
|
||||
}
|
||||
|
||||
// 添加图像到指定图层
|
||||
async addImageToLayer({ targetLayerId, fabricImage, ...options }) {
|
||||
// 如果图层管理器存在,将图像合并到当前活动图层
|
||||
if (this.layerManager) {
|
||||
// 获取当前活动图层
|
||||
let activeLayer = targetLayerId
|
||||
? findLayerRecursively(this.layers.value, targetLayerId)?.layer
|
||||
: this.layerManager.getActiveLayer();
|
||||
|
||||
if (activeLayer) {
|
||||
// 确保新图像具有正确的图层信息
|
||||
fabricImage.set({
|
||||
layerId: activeLayer.id,
|
||||
layerName: activeLayer.name,
|
||||
id: fabricImage.id || generateId("brush_img_"),
|
||||
});
|
||||
|
||||
// 执行高保真合并操作
|
||||
await this.eventManager?.mergeLayerObjectsForPerformance?.({
|
||||
fabricImage,
|
||||
activeLayer,
|
||||
options,
|
||||
});
|
||||
|
||||
this.thumbnailManager?.generateLayerThumbnail(activeLayer.id);
|
||||
|
||||
// 返回true表示不要自动添加到画布,因为我们已经通过图层管理器处理了
|
||||
return true;
|
||||
} else {
|
||||
console.warn("没有活动图层,无法添加图像");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化画布事件监听器
|
||||
* 设置鼠标事件、键盘事件等
|
||||
@@ -91,36 +132,7 @@ export class CanvasManager {
|
||||
_initCanvasEvents() {
|
||||
// 添加笔刷图像转换处理回调
|
||||
this.canvas.onBrushImageConverted = async (fabricImage) => {
|
||||
// 如果图层管理器存在,将图像合并到当前活动图层
|
||||
if (this.layerManager) {
|
||||
// 获取当前活动图层
|
||||
const activeLayer = this.layerManager.getActiveLayer();
|
||||
|
||||
if (activeLayer) {
|
||||
// 确保新图像具有正确的图层信息
|
||||
fabricImage.set({
|
||||
layerId: activeLayer.id,
|
||||
layerName: activeLayer.name,
|
||||
id:
|
||||
fabricImage.id ||
|
||||
`brush_img_${Date.now()}_${Math.floor(Math.random() * 1000)}`,
|
||||
});
|
||||
|
||||
// 执行高保真合并操作
|
||||
await this.eventManager?.mergeLayerObjectsForPerformance?.({
|
||||
fabricImage,
|
||||
activeLayer,
|
||||
});
|
||||
|
||||
this.thumbnailManager?.generateLayerThumbnail(activeLayer.id);
|
||||
|
||||
// 返回true表示不要自动添加到画布,因为我们已经通过图层管理器处理了
|
||||
return true;
|
||||
} else {
|
||||
console.warn("没有活动图层,使用默认行为添加图像");
|
||||
}
|
||||
}
|
||||
|
||||
await this.addImageToLayer({ fabricImage, targetLayerId: null });
|
||||
// 返回false表示使用默认行为(直接添加到画布)
|
||||
return false;
|
||||
};
|
||||
@@ -155,6 +167,15 @@ export class CanvasManager {
|
||||
this.thumbnailManager?.generateLayerThumbnail(
|
||||
this.layerManager?.activeLayerId?.value
|
||||
);
|
||||
|
||||
// 固定图层 的擦除也需要重新生成缩略图 要判断 当前固定图层是否锁定
|
||||
const fixedLayer = this.layers?.value?.find(
|
||||
(layer) => layer.isFixed && !layer.locked
|
||||
);
|
||||
// 如果有固定图层且未锁定,则生成缩略图
|
||||
fixedLayer &&
|
||||
this.isFixedErasable &&
|
||||
this.thumbnailManager?.generateLayerThumbnail(fixedLayer?.id);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -263,6 +284,10 @@ export class CanvasManager {
|
||||
this.animationManager.setupInteractionAnimations();
|
||||
}
|
||||
|
||||
setupCanvasInitEvent(handleCanvasInit) {
|
||||
this.handleCanvasInit = handleCanvasInit;
|
||||
}
|
||||
|
||||
setupLongPress(callback) {
|
||||
if (this.eventManager) {
|
||||
this.eventManager.setupLongPress(callback);
|
||||
@@ -697,13 +722,26 @@ export class CanvasManager {
|
||||
command.undoable =
|
||||
options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
|
||||
|
||||
return (
|
||||
(await command?.execute?.()) || {
|
||||
success: false,
|
||||
layerId: null,
|
||||
imageUrl: null,
|
||||
const result = (await command?.execute?.()) || {
|
||||
success: false,
|
||||
layerId: null,
|
||||
imageUrl: null,
|
||||
};
|
||||
|
||||
const findLayer = this.layers.value.find((fItem) => {
|
||||
if (options.targetLayerType === "fixed") {
|
||||
return fItem.isFixed;
|
||||
}
|
||||
);
|
||||
if (options.targetLayerType === "background") {
|
||||
return fItem.isBackground;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// 如果找到了图层,则生成缩略图
|
||||
findLayer && this.thumbnailManager?.generateLayerThumbnail(findLayer.id);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -934,7 +972,7 @@ export class CanvasManager {
|
||||
}
|
||||
|
||||
// 清除当前画布内容
|
||||
this.canvas.clear();
|
||||
// this.canvas.clear(); // 清除画布内容 可以先去掉 这样加载闪动的情况就比较少 如果有问题 可以再打开
|
||||
console.log("清除当前画布内容", canvasData);
|
||||
delete canvasData.clipPath; // 删除当前裁剪路径
|
||||
// 加载画布数据
|
||||
@@ -1002,6 +1040,8 @@ export class CanvasManager {
|
||||
}, 500);
|
||||
|
||||
console.log("画布JSON数据加载完成");
|
||||
// 画布初始化事件
|
||||
this.handleCanvasInit?.(ture);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
console.error("恢复图层数据失败:", error);
|
||||
|
||||
@@ -569,7 +569,7 @@ export class LayerManager {
|
||||
* @param {string} layerId 目标图层ID,如果不提供则使用当前活动图层
|
||||
* @returns {Object} 添加的对象
|
||||
*/
|
||||
addObjectToLayer(fabricObject, layerId = null) {
|
||||
addObjectToLayer(fabricObject, layerId = null, options = {}) {
|
||||
const targetLayerId = layerId || this.activeLayerId.value;
|
||||
|
||||
// 如果没有指定图层ID,也没有活动图层,则返回错误
|
||||
@@ -597,6 +597,9 @@ export class LayerManager {
|
||||
layerManager: this,
|
||||
});
|
||||
|
||||
// 设置命令的撤销状态
|
||||
if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销
|
||||
|
||||
// 执行命令
|
||||
if (this.commandManager) {
|
||||
this.commandManager.execute(command);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isBoolean } from "lodash-es";
|
||||
import { TransformCommand } from "../../commands/StateCommands";
|
||||
import { generateId } from "../../utils/helper";
|
||||
import { OperationType, OperationTypes } from "../../utils/layerHelper";
|
||||
@@ -577,7 +578,7 @@ export class CanvasEventManager {
|
||||
* @param {Object} options.activeLayer 当前活动图层
|
||||
* @private
|
||||
*/
|
||||
async mergeLayerObjectsForPerformance({ fabricImage, activeLayer }) {
|
||||
async mergeLayerObjectsForPerformance({ fabricImage, activeLayer, options }) {
|
||||
// 确保有命令管理器
|
||||
if (!this.layerManager || !this.layerManager.commandManager) {
|
||||
console.warn("合并对象失败:没有命令管理器");
|
||||
@@ -602,7 +603,7 @@ export class CanvasEventManager {
|
||||
|
||||
// 如果只有一个新图像且图层为空,直接添加到图层
|
||||
if (hasNewImage && !hasExistingObjects) {
|
||||
this.layerManager.addObjectToLayer(fabricImage, activeLayer.id);
|
||||
this.layerManager.addObjectToLayer(fabricImage, activeLayer.id, options);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -615,6 +616,9 @@ export class CanvasEventManager {
|
||||
fabricImage
|
||||
);
|
||||
|
||||
// 设置命令的撤销状态
|
||||
if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销
|
||||
|
||||
this.layerManager?.commandManager?.execute?.(command, {
|
||||
name: `合并图层 ${activeLayer.name} 中的对象为组`,
|
||||
});
|
||||
|
||||
@@ -627,10 +627,10 @@ export class LiquifyCPUManager {
|
||||
// 使用增强的像素算法
|
||||
switch (mode) {
|
||||
case this.modes.CLOCKWISE:
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
|
||||
break;
|
||||
case this.modes.COUNTERCLOCKWISE:
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
|
||||
break;
|
||||
case this.modes.PINCH:
|
||||
this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
|
||||
@@ -1300,10 +1300,10 @@ export class LiquifyCPUManager {
|
||||
// 使用增强的像素算法
|
||||
switch (mode) {
|
||||
case this.modes.CLOCKWISE:
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
|
||||
break;
|
||||
case this.modes.COUNTERCLOCKWISE:
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
|
||||
break;
|
||||
case this.modes.PINCH:
|
||||
this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
|
||||
|
||||
Reference in New Issue
Block a user