diff --git a/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts b/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts index 375dcd0..3e46d37 100644 --- a/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts +++ b/src/components/Canvas/DepthCanvas/manager/CanvasManager.ts @@ -1,7 +1,7 @@ import { fabric } from 'fabric-with-all' import { ref } from 'vue' import { createCanvas } from '../tools/canvasFactory' -import { AnimationManager } from './animationManager' +import { AnimationManager } from './AnimationManager' import { detectDeviceType } from '../tools/index' import { CanvasEventManager } from "./events/CanvasEventManager"; import { OperationType } from '../tools/layerHelper' diff --git a/src/components/Canvas/DepthCanvas/manager/animationManager.ts b/src/components/Canvas/DepthCanvas/manager/animationManager.ts deleted file mode 100644 index 999dc99..0000000 --- a/src/components/Canvas/DepthCanvas/manager/animationManager.ts +++ /dev/null @@ -1,843 +0,0 @@ -import { gsap } from 'gsap' - -/** - * 画布动画管理器 - * 负责处理画布平移、缩放等动画效果 - */ -export class AnimationManager { - /** - * 创建动画管理器 - * @param {fabric.Canvas} canvas fabric.js画布实例 - * @param {Object} options 配置选项 - */ - constructor(canvas, options = {}) { - this.canvas = canvas - this.currentZoom = options.currentZoom || { value: 100 } - - // 动画相关属性 - this._zoomAnimation = null - this._panAnimation = null - this._lastWheelTime = 0 - this._lastWheelProcessTime = 0 // 上次处理wheel事件的时间 - this._wheelEvents = [] - - // 检测设备类型,Mac设备使用更短的节流时间确保响应性 - this._isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0 - this._wheelThrottleTime = this._isMac - ? options.wheelThrottleTime || 8 // Mac设备使用更短的节流时间 - : options.wheelThrottleTime || 30 - - this._accumulatedWheelDelta = 0 // 累积滚轮增量 - this._wheelAccumulationTimeout = null // 滚轮累积超时 - - // Mac设备使用更短的累积时间窗口,确保及时响应 - this._wheelAccumulationTime = this._isMac ? 60 : 120 // 滚轮累积时间窗口(毫秒) - - // 添加新的状态跟踪变量 - this._wasPanning = false // 是否有平移动画正在进行 - this._wasZooming = false // 是否有缩放动画正在进行 - this._combinedAnimation = null // 组合动画引用 - - // Mac特有的动画优化变量 - 使用最小防抖机制 - if (this._isMac) { - this._lastMacAnimationTime = 0 // 上次Mac动画时间 - this._macAnimationCooldown = 2 // 最小的动画冷却时间,确保最大响应性 - } - - // 初始化GSAP默认配置 - gsap.defaults({ - ease: options.defaultEase || (this._isMac ? 'power2.out' : 'power2.out'), // Mac使用简单高效的缓动 - duration: options.defaultDuration || (this._isMac ? 0.3 : 0.3), // Mac使用标准持续时间 - overwrite: 'auto' // 自动覆盖同一对象上的动画 - }) - } - - /** - * 使用 GSAP 实现平滑缩放动画 - * @param {Object} point 缩放中心点 {x, y} - * @param {Number} targetZoom 目标缩放值 - * @param {Object} options 动画选项 - */ - animateZoom(point, targetZoom, options = {}) { - if (!this.canvas) return - - // 限制缩放范围 - targetZoom = Math.min(Math.max(targetZoom, 0.1), 20) - - // 当前缩放值 - const currentZoom = this.canvas.getZoom() - - // 如果变化太小,直接应用缩放 - if (Math.abs(targetZoom - currentZoom) < 0.01) { - this._applyZoom(point, targetZoom) - return - } - - // 停止任何进行中的缩放动画 - if (this._zoomAnimation) { - // 不是直接 kill,而是获取当前进度值作为新的起点 - const currentProgress = this._zoomAnimation.progress() - const currentZoomValue = this._zoomAnimation.targets()[0].value - this._zoomAnimation.kill() - this._zoomAnimation = null - - // 从当前过渡中的值开始新动画,而不是从最初的值 - const zoomObj = { value: currentZoomValue } - const currentVpt = [...this.canvas.viewportTransform] - - // 计算过渡动画持续时间 - 根据当前值到目标值的距离比例 - const progressRatio = - Math.abs(targetZoom - currentZoomValue) / Math.abs(targetZoom - currentZoom) - const duration = options.duration || 0.3 * progressRatio - - // 计算缩放后目标位置需要的修正,保持缩放点不变 - const animOptions = { - value: targetZoom, - duration: duration, - ease: options.ease || 'power2.out', - onUpdate: () => { - // 更新缩放值显示 - this.currentZoom.value = Math.round(zoomObj.value * 100) - - // 计算过渡中的变换矩阵 - const zoom = zoomObj.value - const scale = zoom / currentZoomValue - const currentScaleFactor = scale - - // 应用变换 - const vpt = this.canvas.viewportTransform - vpt[0] = currentVpt[0] * scale - vpt[3] = currentVpt[3] * scale - - // 应用平移修正以保持缩放点 - const adjustX = (1 - currentScaleFactor) * point.x - const adjustY = (1 - currentScaleFactor) * point.y - vpt[4] = currentVpt[4] * scale + adjustX - vpt[5] = currentVpt[5] * scale + adjustY - - this.canvas.renderAll() - }, - onComplete: () => { - this._zoomAnimation = null - - // 确保最终状态准确 - this._applyZoom(point, targetZoom, true) - } - } - - // 启动 GSAP 动画 - this._zoomAnimation = gsap.to(zoomObj, animOptions) - return - } - - // 如果没有正在进行的动画,创建新的缩放动画 - const zoomObj = { value: currentZoom } - const currentVpt = [...this.canvas.viewportTransform] - - // 计算缩放后目标位置需要的修正,保持缩放点不变 - const scaleFactor = targetZoom / currentZoom - const invertedScaleFactor = 1 / scaleFactor - - // 这个数学公式确保缩放点在屏幕上的位置保持不变 - const dx = point.x - point.x * invertedScaleFactor - const dy = point.y - point.y * invertedScaleFactor - - // 创建动画配置 - const animOptions = { - value: targetZoom, - duration: options.duration || 0.3, - ease: options.ease || (this._isMac ? 'expo.out' : 'power2.out'), // Mac使用更平滑的缓动 - onUpdate: () => { - // 更新缩放值显示 - this.currentZoom.value = Math.round(zoomObj.value * 100) - - // 计算过渡中的变换矩阵 - const zoom = zoomObj.value - const scale = zoom / currentZoom - const currentScaleFactor = scale - - // 应用变换 - const vpt = this.canvas.viewportTransform - vpt[0] = currentVpt[0] * scale - vpt[3] = currentVpt[3] * scale - - // 应用平移修正以保持缩放点 - const adjustX = (1 - currentScaleFactor) * point.x - const adjustY = (1 - currentScaleFactor) * point.y - vpt[4] = currentVpt[4] * scale + adjustX - vpt[5] = currentVpt[5] * scale + adjustY - - this.canvas.renderAll() - }, - onComplete: () => { - this._zoomAnimation = null - - // 确保最终状态准确 - this._applyZoom(point, targetZoom, true) - } - } - - // 启动 GSAP 动画 - this._zoomAnimation = gsap.to(zoomObj, animOptions) - } - - /** - * 应用缩放(内部使用) - * @private - */ - _applyZoom(point, zoom, skipUpdate = false) { - if (!skipUpdate) { - this.currentZoom.value = Math.round(zoom * 100) - } - this.canvas.zoomToPoint(point, zoom) - } - - /** - * 使用 GSAP 实现平滑平移动画 - * @param {Object} targetPosition 目标位置 {x, y} - * @param {Object} options 动画选项 - */ - animatePan(targetPosition, options = {}) { - if (!this.canvas) return - - // 停止任何进行中的平移动画 - if (this._panAnimation) { - this._panAnimation.kill() - } - - const currentVpt = [...this.canvas.viewportTransform] - const position = { - x: -currentVpt[4], - y: -currentVpt[5] - } - - // 计算平移距离 - const dx = targetPosition.x - position.x - const dy = targetPosition.y - position.y - - // 如果距离太小,直接应用平移 - if (Math.abs(dx) < 1 && Math.abs(dy) < 1) { - this._applyPan(targetPosition.x, targetPosition.y) - return - } - - // 创建动画配置 - const animOptions = { - x: targetPosition.x, - y: targetPosition.y, - duration: options.duration || 0.3, - ease: options.ease || (this._isMac ? 'circ.out' : 'power2.out'), // Mac使用更柔和的缓动 - onUpdate: () => { - this._applyPan(position.x, position.y) - }, - onComplete: () => { - this._panAnimation = null - // 确保最终位置准确 - this._applyPan(targetPosition.x, targetPosition.y) - } - } - - // 启动 GSAP 动画 - this._panAnimation = gsap.to(position, animOptions) - } - - /** - * 应用平移(内部使用) - * @private - */ - _applyPan(x, y) { - if (!this.canvas) return - - const vpt = this.canvas.viewportTransform - vpt[4] = -x - vpt[5] = -y - - this.canvas.renderAll() - } - - /** - * 使用动画平移到指定元素 - * @param {Object} elementId 元素ID - */ - panToElement(elementId) { - if (!this.canvas) return - - const obj = this.canvas.getObjects().find((obj) => obj.id === elementId) - if (!obj) return - - const zoom = this.canvas.getZoom() - const center = obj.getCenterPoint() - - // 计算目标中心位置 - const targetX = center.x * zoom - this.canvas.width / 2 - const targetY = center.y * zoom - this.canvas.height / 2 - - // 动画平移 - this.animatePan( - { x: targetX, y: targetY }, - { - duration: 0.6, - ease: this._isMac ? 'back.out(0.3)' : 'power3.out' // Mac使用轻微回弹效果 - } - ) - } - - /** - * 重置缩放(带平滑动画) - * @param {Boolean} animated 是否使用动画 - */ - async resetZoom(animated = true) { - return new Promise((resolve) => { - if (animated) { - // 停止任何进行中的动画 - if (this._zoomAnimation) { - this._zoomAnimation.kill() - } - if (this._panAnimation) { - this._panAnimation.kill() - } - - const center = { - x: this.canvas.width / 2, - y: this.canvas.height / 2 - } - - // 获取当前变换矩阵 - const currentVpt = [...this.canvas.viewportTransform] - const currentZoom = this.canvas.getZoom() - - // 创建一个对象来动画整个视图变换 - const viewTransform = { - zoom: currentZoom, - panX: currentVpt[4], - panY: currentVpt[5] - } - - // 使用GSAP同时动画缩放和平移 - gsap.to(viewTransform, { - zoom: 1, - panX: 0, - panY: 0, - duration: 0.5, - ease: this._isMac ? 'back.out(0.2)' : 'power3.out', // Mac使用轻微回弹效果 - onUpdate: () => { - // 更新缩放显示值 - this.currentZoom.value = Math.round(viewTransform.zoom * 100) - - // 应用新的变换 - const vpt = this.canvas.viewportTransform - vpt[0] = viewTransform.zoom - vpt[3] = viewTransform.zoom - vpt[4] = viewTransform.panX - vpt[5] = viewTransform.panY - - this.canvas.renderAll() - }, - onComplete: () => { - // 确保最终状态准确 - this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]) - this.currentZoom.value = 100 - this._zoomAnimation = null - this._panAnimation = null - resolve() - } - }) - } else { - this.canvas.setViewportTransform([1, 0, 0, 1, 0, 0]) - this.currentZoom.value = 100 - resolve() - } - }) - } - - /** - * 处理鼠标滚轮缩放 - * @param {Object} opt 事件对象 - */ - handleMouseWheel(opt) { - const now = Date.now() - let delta = opt.e.deltaY - - // 记录事件用于计算速度和惯性 - this._wheelEvents.push({ - delta: delta, - point: { x: opt.e.offsetX, y: opt.e.offsetY }, - time: now, - hasPanAnimation: this._wasPanning, - hasZoomAnimation: this._wasZooming - }) - - // 保留最近的事件记录 - if (this._wheelEvents.length > 10) { - this._wheelEvents.shift() - } - - // 检查是否是第一个事件或者距离上次处理已经过了足够时间 - const isFirstEvent = !this._wheelAccumulationTimeout - const timeSinceLastProcess = now - (this._lastWheelProcessTime || 0) - - if (isFirstEvent || timeSinceLastProcess > this._wheelAccumulationTime) { - // 立即处理第一个事件或长时间没有处理的事件,确保响应性 - this._processAccumulatedWheel(opt) - this._lastWheelProcessTime = now - - // 清理之前的累积 - this._accumulatedWheelDelta = 0 - - // 如果有pending的timeout,清除它 - if (this._wheelAccumulationTimeout) { - clearTimeout(this._wheelAccumulationTimeout) - this._wheelAccumulationTimeout = null - } - } else { - // 累积后续事件 - this._accumulatedWheelDelta += delta - - // 如果正在累积中,清除之前的定时器 - if (this._wheelAccumulationTimeout) { - clearTimeout(this._wheelAccumulationTimeout) - } - - // 设置新的定时器,处理累积的事件 - this._wheelAccumulationTimeout = setTimeout(() => { - this._processAccumulatedWheel(opt) - this._lastWheelProcessTime = Date.now() - - // 清理 - this._accumulatedWheelDelta = 0 - this._wheelAccumulationTimeout = null - }, this._wheelThrottleTime) - } - - opt.e.preventDefault() - opt.e.stopPropagation() - } - - /** - * 处理累积的滚轮事件并应用缩放 - * @private - * @param {Object} lastOpt 最后一个滚轮事件 - */ - _processAccumulatedWheel(lastOpt) { - if (!this._wheelEvents.length) return - - const now = Date.now() - - // Mac设备的轻量防抖检查 - 进一步减少冷却时间,确保响应性 - if (this._isMac && now - this._lastMacAnimationTime < this._macAnimationCooldown) { - // 如果距离上次动画时间太短,只延迟很短时间,不阻塞太久 - if (this._wheelAccumulationTimeout) { - clearTimeout(this._wheelAccumulationTimeout) - } - this._wheelAccumulationTimeout = setTimeout(() => { - this._processAccumulatedWheel(lastOpt) - }, Math.min(this._macAnimationCooldown, 3)) // 最多延迟3ms - return - } - - const currentZoom = this.canvas.getZoom() - - // 分析滚轮事件模式,计算平均增量、速度和加速度 - let sumDelta = 0 - let count = 0 - let earliestTime = now - let latestTime = 0 - let point = { - x: lastOpt.e.offsetX, - y: lastOpt.e.offsetY - } - - // 判断是否在事件收集期间有平移或缩放动画 - let hadPanAnimation = false - let hadZoomAnimation = false - - // 计算平均增量和速度 - this._wheelEvents.forEach((event) => { - sumDelta += event.delta - count++ - earliestTime = Math.min(earliestTime, event.time) - latestTime = Math.max(latestTime, event.time) - - // 使用最后记录的点作为缩放中心 - if (event.time > latestTime) { - point = event.point - } - - // 检查是否有动画状态 - if (event.hasPanAnimation) hadPanAnimation = true - if (event.hasZoomAnimation) hadZoomAnimation = true - }) - - // 计算平均增量 - const avgDelta = sumDelta / count - - // 计算滚动速度 - 基于事件频率和时间跨度 - const timeSpan = latestTime - earliestTime + 1 // 避免除以零 - const eventsPerSecond = (count / timeSpan) * 1000 - - // 速度系数: 速度越快,缩放越敏感 - let speedFactor = Math.min(3, Math.max(0.5, eventsPerSecond / 10)) - - // 计算缩放因子,应用速度系数 - // 针对Mac设备优化:Mac触控板的deltaY值通常较小,需要适度增加敏感度 - let zoomFactorBase = 0.999 - if (this._isMac) { - // Mac设备的触控板需要适度的敏感度,避免过度反应 - zoomFactorBase = 0.995 // 适度降低基数,增加缩放敏感度 - - // 检测是否为触控板滚动(小幅度、高频次的特征) - const avgAbsDelta = Math.abs(avgDelta) - if (avgAbsDelta < 50 && count > 2) { - // 触控板滚动,适度增加敏感度 - speedFactor *= 1.6 // 适度增加敏感度倍数 - zoomFactorBase = 0.993 // 进一步调整基数 - } - } - - const zoomFactor = zoomFactorBase ** (avgDelta * speedFactor) - let targetZoom = currentZoom * zoomFactor - - // 限制缩放范围 - targetZoom = Math.min(Math.max(targetZoom, 0.1), 20) - - // 根据滚动速度和缩放幅度计算动画持续时间 - // 速度快时缩短动画时间,缩放幅度大时延长动画时间 - const zoomRatio = Math.abs(targetZoom - currentZoom) / currentZoom - - let duration - if (this._isMac) { - // Mac设备使用平衡的动画时间控制 - if (speedFactor > 2) { - // 快速操作:快速但平滑 - duration = Math.min( - 0.18, - Math.max(0.08, (zoomRatio * 0.3) / Math.sqrt(speedFactor)) - ) - } else if (speedFactor > 1.2) { - // 中等速度:标准响应 - duration = Math.min(0.25, Math.max(0.1, (zoomRatio * 0.4) / Math.sqrt(speedFactor))) - } else { - // 慢速精确操作:确保平滑 - duration = Math.min(0.3, Math.max(0.12, (zoomRatio * 0.5) / Math.sqrt(speedFactor))) - } - } else { - duration = Math.min(0.5, Math.max(0.15, (zoomRatio * 0.8) / Math.sqrt(speedFactor))) - } - - // 根据滚动速度选择不同的缓动效果 - let easeType - if (this._isMac) { - // Mac设备使用更简单、性能更好的缓动函数 - // 避免复杂的指数和回弹效果,减少计算量 - if (speedFactor > 2) { - // 快速滚动:使用简单的缓出效果 - easeType = 'power2.out' - } else if (speedFactor > 1.2) { - // 中等速度:使用平滑的缓出 - easeType = 'power1.out' - } else { - // 慢速精确操作:使用线性过渡 - easeType = 'power1.out' - } - } else { - // 非Mac设备保持原有的缓动 - easeType = speedFactor > 1.5 ? 'power1.out' : 'power2.out' - } - - // 根据是否有其他动画正在进行,选择合适的动画方法 - if (hadPanAnimation || this._wasPanning) { - // 如果有平移动画,使用组合动画以保持平滑过渡 - this.animateCombinedTransform(point, targetZoom, { - duration: duration, - ease: easeType - }) - } else { - // 如果没有其他动画,使用标准缩放动画 - this.animateZoom(point, targetZoom, { - duration: duration, - ease: easeType - }) - } - - // 更新Mac设备的最后动画时间 - if (this._isMac) { - this._lastMacAnimationTime = now - } - - // 清理事件记录 - this._wheelEvents = [] - } - - /** - * 计算并应用拖动结束后的惯性效果 - * @param {Array} positions 拖动过程中记录的位置数组 - * @param {Boolean} isTouchDevice 是否是触摸设备 - */ - applyInertiaEffect(positions, isTouchDevice) { - if (!positions || positions.length <= 1) return - - const lastPos = positions[positions.length - 1] - const firstPos = positions[0] - const deltaTime = lastPos.time - firstPos.time - - if (deltaTime <= 0) return - - // 计算速度向量 (像素/毫秒) - const velocityX = (lastPos.x - firstPos.x) / deltaTime - const velocityY = (lastPos.y - firstPos.y) / deltaTime - const speed = Math.sqrt(velocityX * velocityX + velocityY * velocityY) - - // 仅当速度足够大时应用惯性效果 - if (speed > 0.2) { - // 计算惯性距离,基于速度和衰减因子 - const decayFactor = 300 // 调整此值以改变惯性效果的强度 - const inertiaDistanceX = velocityX * decayFactor - const inertiaDistanceY = velocityY * decayFactor - - // 计算目标位置 - const vpt = this.canvas.viewportTransform - const currentPos = { - x: -vpt[4], - y: -vpt[5] - } - - const targetPos = { - x: currentPos.x - inertiaDistanceX, - y: currentPos.y - inertiaDistanceY - } - - // 应用惯性动画,速度越大,动画时间越长 - const animationDuration = Math.min(1.2, Math.max(0.6, speed * 2)) - - // 应用惯性动画 - this.animatePan(targetPos, { - duration: animationDuration, // 动态计算持续时间 - ease: this._isMac ? 'quart.out' : 'power3.out' // Mac使用更自然的减速效果 - }) - } - } - - /** - * 平滑过渡停止所有动画 - * 用于在需要中断当前动画时提供更自然的过渡,而不是硬性中断 - * @param {Object} options 过渡选项 - */ - smoothStopAnimations(options = {}) { - const duration = options.duration || 0.15 // 默认短暂过渡时间 - - // 处理缩放动画 - if (this._zoomAnimation) { - const zoomObj = this._zoomAnimation.targets()[0] - const currentZoom = this.canvas.getZoom() - - // 创建短暂的过渡动画到当前值 - gsap.to(zoomObj, { - value: currentZoom, - duration: duration, - ease: this._isMac ? 'circ.out' : 'power1.out', // Mac使用更平滑的缓动 - onUpdate: () => { - this.currentZoom.value = Math.round(zoomObj.value * 100) - this.canvas.renderAll() - }, - onComplete: () => { - if (this._zoomAnimation) { - this._zoomAnimation.kill() - this._zoomAnimation = null - } - } - }) - } - - // 处理平移动画 - if (this._panAnimation) { - const panObj = this._panAnimation.targets()[0] - const vpt = this.canvas.viewportTransform - const currentPos = { x: -vpt[4], y: -vpt[5] } - - // 创建短暂的过渡动画到当前位置 - gsap.to(panObj, { - x: currentPos.x, - y: currentPos.y, - duration: duration, - ease: this._isMac ? 'circ.out' : 'power1.out', // Mac使用更平滑的缓动 - onUpdate: () => { - this._applyPan(panObj.x, panObj.y) - }, - onComplete: () => { - if (this._panAnimation) { - this._panAnimation.kill() - this._panAnimation = null - } - } - }) - } - } - - /** - * 设置画布交互动画 - * 为对象交互添加流畅的动画效果 - */ - setupInteractionAnimations() { - if (!this.canvas) return - - // 启用对象旋转的流畅动画 - this._setupRotationAnimation() - } - - /** - * 设置旋转动画 - * @private - */ - _setupRotationAnimation() { - if (!fabric) return - - // 保存原始旋转方法 - const originalRotate = fabric.Object.prototype.rotate - const isMac = this._isMac // 保存Mac检测结果 - - // 覆盖旋转方法以添加动画 - fabric.Object.prototype.rotate = function (angle) { - const currentAngle = this.angle || 0 - - if (Math.abs(angle - currentAngle) > 0.1) { - gsap.to(this, { - angle: angle, - duration: 0.3, - ease: isMac ? 'back.out(0.3)' : 'power2.out', // Mac使用轻微回弹 - onUpdate: () => { - this.canvas && this.canvas.renderAll() - } - }) - return this - } - - // 如果角度差异很小,使用原始方法 - return originalRotate.call(this, angle) - } - } - - /** - * 处理滚轮缩放,同时兼容正在进行的平移动画 - * @param {Object} point 缩放中心点 - * @param {Number} targetZoom 目标缩放值 - * @param {Object} options 动画选项 - */ - animateCombinedTransform(point, targetZoom, options = {}) { - if (!this.canvas) return - - // 限制缩放范围 - targetZoom = Math.min(Math.max(targetZoom, 0.1), 20) - - // 当前状态 - const currentZoom = this.canvas.getZoom() - const currentVpt = [...this.canvas.viewportTransform] - const currentPos = { x: -currentVpt[4], y: -currentVpt[5] } - - // 如果有正在进行的动画,先停止它们 - if (this._combinedAnimation) { - this._combinedAnimation.kill() - this._combinedAnimation = null - } - - if (this._zoomAnimation) { - this._zoomAnimation.kill() - this._zoomAnimation = null - } - - if (this._panAnimation) { - this._panAnimation.kill() - this._panAnimation = null - } - - // 创建一个统一的变换对象来动画 - const transform = { - zoom: currentZoom, - panX: currentVpt[4], - panY: currentVpt[5], - progress: 0 // 用于动画进度跟踪 - } - - // 获取平移目标位置(如果有的话) - let panTarget = { x: currentPos.x, y: currentPos.y } - if (this._wasPanning) { - // 如果之前有平移动画,尝试获取平移的目标位置 - const vpt = this.canvas.viewportTransform - panTarget = { - x: currentPos.x, - y: currentPos.y - } - } - - // 计算新的变换矩阵,同时考虑平移和缩放 - const scaleFactor = targetZoom / currentZoom - - // 创建动画 - this._combinedAnimation = gsap.to(transform, { - zoom: targetZoom, - progress: 1, - duration: options.duration || 0.3, - ease: options.ease || (this._isMac ? 'expo.out' : 'power2.out'), // Mac使用更平滑的缓动 - onUpdate: () => { - // 计算当前动画阶段的混合变换 - const currentScaleFactor = transform.zoom / currentZoom - - // 应用缩放 - const vpt = this.canvas.viewportTransform - vpt[0] = currentVpt[0] * (transform.zoom / currentZoom) - vpt[3] = currentVpt[3] * (transform.zoom / currentZoom) - - // 平滑混合平移和缩放调整 - const adjustX = (1 - currentScaleFactor) * point.x - const adjustY = (1 - currentScaleFactor) * point.y - - // 如果存在平移目标,进行插值 - if (this._wasPanning) { - const t = transform.progress - const interpolatedX = currentPos.x * (1 - t) + panTarget.x * t - const interpolatedY = currentPos.y * (1 - t) + panTarget.y * t - - // 结合缩放和平移的调整 - vpt[4] = -interpolatedX * currentScaleFactor + adjustX - vpt[5] = -interpolatedY * currentScaleFactor + adjustY - } else { - // 只有缩放,保持中心点 - vpt[4] = currentVpt[4] * currentScaleFactor + adjustX - vpt[5] = currentVpt[5] * currentScaleFactor + adjustY - } - - // 更新缩放值显示 - this.currentZoom.value = Math.round(transform.zoom * 100) - this.canvas.renderAll() - }, - onComplete: () => { - this._combinedAnimation = null - this._zoomAnimation = null - this._panAnimation = null - this._wasPanning = false - this._wasZooming = false - - // 确保最终状态准确 - this._applyZoom(point, targetZoom, true) - } - }) - } - - /** - * 清理资源 - */ - dispose() { - if (this._zoomAnimation) { - this._zoomAnimation.kill() - this._zoomAnimation = null - } - - if (this._panAnimation) { - this._panAnimation.kill() - this._panAnimation = null - } - - this._wheelEvents = [] - this.canvas = null - this.currentZoom = null - } -}