import { isBoolean } from "lodash-es"; import { TransformCommand } from "../../commands/StateCommands"; import { generateId } from "../../utils/helper"; import { OperationType, OperationTypes } from "../../utils/layerHelper"; export class CanvasEventManager { constructor(canvas, options = {}) { this.canvas = canvas; this.canvasManager = options.canvasManager; this.toolManager = options.toolManager || null; this.animationManager = options.animationManager; this.thumbnailManager = options.thumbnailManager; this.editorMode = options.editorMode || OperationType.SELECT; 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; // 初始化所有事件 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(); } // 共享事件 this.setupSelectionEvents(); this.setupObjectEvents(); this.setupDoubleClickEvents(); // this.setupHandlePathCreated(); } 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; } } } /** * 设置鼠标事件处理 */ setupMouseEvents() { // 鼠标按下事件 this.canvas.on("mouse:down", (opt) => { // console.log("==========鼠标按下",opt) // 平滑停止任何正在进行的惯性动画 this.stopInertiaAnimation(true); // if (opt.e.which === 3 && this.editorMode === OperationType.SELECT) { // console.log("==========选择模式鼠标右击画布对象") // } else if ( opt.e.altKey || opt.e.which === 2 || this.editorMode === OperationType.PAN ) { this.canvas.isDragging = true; this.canvas.lastPosX = opt.e.clientX; this.canvas.lastPosY = opt.e.clientY; this.canvas.defaultCursor = "grabbing"; // 记录拖动开始时间和位置,用于计算速度 this.dragStartTime = Date.now(); this.lastMousePositions = []; // 重置位置历史 if (this.canvas.isDragging) { this.canvas.selection = false; this.canvas.renderAll(); } } }); // 鼠标移动事件 this.canvas.on("mouse:move", (opt) => { if (!this.canvas.isDragging) return; 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, }); // 保持历史记录在限定数量内 if (this.lastMousePositions.length > this.positionHistoryLimit) { this.lastMousePositions.shift(); } this.canvas.renderAll(); this.canvas.lastPosX = opt.e.clientX; this.canvas.lastPosY = opt.e.clientY; }); // 鼠标抬起事件 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模式下处理触摸事件 if (this.editorMode !== OperationType.PAN) { return; } // 平滑停止任何正在进行的惯性动画 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(); // 计算缩放中心点 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 }; 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; this.dragStartTime = Date.now(); this.lastMousePositions = []; 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; this.dragStartTime = Date.now(); this.lastMousePositions = []; this.canvas.selection = false; opt.e.preventDefault(); } }); // 触摸移动事件 - 优化性能 this.canvas.on("mouse:move", (opt) => { // 只在PAN模式下处理 if (this.editorMode !== OperationType.PAN) { return; } // 检查是否是触摸事件 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 ) { // 双指缩放处理 - 修复抖动问题 const currentDistance = this.getTouchDistance(touches[0], touches[1]); // 防止除零和异常值 if (this.touchState.initialDistance === 0 || currentDistance === 0) { return; } const scale = currentDistance / this.touchState.initialDistance; // 防止抖动:忽略微小的变化 if (Math.abs(scale - 1) < 0.01) { return; } const newZoom = this.touchState.initialZoom * scale; // 限制缩放范围 const clampedZoom = Math.max(0.1, Math.min(5, newZoom)); // 使用缩放中心点进行缩放 const point = new fabric.Point( this.touchState.zoomCenter.x, this.touchState.zoomCenter.y ); this.canvas.zoomToPoint(point, clampedZoom); opt.e.preventDefault(); return; } if (!this.canvas.isDragging) return; let currentX, currentY; 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; // 忽略其他情况 } // 优化:减少频繁的DOM操作 const deltaX = currentX - this.canvas.lastPosX; const deltaY = currentY - this.canvas.lastPosY; // 只有移动距离足够大时才更新 if (Math.abs(deltaX) < 1 && Math.abs(deltaY) < 1) { return; } 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.requestRenderAll(); // 使用requestRenderAll代替renderAll this.canvas.lastPosX = currentX; this.canvas.lastPosY = currentY; opt.e.preventDefault(); }); // 触摸结束事件 this.canvas.on("mouse:up", (opt) => { // 只在PAN模式下处理 if (this.editorMode !== OperationType.PAN) { return; } // 重置触摸状态 this.touchState.isZooming = false; this.touchState.initialDistance = 0; this.handleDragEnd(opt, true); }); // 添加原生触摸事件监听器作为备用方案 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.editorMode !== 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.editorMode !== 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.editorMode !== OperationType.PAN) return; if (e.touches.length < 2) { this.touchState.isZooming = false; lastTouchDistance = 0; } e.preventDefault(); }, { passive: false } ); } /** * 处理拖动结束(鼠标抬起或触摸结束) */ handleDragEnd(opt, isTouch = false) { if (this.canvas.isDragging) { // 使用动画管理器处理惯性效果 if (this.lastMousePositions.length > 1 && opt && opt.e) { this.animationManager.applyInertiaEffect( this.lastMousePositions, isTouch ); } } this.canvas.isDragging = false; if (this.toolManager) { this.toolManager.restoreSelectionState(); // 恢复选择状态 } this.canvas.renderAll(); } setupSelectionEvents() { // 监听对象选择事件 this.canvas.on("selection:created", (opt) => this.updateSelectedLayer(opt)); this.canvas.on("selection:updated", (opt) => this.updateSelectedLayer(opt)); // this.canvas.on("selection:cleared", () => this.clearSelectedElements()); } setupObjectEvents() { // 监听对象变化事件,用于更新缩略图 this.canvas.on("object:added", (e) => { if (this.thumbnailManager && e.target && e.target.id) { // 延迟更新以确保对象完全添加 setTimeout(() => { // 现在图层就是元素本身,直接更新元素的缩略图 this.thumbnailManager.generateLayerThumbnail(e.target.layerId); }, 300); } }); // 添加对象开始变换时的状态捕获 this.canvas.on( "object:moving", this._captureInitialTransformState.bind(this) ); this.canvas.on( "object:scaling", this._captureInitialTransformState.bind(this) ); this.canvas.on( "object:rotating", this._captureInitialTransformState.bind(this) ); this.canvas.on( "object:skewing", this._captureInitialTransformState.bind(this) ); this.canvas.on("object:modified", (e) => { // 移除调试日志 // console.log("object:modified", e); const activeObj = e.target || this.canvas.getActiveObject(); if (activeObj && this.layerManager?.commandManager) { // 使用新的轻量级 TransformCommand 替代完整状态保存 // 检查对象是否有初始变换状态记录 if (activeObj._initialTransformState) { // 创建并执行 TransformCommand,只记录变换属性的变化 const transformCmd = new TransformCommand({ canvas: this.canvas, objectId: activeObj.id, initialState: activeObj._initialTransformState, finalState: TransformCommand.captureTransformState(activeObj), objectType: activeObj.type, name: `变换 ${activeObj.type || "对象"}`, layerManager: this.layerManager, layers: this.layers, lastSelectLayerId: this.lastSelectLayerId, }); // 执行并将命令添加到历史栈 this.layerManager.commandManager.execute(transformCmd, { name: "对象修改", }); // 清除临时状态记录 delete activeObj._initialTransformState; } }else{ this.canvasManager.changeCanvas(); } if (this.thumbnailManager && e.target) { if (e.target.id) { this.updateLayerThumbnail(e.target.id, e.target); // 如果该元素是分组图层的一部分,也更新分组图层的缩略图 if (e.target.parentId) { this.updateLayerThumbnail(e.target.parentId); } } } }); this.canvas.on("object:removed", (e) => { if (this.thumbnailManager && e.target) { if (e.target.id) { // 如果该元素是分组图层的一部分,也更新分组图层的缩略图 if (e.target.parentId) { // setTimeout(() => this.updateLayerThumbnail(e.target.parentId), 50); this.thumbnailManager.generateLayerThumbnail(e.target.parentId); } } } }); // // 鼠标抬起时,检查是否需要保存状态 // this.canvas.on("mouse:up", (e) => { // // 只在选择模式下处理对象变换的状态保存 // if (this.editorMode !== OperationType.SELECT) { // // 绘画、擦除等模式通过各自的命令管理状态,不需要在这里保存 // return; // } // const activeObj = this.canvas.getActiveObject(); // if ( // activeObj && // activeObj._stateRecord && // activeObj._stateRecord.isModifying // ) { // const original = activeObj._stateRecord.originalState; // // 检查是否是真正的变换操作(移动、缩放、旋转) // const hasTransformChanged = // original.left !== activeObj.left || // original.top !== activeObj.top || // original.scaleX !== activeObj.scaleX || // original.scaleY !== activeObj.scaleY || // original.angle !== activeObj.angle; // // 只有在对象发生变换且不是命令执行过程中时才保存状态 // if (hasTransformChanged && this.layerManager) { // // 立即保存状态,而不是延迟执行 // this.layerManager.saveCanvasState(); // delete activeObj._stateRecord; // } else { // // 清理状态记录,即使没有保存状态 // delete activeObj._stateRecord; // } // } // }); } setupDoubleClickEvents() { // 双击处理 this.canvas.on("mouse:dblclick", (opt) => { if (opt.target) { // 双击对象的特殊处理 } else { // 双击空白处重置缩放 if (this.animationManager) { this.animationManager.resetZoom(true); } } }); } setupLongPress(callback) { this.canvas.on("mouse:down", (opt) => { if (!opt.target) return; this.longPressTimer = setTimeout(() => { callback(opt); }, this.longPressThreshold); }); this.canvas.on("mouse:up", () => { clearTimeout(this.longPressTimer); }); this.canvas.on("mouse:move", () => { clearTimeout(this.longPressTimer); }); } // 设置路径创建事件 setupHandlePathCreated() { // 在 CanvasEventManager 的构造函数或初始化方法中 // this.canvas.on("path:created", this._handlePathCreated.bind(this)); } _handlePathCreated(e) { // // 获取新创建的路径对象 // const path = e.path; // // 设置路径的ID和其他属性 // path.id = generateId(); // 生成唯一ID // // 获取当前活动图层 // const activeLayer = this.layerManager.getActiveLayer(); // // 将路径对象绑定到当前活动图层 // if (activeLayer) { // // 设置路径的图层ID // path.layerId = activeLayer.id; // // 更新图层对象列表 // if (!activeLayer.fabricObjects) activeLayer.fabricObjects = []; // activeLayer.fabricObjects.push(path); // // 更新图层缩略图 // if (this.thumbnailManager) { // this.thumbnailManager.generateLayerThumbnail(activeLayer.id); // } // } } /** * 合并图层中的对象为组以提高性能 * @param {Object} options 合并选项 * @param {fabric.Image} options.fabricImage 新的图像对象 * @param {Object} options.activeLayer 当前活动图层 * @private */ async mergeLayerObjectsForPerformance({ fabricImage, activeLayer, options }) { // 确保有命令管理器 if (!this.layerManager || !this.layerManager.commandManager) { console.warn("合并对象失败:没有命令管理器"); return; } // 确保有活动图层 if (!activeLayer) { console.warn("合并对象失败:没有活动图层"); return; } // 验证是否需要合并 const hasExistingObjects = Array.isArray(activeLayer.fabricObjects) && activeLayer.fabricObjects.length > 0; const hasNewImage = !!fabricImage; if (!hasExistingObjects && !hasNewImage) { // console.log("没有对象需要合并"); return; } // 如果只有一个新图像且图层为空,直接添加到图层 if (hasNewImage && !hasExistingObjects) { this.layerManager.addObjectToLayer(fabricImage, activeLayer.id, options); return; } // 执行高保真合并操作 try { // console.log(`开始合并图层 ${activeLayer.name} 中的对象为组...`); const command = await this.layerManager.LayerObjectsToGroup( activeLayer, fabricImage ); // 设置命令的撤销状态 if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销 this.layerManager?.commandManager?.execute?.(command, { name: `合并图层 ${activeLayer.name} 中的对象为组`, }); } catch (error) { console.error("合并图层对象时发生错误:", error); // 降级处理:如果合并失败,至少保证新图像能添加到图层 if (fabricImage && this.layerManager) { // console.log("执行降级处理:直接添加图像到图层"); this.layerManager.addObjectToLayer(fabricImage, activeLayer.id); } } } updateSelectedLayer(opt) { const selected = opt.selected[0]; if (selected) { this.layerManager.activeLayerId.value = selected.layerId; } } // clearSelectedElements() { // this.activeElementId.value = null; // } // 更新图层缩略图 updateLayerThumbnail(layerId) { if (!this.thumbnailManager || !layerId || !this.layers) return; const layer = this.layers.value.find((l) => l.id === layerId); if (layer) { this.thumbnailManager.generateLayerThumbnail(layer); } } // 更新子元素组合缩略图 updateLayerChidrenThumbnail(layerId, fabricObject) { if (!this.thumbnailManager || !fabricObject || !this.layers) return; // 查找对应的图层(现在元素就是图层) const layer = this.layers.value.find( (l) => l.fabricObjects && l.fabricObjects?.[0]?.id === layerId ); if (layer) { // 生成图层缩略图 this.thumbnailManager.generateLayerThumbnail(layer); } } /** * 设置编辑器模式 * @param {string} mode 编辑器模式 */ setEditorMode(mode) { if (!OperationTypes.includes(mode)) { console.warn(`不支持的编辑器模式: ${mode}`); return; } // 切换工具时,立即停止任何惯性动画,但使用平滑过渡 this.stopInertiaAnimation(true); this.editorMode = mode; // 如果切换到选择模式,还原鼠标指针 if (mode === OperationType.SELECT) { this.canvas.defaultCursor = "default"; } else if (mode === OperationType.PAN) { this.canvas.defaultCursor = "grab"; } } dispose() { // 移除所有事件监听 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(); } /** * 捕获对象开始变换时的初始状态 * @private * @param {Object} e 事件对象 */ _captureInitialTransformState(e) { const obj = e.target; // 只在首次触发变换事件时记录初始状态 if (obj && !obj._initialTransformState && obj.id) { // 捕获对象的初始变换状态 obj._initialTransformState = TransformCommand.captureTransformState(obj); // 添加调试日志(可选) // console.log(`捕获对象 ${obj.id} (${obj.type}) 的初始变换状态`); } const arrs = []; if (e.target._objects) { e.target._objects.forEach((v) => arrs.push(v)); } else { arrs.push(e.target); } this.canvasManager.beforeChangeCanvas(arrs); } /** * 精确检测设备类型,区分 PC、Mac、平板和移动设备 * @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; 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 } ); } }