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

426 lines
12 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.

// 兼容性处理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();
},
};