Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js

987 lines
28 KiB
JavaScript
Raw Normal View History

2026-03-09 13:44:32 +08:00
import { isBoolean } from "lodash-es";
import { OperationType, OperationTypes } from "../../tools/layerHelper";
2026-03-17 17:17:48 +08:00
import { ShapeToolManager } from "../ShapeToolManager"
2026-03-13 11:18:36 +08:00
import { AISelectboxToolManager } from "../AISelectboxToolManager"
2026-03-09 13:44:32 +08:00
export class CanvasEventManager {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.canvasManager = options.canvasManager;
this.toolManager = options.toolManager || null;
2026-03-12 11:40:48 +08:00
this.stateManager = options.stateManager || null;
2026-03-09 13:44:32 +08:00
this.animationManager = options.animationManager;
this.thumbnailManager = options.thumbnailManager;
this.activeElementId = options.activeElementId || { value: null };
this.layerManager = options.layerManager || null;
this.layers = options.layers || null;
this.lastSelectLayerId = options.lastSelectLayerId || null; // 最后选择的图层ID
// 事件处理的内部状态 - 优化设备检测
this.deviceInfo = this._detectDeviceType();
this.dragStartTime = 0;
this.lastMousePositions = [];
this.positionHistoryLimit = 5; // 追踪鼠标位置的历史记录,用于计算速度
this.longPressTimer = null;
this.longPressThreshold = 500;
2026-03-12 15:51:18 +08:00
const managers = {
canvasManager: this.canvasManager,
stateManager: this.stateManager,
toolManager: this.toolManager,
layerManager: this.layerManager,
}
2026-03-17 17:17:48 +08:00
this.shapeToolManager = new ShapeToolManager(managers)
2026-03-13 11:18:36 +08:00
this.aiSelectboxToolManager = new AISelectboxToolManager(managers)
2026-03-24 11:49:53 +08:00
this.stateManager.setManager({ aiSelectboxToolManager: this.aiSelectboxToolManager })
2026-03-12 15:51:18 +08:00
2026-03-09 13:44:32 +08:00
// 初始化所有事件
this.initEvents();
}
initEvents() {
this.setupZoomEvents();
// 优化三端设备的事件处理逻辑
if (this.deviceInfo.isMobile || this.deviceInfo.isTablet) {
// 真正的移动设备和平板设备使用触摸事件
this.setupTouchEvents();
} else {
// PC 和 Mac 设备主要使用鼠标事件
this.setupMouseEvents();
}
// Mac 设备需要额外的触摸手势支持(用于特殊场景)
if (this.deviceInfo.isMac && this.deviceInfo.hasTouchSupport) {
this.setupMacTouchGestures();
}
// 共享事件
2026-03-11 15:34:56 +08:00
this.setupSelectionEvents();
2026-03-12 11:40:48 +08:00
this.setupObjectEvents();
2026-03-23 17:06:38 +08:00
this.setupDoubleClickEvents();
2026-03-09 13:44:32 +08:00
}
setupZoomEvents() {
// 水平/垂直滚动相关状态
this._scrollWheelEvents = [];
this._scrollAccumulatedDelta = { x: 0, y: 0 };
this._scrollAccumulationTimeout = null;
this._scrollAccumulationTime = 100; // 降低滚轮累积时间窗口
this._lastScrollTime = 0; // 跟踪上次滚动时间
this._scrollThrottleDelay = 5; // 滚动节流延迟(毫秒)
// 缩放处理 - 使用动画管理器,针对 Mac 设备优化
this.canvas.on("mouse:wheel", (opt) => {
// Mac 设备双指滚动优化:确保滚动事件正确处理
if (this.deviceInfo.isMac) {
// Mac设备的简化处理逻辑减少不必要的动画中断
// 让动画管理器自行处理冲突,避免过度干预
} else {
// 非 Mac 设备的标准处理
if (
this.animationManager._panAnimation ||
this.animationManager._zoomAnimation
) {
this.animationManager._wasPanning =
!!this.animationManager._panAnimation;
this.animationManager._wasZooming =
!!this.animationManager._zoomAnimation;
this.animationManager.smoothStopAnimations({ duration: 0.1 });
}
}
// 按住 Ctrl 键时实现垂直滚动Mac 下是 Cmd 键)
const isCtrlOrCmd = this.deviceInfo.isMac ? opt.e.metaKey : opt.e.ctrlKey;
if (isCtrlOrCmd) {
this.handleScrollWheel(opt, "vertical");
opt.e.preventDefault();
return;
}
// 按住 Shift 键时实现水平滚动
if (opt.e.shiftKey) {
this.handleScrollWheel(opt, "horizontal");
opt.e.preventDefault();
return;
}
// 标准缩放行为 - 让 AnimationManager 处理平滑过渡
// Mac 设备下的双指滚动将直接进入这里进行缩放
this.animationManager.handleMouseWheel(opt);
});
}
/**
* 处理滚轮滚动事件
* @param {Object} opt 滚轮事件对象
* @param {String} direction 滚动方向: 'vertical' 'horizontal'
*/
handleScrollWheel(opt, direction) {
// 获取当前视图变换
const vpt = this.canvas.viewportTransform.slice(0); // 创建副本避免直接修改
const zoom = this.canvas.getZoom();
// 计算滚动量 - 根据方向决定是水平还是垂直滚动
let deltaX = 0;
let deltaY = 0;
// 设置滚动方向和距离
if (direction === "horizontal") {
deltaX = opt.e.deltaY; // 水平滚动
} else {
deltaY = opt.e.deltaY; // 垂直滚动
}
// 计算滚动因子,基于缩放级别和设备类型调整
let scrollFactor = Math.max(0.4, Math.min(1, 1 / zoom));
// Mac 设备优化:触控板滚动通常比鼠标滚轮更敏感
if (this.deviceInfo.isMac) {
const isMacTrackpadScroll =
Math.abs(opt.e.deltaY) < 100 && opt.e.deltaMode === 0;
if (isMacTrackpadScroll) {
// Mac 触控板滚动更细腻,需要调整滚动因子
scrollFactor *= 0.8; // 降低滚动敏感度
}
}
// 直接应用滚动变化,不使用累积和计时器
vpt[4] -= deltaX * scrollFactor;
vpt[5] -= deltaY * scrollFactor;
// 直接设置新的视图变换,不使用动画
this.canvas.setViewportTransform(vpt);
// 请求重新渲染画布
this.canvas.renderAll();
}
/**
* 处理累积的滚轮滚动事件并应用平移
* @private
* @param {String} direction 滚动方向
*/
_processAccumulatedScroll(direction) {
// 这个函数不再需要,但为了兼容性保留空实现
// 所有滚动逻辑已经移到 handleScrollWheel 中直接处理
return;
}
/**
* 停止所有惯性动画
* @param {boolean} smooth 是否平滑过渡默认为 false立即停止
*/
stopInertiaAnimation(smooth = false) {
if (this.animationManager) {
if (this.animationManager._panAnimation && !smooth) {
this.animationManager._panAnimation.kill();
this.animationManager._panAnimation = null;
}
if (this.animationManager._zoomAnimation && !smooth) {
this.animationManager._zoomAnimation.kill();
this.animationManager._zoomAnimation = null;
}
}
}
2026-03-25 11:27:24 +08:00
_handleMouseDown(opt) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (this.aiSelectboxToolManager.tools.includes(currentTool)) {
// 选择框模式
this.aiSelectboxToolManager.mouseDownEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseDownEvent(opt);
} else {
return false
}
return true
}
_handleMouseMove(opt) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (this.aiSelectboxToolManager.tools.includes(currentTool)) {
// 选择框模式
this.aiSelectboxToolManager.mouseMoveEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseMoveEvent(opt);
} else {
return false
}
return true
}
_handleMouseUp(opt) {
const currentTool = this.toolManager.currentTool.value;
if (currentTool === OperationType.DRAW) {
// 绘画模式
} else if (currentTool === OperationType.ERASER) {
// 橡皮擦模式
} else if (this.aiSelectboxToolManager.tools.includes(currentTool)) {
// 选择框模式
this.aiSelectboxToolManager.mouseUpEvent(opt);
} else if (this.shapeToolManager.tools.includes(currentTool)) {
// 形状模式
this.shapeToolManager.mouseUpEvent(opt);
} else {
return false
}
return true
}
2026-03-09 13:44:32 +08:00
/**
* 设置鼠标事件处理
*/
setupMouseEvents() {
// 鼠标按下事件
this.canvas.on("mouse:down", (opt) => {
// console.log("==========鼠标按下",opt)
// 平滑停止任何正在进行的惯性动画
this.stopInertiaAnimation(true);
// if (opt.e.which === 3 && this.toolManager.currentTool.value === OperationType.SELECT) {
// console.log("==========选择模式鼠标右击画布对象")
// } else
2026-03-12 15:51:18 +08:00
const currentTool = this.toolManager.currentTool.value;
2026-03-25 11:27:24 +08:00
if (this._handleMouseDown(opt)) {
2026-03-12 15:51:18 +08:00
} else if (opt.e.altKey || opt.e.which === 2 || currentTool === OperationType.PAN) {
2026-03-09 13:44:32 +08:00
this.canvas.isDragging = true;
this.canvas.lastPosX = opt.e.clientX;
this.canvas.lastPosY = opt.e.clientY;
2026-03-12 15:51:18 +08:00
// this.canvas.defaultCursor = "grabbing";
2026-03-09 13:44:32 +08:00
// 记录拖动开始时间和位置,用于计算速度
this.dragStartTime = Date.now();
this.lastMousePositions = []; // 重置位置历史
2026-03-12 15:51:18 +08:00
this.canvas.selection = false;
this.canvas.renderAll();
2026-03-09 13:44:32 +08:00
}
});
// 鼠标移动事件
this.canvas.on("mouse:move", (opt) => {
2026-03-12 15:51:18 +08:00
const currentTool = this.toolManager.currentTool.value;
2026-03-25 11:27:24 +08:00
if (this._handleMouseMove(opt)) {
2026-03-12 15:51:18 +08:00
} else if (this.canvas.isDragging) {
const vpt = this.canvas.viewportTransform;
vpt[4] += opt.e.clientX - this.canvas.lastPosX;
vpt[5] += opt.e.clientY - this.canvas.lastPosY;
// 记录鼠标位置和时间,用于计算惯性
const now = Date.now();
this.lastMousePositions.push({
x: opt.e.clientX,
y: opt.e.clientY,
time: now,
});
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
// 保持历史记录在限定数量内
if (this.lastMousePositions.length > this.positionHistoryLimit) {
this.lastMousePositions.shift();
}
this.canvas.setViewportTransform(vpt);
this.canvas.renderAll();
this.canvas.lastPosX = opt.e.clientX;
this.canvas.lastPosY = opt.e.clientY;
2026-03-09 13:44:32 +08:00
}
});
// 鼠标抬起事件
this.canvas.on("mouse:up", (opt) => {
this.handleDragEnd(opt);
});
}
/**
* 设置触摸事件处理 - 修复iPad触摸事件支持
*/
setupTouchEvents() {
// 启用Fabric.js的指针事件支持适用于触摸设备
this.canvas.enablePointerEvents = true;
// 触摸状态管理
this.touchState = {
isZooming: false,
initialDistance: 0,
initialZoom: 1,
lastTouchTime: 0,
lastZoomTime: 0,
zoomThrottle: 16, // 约60fps的节流
};
// iPad特殊处理禁用默认的触摸行为
if (this.deviceInfo.isTablet) {
document.addEventListener(
"touchstart",
(e) => {
if (e.target.closest("canvas")) {
e.preventDefault();
}
},
{ passive: false }
);
document.addEventListener(
"touchmove",
(e) => {
if (e.target.closest("canvas")) {
e.preventDefault();
}
},
{ passive: false }
);
}
// 使用标准的mouse事件Fabric.js会自动处理触摸转换
this.canvas.on("mouse:down", (opt) => {
// 只在PAN模式下处理触摸事件
2026-03-12 15:51:18 +08:00
const currentTool = this.toolManager.currentTool.value;
2026-03-25 11:27:24 +08:00
if (this._handleMouseDown(opt)) {
2026-03-12 15:51:18 +08:00
} else if (currentTool === OperationType.PAN) {
// 平滑停止任何正在进行的惯性动画
this.stopInertiaAnimation(true);
// 检查是否是触摸事件
const isTouch = opt.e.type && opt.e.type.includes("touch");
const touches =
opt.e.touches || (opt.e.originalEvent && opt.e.originalEvent.touches);
if (isTouch && touches && touches.length === 2) {
// 双指触摸 - 用于缩放
this.touchState.isZooming = true;
this.touchState.initialDistance = this.getTouchDistance(
touches[0],
touches[1]
);
this.touchState.initialZoom = this.canvas.getZoom();
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
// 计算缩放中心点
const centerX = (touches[0].clientX + touches[1].clientX) / 2;
const centerY = (touches[0].clientY + touches[1].clientY) / 2;
this.touchState.zoomCenter = { x: centerX, y: centerY };
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
opt.e.preventDefault();
} else if (isTouch && touches && touches.length === 1) {
// 单指触摸 - 用于拖拽
this.canvas.isDragging = true;
this.canvas.lastPosX = touches[0].clientX;
this.canvas.lastPosY = touches[0].clientY;
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
this.dragStartTime = Date.now();
this.lastMousePositions = [];
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
this.canvas.selection = false;
opt.e.preventDefault();
} else if (!isTouch) {
// 鼠标事件 - 用于拖拽
this.canvas.isDragging = true;
this.canvas.lastPosX = opt.e.clientX;
this.canvas.lastPosY = opt.e.clientY;
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
this.dragStartTime = Date.now();
this.lastMousePositions = [];
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
this.canvas.selection = false;
opt.e.preventDefault();
}
2026-03-09 13:44:32 +08:00
}
});
// 触摸移动事件 - 优化性能
this.canvas.on("mouse:move", (opt) => {
2026-03-12 15:51:18 +08:00
const currentTool = this.toolManager.currentTool.value;
2026-03-25 11:27:24 +08:00
if (this._handleMouseMove(opt)) {
2026-03-12 15:51:18 +08:00
} else if (currentTool === OperationType.PAN) {
// 检查是否是触摸事件
const isTouch = opt.e.type && opt.e.type.includes("touch");
const touches =
opt.e.touches || (opt.e.originalEvent && opt.e.originalEvent.touches);
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
if (
isTouch &&
touches &&
touches.length === 2 &&
this.touchState.isZooming
) {
// 双指缩放处理 - 修复抖动问题
const currentDistance = this.getTouchDistance(touches[0], touches[1]);
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
// 防止除零和异常值
if (this.touchState.initialDistance === 0 || currentDistance === 0) {
return;
}
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
const scale = currentDistance / this.touchState.initialDistance;
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
// 防止抖动:忽略微小的变化
if (Math.abs(scale - 1) < 0.01) {
return;
}
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
const newZoom = this.touchState.initialZoom * scale;
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
// 限制缩放范围
const clampedZoom = Math.max(0.1, Math.min(5, newZoom));
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
// 使用缩放中心点进行缩放
const point = new fabric.Point(
this.touchState.zoomCenter.x,
this.touchState.zoomCenter.y
);
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
this.canvas.zoomToPoint(point, clampedZoom);
opt.e.preventDefault();
return;
}
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
if (!this.canvas.isDragging) return;
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
let currentX, currentY;
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
if (isTouch && touches && touches.length === 1) {
// 单指触摸移动
currentX = touches[0].clientX;
currentY = touches[0].clientY;
} else if (!isTouch) {
// 鼠标移动
currentX = opt.e.clientX;
currentY = opt.e.clientY;
} else {
return; // 忽略其他情况
}
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
// 优化减少频繁的DOM操作
const deltaX = currentX - this.canvas.lastPosX;
const deltaY = currentY - this.canvas.lastPosY;
2026-03-09 13:44:32 +08:00
2026-03-12 15:51:18 +08:00
// 只有移动距离足够大时才更新
if (Math.abs(deltaX) < 1 && Math.abs(deltaY) < 1) {
return;
2026-03-09 13:44:32 +08:00
}
2026-03-12 15:51:18 +08:00
const vpt = this.canvas.viewportTransform;
vpt[4] += deltaX;
vpt[5] += deltaY;
// 优化:减少历史记录频率
const now = Date.now();
if (now - this.touchState.lastTouchTime > 16) {
// 约60fps
this.lastMousePositions.push({
x: currentX,
y: currentY,
time: now,
});
if (this.lastMousePositions.length > this.positionHistoryLimit) {
this.lastMousePositions.shift();
}
this.touchState.lastTouchTime = now;
}
this.canvas.setViewportTransform(vpt);
this.canvas.requestRenderAll(); // 使用requestRenderAll代替renderAll
this.canvas.lastPosX = currentX;
this.canvas.lastPosY = currentY;
opt.e.preventDefault();
2026-03-09 13:44:32 +08:00
}
});
// 触摸结束事件
this.canvas.on("mouse:up", (opt) => {
2026-03-12 15:51:18 +08:00
const currentTool = this.toolManager.currentTool.value;
2026-03-25 11:27:24 +08:00
if (this._handleMouseUp(opt)) {
2026-03-12 15:51:18 +08:00
} else if (currentTool === OperationType.PAN) {
// 重置触摸状态
this.touchState.isZooming = false;
this.touchState.initialDistance = 0;
this.handleDragEnd(opt, true);
2026-03-09 13:44:32 +08:00
}
});
// 添加原生触摸事件监听器作为备用方案
this.setupNativeTouchEvents();
}
/**
* 计算两个触摸点之间的距离
*/
getTouchDistance(touch1, touch2) {
const dx = touch1.clientX - touch2.clientX;
const dy = touch1.clientY - touch2.clientY;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* 设置原生触摸事件监听器备用方案- 专门处理iPad双指缩放
*/
setupNativeTouchEvents() {
const canvasElement = this.canvas.upperCanvasEl;
// 确保canvas元素支持触摸
canvasElement.style.touchAction = "none";
let lastTouchDistance = 0;
let lastZoom = 1;
// 原生touchstart事件 - 处理双指缩放初始化
canvasElement.addEventListener(
"touchstart",
(e) => {
if (this.toolManager.currentTool.value !== OperationType.PAN) return;
// 调试信息
if (process.env.NODE_ENV === "development") {
// console.log("iPad touchstart:", e.touches.length, "fingers");
}
if (e.touches.length === 2) {
// 双指触摸开始
this.touchState.isZooming = true;
lastTouchDistance = this.getTouchDistance(e.touches[0], e.touches[1]);
lastZoom = this.canvas.getZoom();
// 计算缩放中心点
const centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2;
const centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2;
this.touchState.zoomCenter = { x: centerX, y: centerY };
if (process.env.NODE_ENV === "development") {
// console.log("iPad双指缩放开始:", {
// distance: lastTouchDistance,
// zoom: lastZoom,
// center: this.touchState.zoomCenter,
// });
}
e.preventDefault();
}
},
{ passive: false }
);
// 原生touchmove事件 - 处理双指缩放(修复抖动问题)
canvasElement.addEventListener(
"touchmove",
(e) => {
if (this.toolManager.currentTool.value !== OperationType.PAN) return;
if (e.touches.length === 2 && this.touchState.isZooming) {
// 节流处理,避免过于频繁的缩放操作
const now = Date.now();
if (
now - this.touchState.lastZoomTime <
this.touchState.zoomThrottle
) {
return;
}
const currentDistance = this.getTouchDistance(
e.touches[0],
e.touches[1]
);
// 防止除零和异常值
if (lastTouchDistance === 0 || currentDistance === 0) {
return;
}
const scale = currentDistance / lastTouchDistance;
// 防止抖动:忽略微小的变化
if (Math.abs(scale - 1) < 0.02) {
return;
}
// 使用当前缩放值而不是初始缩放值,避免累积误差
const currentZoom = this.canvas.getZoom();
const newZoom = currentZoom * scale;
// 限制缩放范围
const clampedZoom = Math.max(0.1, Math.min(5, newZoom));
if (process.env.NODE_ENV === "development") {
// console.log("iPad双指缩放中:", {
// currentDistance,
// lastTouchDistance,
// scale,
// currentZoom,
// newZoom,
// clampedZoom,
// });
}
// 使用缩放中心点进行缩放
const point = new fabric.Point(
this.touchState.zoomCenter.x,
this.touchState.zoomCenter.y
);
this.canvas.zoomToPoint(point, clampedZoom);
// 更新基准距离和时间,避免累积误差
lastTouchDistance = currentDistance;
this.touchState.lastZoomTime = now;
e.preventDefault();
}
},
{ passive: false }
);
// 原生touchend事件 - 重置缩放状态
canvasElement.addEventListener(
"touchend",
(e) => {
if (this.toolManager.currentTool.value !== OperationType.PAN) return;
if (e.touches.length < 2) {
this.touchState.isZooming = false;
lastTouchDistance = 0;
}
e.preventDefault();
},
{ passive: false }
);
}
/**
* 处理拖动结束鼠标抬起或触摸结束
*/
handleDragEnd(opt, isTouch = false) {
2026-03-12 15:51:18 +08:00
const currentTool = this.toolManager.currentTool.value;
2026-03-25 11:27:24 +08:00
if (this._handleMouseUp(opt)) {
2026-03-12 15:51:18 +08:00
} else if (this.canvas.isDragging) {
2026-03-09 16:45:30 +08:00
// if (this.lastMousePositions.length > 1 && opt && opt.e) {
// this.animationManager.applyInertiaEffect(
// this.lastMousePositions,
// isTouch
// );
// }
2026-03-12 15:51:18 +08:00
this.canvas.isDragging = false;
const vpt = this.canvas.viewportTransform;
this.canvas.setViewportTransform(vpt);
this.canvas.renderAll();
2026-03-09 13:44:32 +08:00
}
}
setupSelectionEvents() {
// 监听对象选择事件
this.canvas.on("selection:created", (opt) => this.updateSelectedLayer(opt));
this.canvas.on("selection:updated", (opt) => this.updateSelectedLayer(opt));
2026-03-25 11:27:24 +08:00
this.canvas.on("selection:cleared", (opt) => this.clearSelectedElements(opt));
}
clearSelectedElements(opt) {
if (opt.deselected && opt.deselected.length > 0) {
opt.deselected.forEach((object) => {
if (object.type !== "group") return;
if (object._originPosition) delete object._originPosition
})
}
2026-03-09 13:44:32 +08:00
}
setupObjectEvents() {
// 监听对象变化事件,用于更新缩略图
2026-03-12 11:40:48 +08:00
// this.canvas.on("object:added", (e) => {
// if (this.thumbnailManager && e.target && e.target.id) {
// // 延迟更新以确保对象完全添加
// setTimeout(() => {
// // 现在图层就是元素本身,直接更新元素的缩略图
// this.thumbnailManager.generateLayerThumbnail(e.target.layerId);
// }, 300);
// }
// });
2026-03-09 13:44:32 +08:00
2026-03-12 11:40:48 +08:00
// 添加对象开始变换时的状态捕获
this.canvas.on("object:moving", (e) => {
2026-03-25 11:27:24 +08:00
console.log("object:moving", e);
const target = e.target;
if (target._objects && target._objects.length > 0) {
target._objects.forEach((object) => {
if (object.type !== "group") return;
if (!object._originPosition) return
const originTop = object._originPosition.top
const originLeft = object._originPosition.left
const originCpTop = object._originPosition.cpTop
const originCpLeft = object._originPosition.cpLeft
const top = object.top + target.top + target.height / 2;
const left = object.left + target.left + target.width / 2;
object.clipPath.set({
top: originCpTop + (top - originTop),
left: originCpLeft + (left - originLeft),
})
})
this.canvasManager.updateSubLayerClipPath()
}
2026-03-12 11:40:48 +08:00
});
this.canvas.on("object:scaling", (e) => {
// console.log("object:scaling", e);
});
this.canvas.on("object:rotating", (e) => {
// console.log("object:rotating", e);
});
this.canvas.on("object:skewing", (e) => {
// console.log("object:skewing", e);
});
2026-03-13 14:08:40 +08:00
this.canvas.on("object:modified", async (e) => {
2026-03-25 11:27:24 +08:00
// console.log("object:modified", e);
2026-03-23 16:43:08 +08:00
const target = e.target;
const id = target?.info?.id;
2026-03-13 14:08:40 +08:00
if (id) await this.layerManager.updateLayerThumbnailsById(id)
2026-03-12 11:40:48 +08:00
this.stateManager.recordState();
2026-03-09 13:44:32 +08:00
});
this.canvas.on("object:removed", (e) => {
2026-03-16 16:51:12 +08:00
// updateLayers(e);
2026-03-09 13:44:32 +08:00
});
}
setupDoubleClickEvents() {
// 双击处理
this.canvas.on("mouse:dblclick", (opt) => {
if (opt.target) {
// 双击对象的特殊处理
} else {
// 双击空白处重置缩放
if (this.animationManager) {
this.animationManager.resetZoom(true);
}
}
});
}
updateSelectedLayer(opt) {
const selected = opt.selected[0];
2026-03-12 11:40:48 +08:00
if (selected && opt.selected.length === 1) {
2026-03-11 15:34:56 +08:00
this.layerManager.setActiveID(selected?.info?.id, false)
2026-03-09 13:44:32 +08:00
}
}
/**
* 精确检测设备类型区分 PCMac平板和移动设备
* @private
* @returns {Object} 设备信息对象
*/
_detectDeviceType() {
const userAgent = navigator.userAgent.toLowerCase();
const platform = navigator.platform.toLowerCase();
const hasTouchSupport =
"ontouchstart" in window || navigator.maxTouchPoints > 0;
// 检测操作系统
const isMac = /mac|darwin/.test(platform) || /macintosh/.test(userAgent);
const isWindows = /win/.test(platform);
const isLinux = /linux/.test(platform) && !/android/.test(userAgent);
// 检测设备类型 - 修复iPad检测逻辑
const isMobile = /mobile|phone|android.*mobile|iphone/.test(userAgent);
// 修复iPad检测包括iOS iPad和Android平板
const isTablet =
/tablet|ipad|android(?!.*mobile)/.test(userAgent) ||
/ipad/.test(userAgent) ||
(navigator.maxTouchPoints &&
navigator.maxTouchPoints > 1 &&
/mac/.test(userAgent));
const isDesktop = !isMobile && !isTablet;
// 检测浏览器类型(用于特定优化)
const isSafari = /safari/.test(userAgent) && !/chrome/.test(userAgent);
const isChrome = /chrome/.test(userAgent);
const isFirefox = /firefox/.test(userAgent);
// 调试日志 - 仅在开发环境输出
if (process.env.NODE_ENV === "development") {
// console.log("设备检测结果:", {
// userAgent,
// platform,
// isMobile,
// isTablet,
// isDesktop,
// hasTouchSupport,
// maxTouchPoints: navigator.maxTouchPoints,
// });
}
return {
isMac,
isWindows,
isLinux,
isMobile,
isTablet,
isDesktop,
isSafari,
isChrome,
isFirefox,
hasTouchSupport,
// 判断是否应该使用触摸事件作为主要交互方式
preferTouchEvents: (isMobile || isTablet) && !isDesktop,
// 判断是否需要特殊的 Mac 触控板处理
needsMacTrackpadOptimization: isMac && isDesktop && hasTouchSupport,
};
}
/**
* 设置 Mac 专用的触摸手势处理
* 主要用于处理触控板的多指手势但不干扰双指滚动的缩放功能
*/
setupMacTouchGestures() {
// Mac 触控板专用:三指拖拽进行画布平移
let macGestureState = {
isThreeFingerDrag: false,
startX: 0,
startY: 0,
};
// 监听 Mac 专用的手势事件
this.canvas.upperCanvasEl.addEventListener(
"gesturestart",
(e) => {
// 阻止浏览器默认的手势行为,但保留双指缩放
if (e.scale !== 1) {
e.preventDefault();
}
},
{ passive: false }
);
this.canvas.upperCanvasEl.addEventListener(
"gesturechange",
(e) => {
// 只处理三指以上的手势,保留双指缩放给 mouse:wheel 事件
if (e.touches && e.touches.length >= 3) {
e.preventDefault();
if (!macGestureState.isThreeFingerDrag) {
macGestureState.isThreeFingerDrag = true;
macGestureState.startX = e.pageX;
macGestureState.startY = e.pageY;
this.canvas.isDragging = true;
this.canvas.lastPosX = e.pageX;
this.canvas.lastPosY = e.pageY;
this.stopInertiaAnimation(true);
} else {
// 执行三指拖拽平移
const vpt = this.canvas.viewportTransform;
vpt[4] += e.pageX - this.canvas.lastPosX;
vpt[5] += e.pageY - this.canvas.lastPosY;
2026-03-11 15:34:56 +08:00
this.canvas.setViewportTransform(vpt);
2026-03-09 13:44:32 +08:00
this.canvas.renderAll();
this.canvas.lastPosX = e.pageX;
this.canvas.lastPosY = e.pageY;
}
}
},
{ passive: false }
);
this.canvas.upperCanvasEl.addEventListener(
"gestureend",
(e) => {
if (macGestureState.isThreeFingerDrag) {
macGestureState.isThreeFingerDrag = false;
this.canvas.isDragging = false;
if (this.toolManager) {
this.toolManager.restoreSelectionState();
}
this.canvas.renderAll();
}
},
{ passive: false }
);
// 添加 Mac 专用的鼠标滚轮优化,确保双指滚动正常工作
this.setupMacScrollOptimization();
}
/**
* Mac 滚轮优化确保双指滚动正确触发缩放
*/
setupMacScrollOptimization() {
if (!this.deviceInfo.isMac) return;
// Mac 下的滚轮事件优化
let macScrollState = {
lastWheelTime: 0,
wheelTimeout: null,
};
// 监听原生滚轮事件,确保 Mac 双指滚动正确处理
this.canvas.upperCanvasEl.addEventListener(
"wheel",
(e) => {
const now = Date.now();
// Mac 双指滚动的特征:通常有较高的 deltaY 精度和连续性
const isMacTrackpadScroll =
this.deviceInfo.isMac &&
Math.abs(e.deltaY) < 100 && // 像素模式
e.deltaMode === 0; // 像素模式
if (isMacTrackpadScroll) {
// 清除之前的超时
if (macScrollState.wheelTimeout) {
clearTimeout(macScrollState.wheelTimeout);
}
// 确保这个事件会被 Fabric.js 的 mouse:wheel 正确处理
macScrollState.lastWheelTime = now;
// 设置短暂延迟,防止与触摸事件冲突
macScrollState.wheelTimeout = setTimeout(() => {
// 滚轮事件处理完成
}, 16); // 约一帧的时间
}
},
{ passive: true }
);
}
2026-03-12 15:51:18 +08:00
dispose() {
2026-03-17 17:17:48 +08:00
this.shapeToolManager?.dispose()
2026-03-13 11:18:36 +08:00
this.aiSelectboxToolManager?.dispose()
2026-03-12 15:51:18 +08:00
// 移除所有事件监听
this.canvas.off();
// 清理 Mac 专用的原生事件监听器
if (this.deviceInfo.isMac && this.canvas.upperCanvasEl) {
const upperCanvas = this.canvas.upperCanvasEl;
// 移除手势事件监听器
upperCanvas.removeEventListener("gesturestart", null);
upperCanvas.removeEventListener("gesturechange", null);
upperCanvas.removeEventListener("gestureend", null);
upperCanvas.removeEventListener("wheel", null);
}
// 清除计时器
if (this.longPressTimer) {
clearTimeout(this.longPressTimer);
this.longPressTimer = null;
}
// 停止所有动画
this.stopInertiaAnimation();
}
2026-03-09 13:44:32 +08:00
}