// 兼容性处理:requestAnimationFrame polyfill const getRequestAnimationFrame = () => { // 检查全局对象是否存在(uni-app 中可能是 uni 而不是 window) const global = typeof window !== 'undefined' ? window : typeof uni !== 'undefined' ? uni : typeof global !== 'undefined' ? global : {}; return global.requestAnimationFrame || global.webkitRequestAnimationFrame || global.mozRequestAnimationFrame || global.oRequestAnimationFrame || global.msRequestAnimationFrame || function(callback) { return setTimeout(callback, 1000 / 60); // 60 FPS fallback }; }; const getCancelAnimationFrame = () => { const global = typeof window !== 'undefined' ? window : typeof uni !== 'undefined' ? uni : typeof global !== 'undefined' ? global : {}; return global.cancelAnimationFrame || global.webkitCancelAnimationFrame || global.mozCancelAnimationFrame || global.oCancelAnimationFrame || global.msCancelAnimationFrame || function(id) { return clearTimeout(id); }; }; const requestAnimationFrame = getRequestAnimationFrame(); const cancelAnimationFrame = getCancelAnimationFrame(); export default { data() { return { // 触摸缩放相关状态 touchStartPosition1: { x: 0, y: 0, }, touchStartPosition2: { x: 0, y: 0, }, initialDistance: 0, x: 0, // 元素的x坐标 y: 0, // 元素的y坐标 scale: 1, // 元素的缩放比例 initialX: 0, // 元素的初始x坐标 initialY: 0, // 元素的初始y坐标 minScale: 0.5, // 最小缩放比例 maxScale: 3, // 最大缩放比例 isScaling: false, // 是否正在缩放 lastTouchTime: 0, // 上次触摸时间 // 拖拽优化相关 isDragging: false, // 是否正在拖拽 dragStartTime: 0, // 拖拽开始时间 lastMoveTime: 0, // 上次移动时间 velocity: { x: 0, y: 0 }, // 移动速度 animationId: null, // 动画帧ID pendingUpdate: false, // 是否有待处理的更新 // 边界控制 enableBoundary: false, // 是否启用边界控制 boundary: { minX: -500, maxX: 500, minY: -500, maxY: 500, }, // 移动边界 // 兼容性相关 useTimer: false, // 是否使用定时器代替 RAF(兼容性降级) }; }, methods: { /** * 触摸开始事件处理 * @param {TouchEvent} event 触摸事件 */ handleTouchStart(event) { event.preventDefault(); // 取消之前的动画 if (this.animationId) { this.safeCancelAnimationFrame(this.animationId); this.animationId = null; } const currentTime = Date.now(); this.lastTouchTime = currentTime; if (event.touches.length === 1) { // 单指拖拽初始化 this.isScaling = false; this.isDragging = true; this.dragStartTime = currentTime; this.lastMoveTime = currentTime; this.initialX = event.touches[0].clientX; this.initialY = event.touches[0].clientY; this.velocity = { x: 0, y: 0 }; this.pendingUpdate = false; } else if (event.touches.length === 2) { // 双指缩放初始化 this.isScaling = true; this.isDragging = false; this.touchStartPosition1.x = event.touches[0].clientX; this.touchStartPosition1.y = event.touches[0].clientY; this.touchStartPosition2.x = event.touches[1].clientX; this.touchStartPosition2.y = event.touches[1].clientY; // 计算初始两指间距离 this.initialDistance = this.getDistance( this.touchStartPosition1, this.touchStartPosition2 ); } }, /** * 触摸移动事件处理 * @param {TouchEvent} event 触摸事件 */ handleTouchMove(event) { event.preventDefault(); if (event.touches.length === 2 && this.isScaling) { // 双指缩放逻辑 const currentDistance = this.getDistance( { x: event.touches[0].clientX, y: event.touches[0].clientY }, { x: event.touches[1].clientX, y: event.touches[1].clientY } ); if (this.initialDistance > 0) { // 计算缩放比例变化 const scaleChange = currentDistance / this.initialDistance; let newScale = this.scale * scaleChange; // 限制缩放范围 newScale = Math.max(this.minScale, Math.min(this.maxScale, newScale)); this.scale = newScale; // 更新初始距离为当前距离,用于下次计算 this.initialDistance = currentDistance; } } else if (event.touches.length === 1 && this.isDragging) { // 单指拖拽逻辑优化 const currentTime = Date.now(); const currentX = event.touches[0].clientX; const currentY = event.touches[0].clientY; // 计算移动距离 const deltaX = currentX - this.initialX; const deltaY = currentY - this.initialY; // 计算移动速度(用于惯性效果) const timeDelta = currentTime - this.lastMoveTime; if (timeDelta > 0) { this.velocity.x = deltaX / timeDelta * 16; // 标准化到16ms(60fps) this.velocity.y = deltaY / timeDelta * 16; } // 使用节流机制更新位置 this.schedulePositionUpdate(deltaX, deltaY); // 更新初始位置和时间 this.initialX = currentX; this.initialY = currentY; this.lastMoveTime = currentTime; } }, /** * 触摸结束事件处理 * @param {TouchEvent} event 触摸事件 */ handleTouchEnd(event) { if (event.touches.length === 0) { // 所有手指都离开屏幕,重置状态 this.isScaling = false; this.initialDistance = 0; // 如果是拖拽结束,启动惯性效果 if (this.isDragging) { this.isDragging = false; this.startInertiaAnimation(); } } else if (event.touches.length === 1 && this.isScaling) { // 从双指变为单指,切换到拖拽模式 this.isScaling = false; this.isDragging = true; this.initialX = event.touches[0].clientX; this.initialY = event.touches[0].clientY; this.lastMoveTime = Date.now(); } }, /** * 计算两点间距离的辅助方法 * @param {Object} point1 第一个点 {x, y} * @param {Object} point2 第二个点 {x, y} * @return {Number} 两点间距离 */ getDistance(point1, point2) { const dx = point1.x - point2.x; const dy = point1.y - point2.y; return Math.sqrt(dx * dx + dy * dy); }, /** * 重置缩放和位置 */ resetTransform() { this.x = 0; this.y = 0; this.scale = 1; this.isScaling = false; this.initialDistance = 0; }, /** * 安全的 requestAnimationFrame 调用 * @param {Function} callback 回调函数 * @return {Number} 动画ID */ safeRequestAnimationFrame(callback) { try { if (this.useTimer) { return setTimeout(callback, 16); // 约 60 FPS } return requestAnimationFrame(callback); } catch (error) { console.warn('requestAnimationFrame failed, fallback to setTimeout:', error); this.useTimer = true; return setTimeout(callback, 16); } }, /** * 安全的 cancelAnimationFrame 调用 * @param {Number} id 动画ID */ safeCancelAnimationFrame(id) { try { if (this.useTimer || typeof id === 'number') { clearTimeout(id); } else { cancelAnimationFrame(id); } } catch (error) { console.warn('cancelAnimationFrame failed, fallback to clearTimeout:', error); clearTimeout(id); } }, /** * 节流位置更新机制 * @param {Number} deltaX x轴位移 * @param {Number} deltaY y轴位移 */ schedulePositionUpdate(deltaX, deltaY) { // 计算新位置 let newX = this.x + deltaX; let newY = this.y + deltaY; // 应用边界限制 if (this.enableBoundary) { const pos = this.applyBoundaryConstraints(newX, newY); newX = pos.x; newY = pos.y; } // 更新位置 this.x = newX; this.y = newY; if (!this.pendingUpdate) { this.pendingUpdate = true; this.safeRequestAnimationFrame(() => { this.pendingUpdate = false; }); } }, /** * 启动惯性动画 */ startInertiaAnimation() { const minVelocity = 0.1; // 最小速度阈值 const friction = 0.95; // 摩擦系数 // 如果速度太小,直接停止 if (Math.abs(this.velocity.x) < minVelocity && Math.abs(this.velocity.y) < minVelocity) { return; } const animate = () => { // 应用摩擦力 this.velocity.x *= friction; this.velocity.y *= friction; // 更新位置 let newX = this.x + this.velocity.x; let newY = this.y + this.velocity.y; // 应用边界限制 if (this.enableBoundary) { const pos = this.applyBoundaryConstraints(newX, newY); newX = pos.x; newY = pos.y; // 如果到达边界,减慢对应方向的速度 if (newX !== this.x + this.velocity.x) { this.velocity.x *= 0.3; } if (newY !== this.y + this.velocity.y) { this.velocity.y *= 0.3; } } this.x = newX; this.y = newY; // 检查是否继续动画 if (Math.abs(this.velocity.x) > minVelocity || Math.abs(this.velocity.y) > minVelocity) { this.animationId = this.safeRequestAnimationFrame(animate); } else { this.animationId = null; this.velocity = { x: 0, y: 0 }; } }; this.animationId = this.safeRequestAnimationFrame(animate); }, /** * 应用边界约束 * @param {Number} x x坐标 * @param {Number} y y坐标 * @return {Object} 约束后的坐标 {x, y} */ applyBoundaryConstraints(x, y) { return { x: Math.max(this.boundary.minX, Math.min(this.boundary.maxX, x)), y: Math.max(this.boundary.minY, Math.min(this.boundary.maxY, y)), }; }, /** * 设置缩放范围 * @param {Number} minScale 最小缩放比例 * @param {Number} maxScale 最大缩放比例 */ setScaleRange(minScale, maxScale) { this.minScale = minScale || 0.5; this.maxScale = maxScale || 3; }, /** * 设置移动边界 * @param {Object} boundary 边界配置 {minX, maxX, minY, maxY} * @param {Boolean} enable 是否启用边界控制 */ setBoundary(boundary, enable = true) { this.boundary = { ...this.boundary, ...boundary }; this.enableBoundary = enable; }, /** * 检测并设置兼容性模式 */ detectCompatibility() { try { // 尝试调用 requestAnimationFrame const testId = requestAnimationFrame(() => {}); cancelAnimationFrame(testId); this.useTimer = false; } catch (error) { console.warn('requestAnimationFrame not supported, using timer fallback'); this.useTimer = true; } }, /** * 强制使用定时器模式(用于调试或特殊需求) * @param {Boolean} useTimer 是否使用定时器 */ setTimerMode(useTimer = true) { this.useTimer = useTimer; if (useTimer) { console.info('TouchScale: 已切换到定时器模式'); } else { console.info('TouchScale: 已切换到 requestAnimationFrame 模式'); } }, /** * 清理动画和状态 */ cleanupTouchScale() { if (this.animationId) { this.safeCancelAnimationFrame(this.animationId); this.animationId = null; } this.isDragging = false; this.isScaling = false; this.velocity = { x: 0, y: 0 }; this.pendingUpdate = false; }, }, // 组件创建时检测兼容性 mounted() { this.detectCompatibility(); }, // 组件销毁时清理 beforeDestroy() { this.cleanupTouchScale(); }, };