diff --git a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js index 0817de9b..8306e125 100644 --- a/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js +++ b/src/component/Canvas/CanvasEditor/managers/events/CanvasEventManager.js @@ -250,8 +250,33 @@ export class CanvasEventManager { 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模式下处理触摸事件 @@ -325,9 +350,21 @@ export class CanvasEventManager { 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; // 限制缩放范围 @@ -424,7 +461,7 @@ export class CanvasEventManager { } /** - * 设置原生触摸事件监听器(备用方案)- 优化性能 + * 设置原生触摸事件监听器(备用方案)- 专门处理iPad双指缩放 */ setupNativeTouchEvents() { const canvasElement = this.canvas.upperCanvasEl; @@ -432,38 +469,126 @@ export class CanvasEventManager { // 确保canvas元素支持触摸 canvasElement.style.touchAction = "none"; - // 原生touchstart事件 - 简化处理 + let lastTouchDistance = 0; + let lastZoom = 1; + + // 原生touchstart事件 - 处理双指缩放初始化 canvasElement.addEventListener( "touchstart", (e) => { - // 只在PAN模式下处理 - if (this.editorMode === OperationType.PAN) { + 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事件 - 简化处理 + // 原生touchmove事件 - 处理双指缩放(修复抖动问题) canvasElement.addEventListener( "touchmove", (e) => { - // 只在PAN模式下处理 - if (this.editorMode === OperationType.PAN) { + 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事件 - 简化处理 + // 原生touchend事件 - 重置缩放状态 canvasElement.addEventListener( "touchend", (e) => { - // 只在PAN模式下处理 - if (this.editorMode === OperationType.PAN) { - e.preventDefault(); + if (this.editorMode !== OperationType.PAN) return; + + if (e.touches.length < 2) { + this.touchState.isZooming = false; + lastTouchDistance = 0; } + + e.preventDefault(); }, { passive: false } );