Files
FiDA_Front/src/components/Canvas/DepthCanvas/manager/events/CanvasEventManager.js
2026-03-27 09:50:09 +08:00

1004 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { isBoolean } from "lodash-es";
import { OperationType, OperationTypes } from "../../tools/layerHelper";
import { ShapeToolManager } from "../ShapeToolManager"
import { AISelectboxToolManager } from "../AISelectboxToolManager"
export class CanvasEventManager {
constructor(canvas, options = {}) {
this.canvas = canvas;
this.canvasManager = options.canvasManager;
this.toolManager = options.toolManager || null;
this.stateManager = options.stateManager || null;
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;
const managers = {
canvasManager: this.canvasManager,
stateManager: this.stateManager,
toolManager: this.toolManager,
layerManager: this.layerManager,
}
this.shapeToolManager = new ShapeToolManager(managers)
this.aiSelectboxToolManager = new AISelectboxToolManager(managers)
this.stateManager.setManager({ aiSelectboxToolManager: this.aiSelectboxToolManager })
// 初始化所有事件
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();
}
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;
}
}
}
_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
}
/**
* 设置鼠标事件处理
*/
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
const currentTool = this.toolManager.currentTool.value;
if (this._handleMouseDown(opt)) {
} else if (opt.e.altKey || opt.e.which === 2 || currentTool === 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 = []; // 重置位置历史
this.canvas.selection = false;
this.canvas.renderAll();
}
});
// 鼠标移动事件
this.canvas.on("mouse:move", (opt) => {
const currentTool = this.toolManager.currentTool.value;
if (this._handleMouseMove(opt)) {
} 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,
});
// 保持历史记录在限定数量内
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;
}
});
// 鼠标抬起事件
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模式下处理触摸事件
const currentTool = this.toolManager.currentTool.value;
if (this._handleMouseDown(opt)) {
} 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();
// 计算缩放中心点
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) => {
const currentTool = this.toolManager.currentTool.value;
if (this._handleMouseMove(opt)) {
} 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);
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.setViewportTransform(vpt);
this.canvas.requestRenderAll(); // 使用requestRenderAll代替renderAll
this.canvas.lastPosX = currentX;
this.canvas.lastPosY = currentY;
opt.e.preventDefault();
}
});
// 触摸结束事件
this.canvas.on("mouse:up", (opt) => {
const currentTool = this.toolManager.currentTool.value;
if (this._handleMouseUp(opt)) {
} else if (currentTool === OperationType.PAN) {
// 重置触摸状态
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.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) {
const currentTool = this.toolManager.currentTool.value;
if (this._handleMouseUp(opt)) {
} else if (this.canvas.isDragging) {
// if (this.lastMousePositions.length > 1 && opt && opt.e) {
// this.animationManager.applyInertiaEffect(
// this.lastMousePositions,
// isTouch
// );
// }
this.canvas.isDragging = false;
const vpt = this.canvas.viewportTransform;
this.canvas.setViewportTransform(vpt);
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", (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
})
}
}
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", (e) => {
// 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()
}
});
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);
});
this.canvas.on("object:modified", async (e) => {
// console.log("object:modified", e);
const target = e.target;
const id = target?.info?.id;
if (id) await this.layerManager.updateLayerThumbnailsById(id)
this.stateManager.recordState();
});
this.canvas.on("object:removed", (e) => {
// updateLayers(e);
});
this.canvas.on("erasing:start", (e) => {
// console.log("erasing:start", e);
});
this.canvas.on("erasing:end", async (e) => {
// console.log("erasing:end", e);
const targets = e.targets;
var isRecord = false;
for (let i = 0; i < targets.length; i++) {
const target = targets[i];
const id = target?.info?.id;
if (id) {
isRecord = true;
await this.layerManager.updateLayerThumbnailsById(id)
}
}
if (isRecord) this.stateManager.recordState();
});
}
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];
if (selected && opt.selected.length === 1) {
this.layerManager.setActiveID(selected?.info?.id, false)
}
}
/**
* 精确检测设备类型,区分 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.setViewportTransform(vpt);
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 }
);
}
dispose() {
this.shapeToolManager?.dispose()
this.aiSelectboxToolManager?.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();
}
}