web-africa-h5/pages/architecture/resettleArchite/mixin/touchScaleMixin.js

426 lines
12 KiB
JavaScript
Raw Normal View History

2025-08-28 10:54:21 +08:00
// 兼容性处理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; // 标准化到16ms60fps
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();
},
};