fix: 修复ipad平移手势操作
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user