触摸事件与手势识别
触摸事件基础
HTML5为移动设备提供了丰富的触摸事件接口,使开发者能够创建响应式的触控交互体验。触摸事件与传统鼠标事件不同,可以同时跟踪多个触控点,实现复杂的手势操作。
// 基本触摸事件监听示例
const element = document.getElementById('touchArea');
element.addEventListener('touchstart', (e) => {
console.log('触摸开始', e.touches);
});
element.addEventListener('touchmove', (e) => {
e.preventDefault(); // 阻止默认滚动行为
console.log('触摸移动', e.touches);
});
element.addEventListener('touchend', (e) => {
console.log('触摸结束', e.changedTouches);
});
触摸事件主要包括:
touchstart
:手指触摸屏幕时触发touchmove
:手指在屏幕上滑动时触发touchend
:手指离开屏幕时触发touchcancel
:系统取消触摸时触发(如来电打断)
多点触控处理
多点触控是移动设备的重要特性,通过TouchEvent
对象的touches
属性可以获取所有当前触摸点的信息。
element.addEventListener('touchmove', (e) => {
const touches = e.touches;
if (touches.length === 2) {
// 计算两点间距离
const dx = touches[0].clientX - touches[1].clientX;
const dy = touches[0].clientY - touches[1].clientY;
const distance = Math.sqrt(dx * dx + dy * dy);
console.log('两点距离:', distance);
}
});
每个触摸点包含以下常用属性:
clientX/clientY
:相对于视口的坐标pageX/pageY
:相对于文档的坐标identifier
:触摸点唯一标识符target
:最初触摸的DOM元素
常见手势识别
点击与长按
let touchTimer;
let isLongPress = false;
element.addEventListener('touchstart', () => {
touchTimer = setTimeout(() => {
isLongPress = true;
console.log('长按事件');
}, 500); // 500毫秒判定为长按
});
element.addEventListener('touchend', (e) => {
clearTimeout(touchTimer);
if (!isLongPress) {
console.log('点击事件');
}
isLongPress = false;
});
滑动识别
let startX, startY;
element.addEventListener('touchstart', (e) => {
startX = e.touches[0].clientX;
startY = e.touches[0].clientY;
});
element.addEventListener('touchmove', (e) => {
const deltaX = e.touches[0].clientX - startX;
const deltaY = e.touches[0].clientY - startY;
if (Math.abs(deltaX) > Math.abs(deltaY)) {
if (deltaX > 10) {
console.log('向右滑动');
} else if (deltaX < -10) {
console.log('向左滑动');
}
} else {
if (deltaY > 10) {
console.log('向下滑动');
} else if (deltaY < -10) {
console.log('向上滑动');
}
}
});
缩放手势
let initialDistance = 0;
element.addEventListener('touchstart', (e) => {
if (e.touches.length === 2) {
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
initialDistance = Math.sqrt(dx * dx + dy * dy);
}
});
element.addEventListener('touchmove', (e) => {
if (e.touches.length === 2) {
const dx = e.touches[0].clientX - e.touches[1].clientX;
const dy = e.touches[0].clientY - e.touches[1].clientY;
const currentDistance = Math.sqrt(dx * dx + dy * dy);
if (initialDistance > 0) {
const scale = currentDistance / initialDistance;
console.log('缩放比例:', scale.toFixed(2));
}
}
});
性能优化与最佳实践
事件节流
let lastMoveTime = 0;
const moveThreshold = 16; // 约60fps
element.addEventListener('touchmove', (e) => {
const now = Date.now();
if (now - lastMoveTime >= moveThreshold) {
// 执行操作
updatePosition(e.touches[0].clientX, e.touches[0].clientY);
lastMoveTime = now;
}
});
被动事件监听器
// 改善滚动性能
element.addEventListener('touchmove', (e) => {
// 轻量级操作
}, { passive: true });
CSS硬件加速
.touch-element {
will-change: transform; /* 提示浏览器可能的变化 */
transform: translateZ(0); /* 触发硬件加速 */
}
手势库的应用
虽然可以手动实现手势识别,但使用专业库能提高开发效率和稳定性。Hammer.js是流行的选择:
import Hammer from 'hammerjs';
const mc = new Hammer(element);
mc.get('pan').set({ direction: Hammer.DIRECTION_ALL });
mc.on('pan', (e) => {
console.log('平移:', e.deltaX, e.deltaY);
});
mc.on('pinch', (e) => {
console.log('缩放:', e.scale);
});
mc.on('rotate', (e) => {
console.log('旋转:', e.rotation);
});
跨平台兼容性处理
不同设备和浏览器对触摸事件的支持存在差异,需要做好兼容处理:
function getSupportedEvents() {
if ('ontouchstart' in window) {
return {
start: 'touchstart',
move: 'touchmove',
end: 'touchend',
cancel: 'touchcancel'
};
} else if ('onpointerdown' in window) {
return {
start: 'pointerdown',
move: 'pointermove',
end: 'pointerup',
cancel: 'pointercancel'
};
} else {
return {
start: 'mousedown',
move: 'mousemove',
end: 'mouseup',
cancel: null
};
}
}
const events = getSupportedEvents();
element.addEventListener(events.start, handleStart);
高级手势组合
复杂手势通常需要状态管理:
class GestureRecognizer {
constructor(element) {
this.state = 'idle';
this.element = element;
this.setupEvents();
}
setupEvents() {
this.element.addEventListener('touchstart', this.handleStart.bind(this));
this.element.addEventListener('touchmove', this.handleMove.bind(this));
this.element.addEventListener('touchend', this.handleEnd.bind(this));
}
handleStart(e) {
if (e.touches.length === 2) {
this.state = 'potentialZoom';
this.initialDistance = this.getTouchDistance(e.touches);
} else {
this.state = 'potentialTap';
this.startTime = Date.now();
}
}
handleMove(e) {
if (this.state === 'potentialZoom' && e.touches.length === 2) {
const currentDistance = this.getTouchDistance(e.touches);
const scale = currentDistance / this.initialDistance;
this.dispatchEvent('zoom', { scale });
}
}
handleEnd(e) {
if (this.state === 'potentialTap' && Date.now() - this.startTime < 200) {
this.dispatchEvent('tap');
}
this.state = 'idle';
}
getTouchDistance(touches) {
const dx = touches[0].clientX - touches[1].clientX;
const dy = touches[0].clientY - touches[1].clientY;
return Math.sqrt(dx * dx + dy * dy);
}
dispatchEvent(type, detail) {
const event = new CustomEvent(`gesture:${type}`, { detail });
this.element.dispatchEvent(event);
}
}
实际应用场景
图片查看器
class ImageViewer {
constructor(container) {
this.container = container;
this.image = container.querySelector('img');
this.scale = 1;
this.position = { x: 0, y: 0 };
this.setupGestures();
}
setupGestures() {
const mc = new Hammer(this.container);
// 双击恢复原始大小
mc.on('doubletap', () => {
this.resetTransform();
});
// 平移
mc.on('pan', (e) => {
this.position.x += e.deltaX;
this.position.y += e.deltaY;
this.updateTransform();
});
// 缩放
mc.on('pinch', (e) => {
this.scale *= e.scale;
this.updateTransform();
});
}
updateTransform() {
this.image.style.transform = `
translate(${this.position.x}px, ${this.position.y}px)
scale(${this.scale})
`;
}
resetTransform() {
this.scale = 1;
this.position = { x: 0, y: 0 };
this.updateTransform();
}
}
滑动菜单
class SlideMenu {
constructor(menuElement) {
this.menu = menuElement;
this.startX = 0;
this.currentX = 0;
this.threshold = 100;
this.setupTouchEvents();
}
setupTouchEvents() {
this.menu.addEventListener('touchstart', (e) => {
this.startX = e.touches[0].clientX;
});
this.menu.addEventListener('touchmove', (e) => {
this.currentX = e.touches[0].clientX - this.startX;
// 限制向右滑动
if (this.currentX > 0) {
this.menu.style.transform = `translateX(${this.currentX}px)`;
}
});
this.menu.addEventListener('touchend', () => {
if (this.currentX > this.threshold) {
this.openMenu();
} else {
this.closeMenu();
}
});
}
openMenu() {
this.menu.style.transform = 'translateX(250px)';
}
closeMenu() {
this.menu.style.transform = 'translateX(0)';
}
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn