diff --git a/src/components/Canvas/DepthCanvas/manager/animationManager.ts b/src/components/Canvas/DepthCanvas/manager/animationManager.ts
new file mode 100644
index 0000000..999dc99
--- /dev/null
+++ b/src/components/Canvas/DepthCanvas/manager/animationManager.ts
@@ -0,0 +1,843 @@
+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
+ }
+}
diff --git a/src/views/home/components/Input.vue b/src/views/home/components/Input.vue
index 08ba09b..3feaa15 100644
--- a/src/views/home/components/Input.vue
+++ b/src/views/home/components/Input.vue
@@ -8,9 +8,13 @@
v-for="(image, index) in uploadedImages"
:key="index"
class="image-preview-item"
- @click="previewImage(image.url)"
>
-
+