diff --git a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js index 6408878f..0817de9b 100644 --- a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js +++ b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js @@ -69,9 +69,14 @@ export class CanvasEventManager { // 让动画管理器自行处理冲突,避免过度干预 } else { // 非 Mac 设备的标准处理 - if (this.animationManager._panAnimation || this.animationManager._zoomAnimation) { - this.animationManager._wasPanning = !!this.animationManager._panAnimation; - this.animationManager._wasZooming = !!this.animationManager._zoomAnimation; + 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 }); } } @@ -123,7 +128,8 @@ export class CanvasEventManager { // Mac 设备优化:触控板滚动通常比鼠标滚轮更敏感 if (this.deviceInfo.isMac) { - const isMacTrackpadScroll = Math.abs(opt.e.deltaY) < 100 && opt.e.deltaMode === 0; + const isMacTrackpadScroll = + Math.abs(opt.e.deltaY) < 100 && opt.e.deltaMode === 0; if (isMacTrackpadScroll) { // Mac 触控板滚动更细腻,需要调整滚动因子 scrollFactor *= 0.8; // 降低滚动敏感度 @@ -178,7 +184,11 @@ export class CanvasEventManager { // 平滑停止任何正在进行的惯性动画 this.stopInertiaAnimation(true); - if (opt.e.altKey || opt.e.which === 2 || this.editorMode === OperationType.PAN) { + 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; @@ -228,122 +238,235 @@ export class CanvasEventManager { } /** - * 设置触摸事件处理 + * 设置触摸事件处理 - 修复iPad触摸事件支持 */ setupTouchEvents() { - // 触摸开始事件 - this.canvas.on("touch:gesture", (opt) => { - // 平滑停止任何正在进行的惯性动画 - this.stopInertiaAnimation(true); + // 启用Fabric.js的指针事件支持(适用于触摸设备) + this.canvas.enablePointerEvents = true; - if (opt.e.touches && opt.e.touches.length === 2) { - this.canvas.isDragging = true; - this.canvas.lastPosX = (opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2; - this.canvas.lastPosY = (opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2; + // 触摸状态管理 + this.touchState = { + isZooming: false, + initialDistance: 0, + initialZoom: 1, + lastTouchTime: 0, + }; - // 重置触摸位置历史 - this.dragStartTime = Date.now(); - this.lastMousePositions = []; - - if (this.canvas.isDragging) { - this.canvas.selection = false; - this.canvas.renderAll(); - } - opt.e.preventDefault(); + // 使用标准的mouse事件,Fabric.js会自动处理触摸转换 + this.canvas.on("mouse:down", (opt) => { + // 只在PAN模式下处理触摸事件 + if (this.editorMode !== OperationType.PAN) { + return; } - }); - // 单指触摸开始 - 处理拖动 - this.canvas.on("touch:drag", (opt) => { // 平滑停止任何正在进行的惯性动画 this.stopInertiaAnimation(true); - if (this.editorMode === OperationType.PAN) { + // 检查是否是触摸事件 + 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 = opt.e.touches[0].clientX; - this.canvas.lastPosY = opt.e.touches[0].clientY; + this.canvas.lastPosX = touches[0].clientX; + this.canvas.lastPosY = touches[0].clientY; this.dragStartTime = Date.now(); this.lastMousePositions = []; - if (this.canvas.isDragging) { - this.canvas.selection = false; - this.canvas.renderAll(); - } + 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("touch:gesture:update", (opt) => { + // 触摸移动事件 - 优化性能 + 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]); + const scale = currentDistance / this.touchState.initialDistance; + 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; - if (opt.e.touches && opt.e.touches.length === 2) { - const currentX = (opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2; - const currentY = (opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2; + let currentX, currentY; - const vpt = this.canvas.viewportTransform; - vpt[4] += currentX - this.canvas.lastPosX; - vpt[5] += currentY - this.canvas.lastPosY; + 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; // 忽略其他情况 + } - // 记录触摸位置和时间 - const now = Date.now(); + // 优化:减少频繁的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.canvas.renderAll(); - this.canvas.lastPosX = currentX; - this.canvas.lastPosY = currentY; - opt.e.preventDefault(); - } - }); - - // 单指拖动更新 - this.canvas.on("touch:drag:update", (opt) => { - if (!this.canvas.isDragging || this.editorMode !== OperationType.PAN) return; - - const currentX = opt.e.touches[0].clientX; - const currentY = opt.e.touches[0].clientY; - - const vpt = this.canvas.viewportTransform; - vpt[4] += currentX - this.canvas.lastPosX; - vpt[5] += currentY - this.canvas.lastPosY; - - // 记录触摸位置和时间 - const now = Date.now(); - this.lastMousePositions.push({ - x: currentX, - y: currentY, - time: now, - }); - - if (this.lastMousePositions.length > this.positionHistoryLimit) { - this.lastMousePositions.shift(); + this.touchState.lastTouchTime = now; } - this.canvas.renderAll(); + this.canvas.requestRenderAll(); // 使用requestRenderAll代替renderAll this.canvas.lastPosX = currentX; this.canvas.lastPosY = currentY; opt.e.preventDefault(); }); // 触摸结束事件 - this.canvas.on("touch:gesture:end", (opt) => { + 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.canvas.on("touch:drag:end", (opt) => { - 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); + } + + /** + * 设置原生触摸事件监听器(备用方案)- 优化性能 + */ + setupNativeTouchEvents() { + const canvasElement = this.canvas.upperCanvasEl; + + // 确保canvas元素支持触摸 + canvasElement.style.touchAction = "none"; + + // 原生touchstart事件 - 简化处理 + canvasElement.addEventListener( + "touchstart", + (e) => { + // 只在PAN模式下处理 + if (this.editorMode === OperationType.PAN) { + e.preventDefault(); + } + }, + { passive: false } + ); + + // 原生touchmove事件 - 简化处理 + canvasElement.addEventListener( + "touchmove", + (e) => { + // 只在PAN模式下处理 + if (this.editorMode === OperationType.PAN) { + e.preventDefault(); + } + }, + { passive: false } + ); + + // 原生touchend事件 - 简化处理 + canvasElement.addEventListener( + "touchend", + (e) => { + // 只在PAN模式下处理 + if (this.editorMode === OperationType.PAN) { + e.preventDefault(); + } + }, + { passive: false } + ); } /** @@ -353,7 +476,10 @@ export class CanvasEventManager { if (this.canvas.isDragging) { // 使用动画管理器处理惯性效果 if (this.lastMousePositions.length > 1 && opt && opt.e) { - this.animationManager.applyInertiaEffect(this.lastMousePositions, isTouch); + this.animationManager.applyInertiaEffect( + this.lastMousePositions, + isTouch + ); } } @@ -388,10 +514,22 @@ export class CanvasEventManager { }); // 添加对象开始变换时的状态捕获 - 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: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) => { // 移除调试日志 @@ -566,7 +704,8 @@ export class CanvasEventManager { } // 验证是否需要合并 const hasExistingObjects = - Array.isArray(activeLayer.fabricObjects) && activeLayer.fabricObjects.length > 0; + Array.isArray(activeLayer.fabricObjects) && + activeLayer.fabricObjects.length > 0; const hasNewImage = !!fabricImage; if (!hasExistingObjects && !hasNewImage) { @@ -584,7 +723,10 @@ export class CanvasEventManager { try { console.log(`开始合并图层 ${activeLayer.name} 中的对象为组...`); - const command = await this.layerManager.LayerObjectsToGroup(activeLayer, fabricImage); + const command = await this.layerManager.LayerObjectsToGroup( + activeLayer, + fabricImage + ); // 设置命令的撤销状态 if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销 @@ -713,16 +855,23 @@ export class CanvasEventManager { _detectDeviceType() { const userAgent = navigator.userAgent.toLowerCase(); const platform = navigator.platform.toLowerCase(); - const hasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0; + 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); - const isTablet = /tablet|ipad|android(?!.*mobile)/.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; // 检测浏览器类型(用于特定优化) @@ -730,6 +879,19 @@ export class CanvasEventManager { 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,