Pointer Events vs Mouse Events vs Touch Events
这三种事件用于处理不同类型的用户输入,让我详细对比它们:
一、基本概念
Mouse Events(鼠标事件)
- 专门处理鼠标输入
- 最早的事件类型
- 只能处理鼠标操作
Touch Events(触摸事件)
- 专门处理触摸屏输入
- 为移动设备设计
- 支持多点触控
Pointer Events(指针事件)
- 统一的事件模型
- 可以处理鼠标、触摸、触控笔等所有指针输入
- 现代推荐使用
二、事件对比表
| 事件类型 | Mouse Events | Touch Events | Pointer Events |
|---|---|---|---|
| 鼠标支持 | ✅ | ❌ | ✅ |
| 触摸支持 | ❌ | ✅ | ✅ |
| 触控笔支持 | 部分 | ❌ | ✅ |
| 多点触控 | ❌ | ✅ | ✅ |
| 压感检测 | ❌ | ❌ | ✅ |
| 倾斜角度 | ❌ | ❌ | ✅ |
| 浏览器兼容 | 最好 | 好 | 好(IE10+) |
| API 统一性 | ❌ | ❌ | ✅ |
三、具体事件列表
1. Mouse Events
javascript
// 鼠标事件
element.addEventListener('mousedown', handler); // 鼠标按下
element.addEventListener('mouseup', handler); // 鼠标松开
element.addEventListener('mousemove', handler); // 鼠标移动
element.addEventListener('mouseenter', handler); // 鼠标进入(不冒泡)
element.addEventListener('mouseleave', handler); // 鼠标离开(不冒泡)
element.addEventListener('mouseover', handler); // 鼠标进入(冒泡)
element.addEventListener('mouseout', handler); // 鼠标离开(冒泡)
element.addEventListener('click', handler); // 单击
element.addEventListener('dblclick', handler); // 双击
element.addEventListener('contextmenu', handler); // 右键菜单示例:
javascript
const box = document.getElementById('box');
box.addEventListener('mousedown', (e) => {
console.log('鼠标按下');
console.log('按钮:', e.button); // 0=左键, 1=中键, 2=右键
console.log('位置:', e.clientX, e.clientY);
});
box.addEventListener('mousemove', (e) => {
console.log('鼠标移动:', e.clientX, e.clientY);
});2. Touch Events
javascript
// 触摸事件
element.addEventListener('touchstart', handler); // 触摸开始
element.addEventListener('touchmove', handler); // 触摸移动
element.addEventListener('touchend', handler); // 触摸结束
element.addEventListener('touchcancel', handler); // 触摸取消示例:
javascript
const box = document.getElementById('box');
box.addEventListener('touchstart', (e) => {
console.log('触摸开始');
console.log('触摸点数量:', e.touches.length);
// 遍历所有触摸点
for (let touch of e.touches) {
console.log('触摸点ID:', touch.identifier);
console.log('位置:', touch.clientX, touch.clientY);
}
});
box.addEventListener('touchmove', (e) => {
e.preventDefault(); // 阻止默认滚动
const touch = e.touches[0];
console.log('触摸移动:', touch.clientX, touch.clientY);
});
box.addEventListener('touchend', (e) => {
console.log('触摸结束');
console.log('剩余触摸点:', e.touches.length);
});TouchEvent 属性:
javascript
event.touches // 当前所有触摸点
event.targetTouches // 当前元素上的触摸点
event.changedTouches // 状态改变的触摸点3. Pointer Events(推荐)
javascript
// 指针事件
element.addEventListener('pointerdown', handler); // 指针按下
element.addEventListener('pointerup', handler); // 指针抬起
element.addEventListener('pointermove', handler); // 指针移动
element.addEventListener('pointerenter', handler); // 指针进入
element.addEventListener('pointerleave', handler); // 指针离开
element.addEventListener('pointerover', handler); // 指针悬停
element.addEventListener('pointerout', handler); // 指针离开
element.addEventListener('pointercancel', handler); // 指针取消
element.addEventListener('gotpointercapture', handler); // 捕获指针
element.addEventListener('lostpointercapture', handler); // 失去捕获示例:
javascript
const box = document.getElementById('box');
box.addEventListener('pointerdown', (e) => {
console.log('指针按下');
console.log('指针类型:', e.pointerType); // 'mouse', 'pen', 'touch'
console.log('指针ID:', e.pointerId);
console.log('位置:', e.clientX, e.clientY);
console.log('压感:', e.pressure); // 0.0 - 1.0
console.log('倾斜角度:', e.tiltX, e.tiltY);
console.log('按钮:', e.button);
// 捕获指针,后续事件都会触发到这个元素
box.setPointerCapture(e.pointerId);
});
box.addEventListener('pointermove', (e) => {
console.log('指针移动:', e.clientX, e.clientY);
});
box.addEventListener('pointerup', (e) => {
console.log('指针抬起');
// 释放指针捕获
box.releasePointerCapture(e.pointerId);
});PointerEvent 独有属性:
javascript
event.pointerId // 指针的唯一ID
event.pointerType // 'mouse', 'pen', 'touch'
event.pressure // 压感(0.0-1.0)
event.tiltX // X轴倾斜角度(-90到90)
event.tiltY // Y轴倾斜角度(-90到90)
event.width // 接触面积宽度
event.height // 接触面积高度
event.isPrimary // 是否是主指针四、实际应用场景对比
1. 简单拖拽实现
使用 Mouse Events(只支持鼠标)
javascript
let isDragging = false;
let offsetX, offsetY;
box.addEventListener('mousedown', (e) => {
isDragging = true;
offsetX = e.clientX - box.offsetLeft;
offsetY = e.clientY - box.offsetTop;
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
box.style.left = e.clientX - offsetX + 'px';
box.style.top = e.clientY - offsetY + 'px';
});
document.addEventListener('mouseup', () => {
isDragging = false;
});使用 Touch Events(只支持触摸)
javascript
let isDragging = false;
let offsetX, offsetY;
box.addEventListener('touchstart', (e) => {
isDragging = true;
const touch = e.touches[0];
offsetX = touch.clientX - box.offsetLeft;
offsetY = touch.clientY - box.offsetTop;
});
box.addEventListener('touchmove', (e) => {
if (!isDragging) return;
e.preventDefault();
const touch = e.touches[0];
box.style.left = touch.clientX - offsetX + 'px';
box.style.top = touch.clientY - offsetY + 'px';
});
box.addEventListener('touchend', () => {
isDragging = false;
});使用 Pointer Events(统一支持所有设备)✅ 推荐
javascript
let isDragging = false;
let offsetX, offsetY;
box.addEventListener('pointerdown', (e) => {
isDragging = true;
offsetX = e.clientX - box.offsetLeft;
offsetY = e.clientY - box.offsetTop;
box.setPointerCapture(e.pointerId); // 捕获指针
});
box.addEventListener('pointermove', (e) => {
if (!isDragging) return;
box.style.left = e.clientX - offsetX + 'px';
box.style.top = e.clientY - offsetY + 'px';
});
box.addEventListener('pointerup', (e) => {
isDragging = false;
box.releasePointerCapture(e.pointerId);
});2. 多点触控(缩放)
使用 Touch Events
javascript
let initialDistance = 0;
let initialScale = 1;
element.addEventListener('touchstart', (e) => {
if (e.touches.length === 2) {
const touch1 = e.touches[0];
const touch2 = e.touches[1];
initialDistance = getDistance(touch1, touch2);
}
});
element.addEventListener('touchmove', (e) => {
if (e.touches.length === 2) {
e.preventDefault();
const touch1 = e.touches[0];
const touch2 = e.touches[1];
const currentDistance = getDistance(touch1, touch2);
const scale = initialScale * (currentDistance / initialDistance);
element.style.transform = `scale(${scale})`;
}
});
function getDistance(touch1, touch2) {
const dx = touch1.clientX - touch2.clientX;
const dy = touch1.clientY - touch2.clientY;
return Math.sqrt(dx * dx + dy * dy);
}使用 Pointer Events
javascript
const pointers = new Map();
let initialDistance = 0;
let initialScale = 1;
element.addEventListener('pointerdown', (e) => {
pointers.set(e.pointerId, e);
if (pointers.size === 2) {
const [p1, p2] = Array.from(pointers.values());
initialDistance = getDistance(p1, p2);
}
});
element.addEventListener('pointermove', (e) => {
pointers.set(e.pointerId, e);
if (pointers.size === 2) {
const [p1, p2] = Array.from(pointers.values());
const currentDistance = getDistance(p1, p2);
const scale = initialScale * (currentDistance / initialDistance);
element.style.transform = `scale(${scale})`;
}
});
element.addEventListener('pointerup', (e) => {
pointers.delete(e.pointerId);
});
function getDistance(p1, p2) {
const dx = p1.clientX - p2.clientX;
const dy = p1.clientY - p2.clientY;
return Math.sqrt(dx * dx + dy * dy);
}3. 绘图应用(支持触控笔压感)
javascript
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.addEventListener('pointerdown', (e) => {
ctx.beginPath();
ctx.moveTo(e.clientX, e.clientY);
// 根据压感调整线条粗细
ctx.lineWidth = e.pressure * 10 || 2;
// 根据指针类型调整颜色
if (e.pointerType === 'pen') {
ctx.strokeStyle = 'black';
} else if (e.pointerType === 'touch') {
ctx.strokeStyle = 'blue';
} else {
ctx.strokeStyle = 'red';
}
});
canvas.addEventListener('pointermove', (e) => {
if (e.buttons === 0) return; // 没有按下
ctx.lineTo(e.clientX, e.clientY);
ctx.lineWidth = e.pressure * 10 || 2;
ctx.stroke();
});五、兼容性处理
1. 同时支持所有事件(不推荐)
javascript
// ❌ 不推荐:代码重复
element.addEventListener('mousedown', handleStart);
element.addEventListener('touchstart', handleStart);
element.addEventListener('pointerdown', handleStart);2. 特性检测(推荐)
javascript
// ✅ 推荐:优先使用 Pointer Events
if (window.PointerEvent) {
// 使用 Pointer Events
element.addEventListener('pointerdown', handlePointerDown);
element.addEventListener('pointermove', handlePointerMove);
element.addEventListener('pointerup', handlePointerUp);
} else if (window.TouchEvent) {
// 降级到 Touch Events
element.addEventListener('touchstart', handleTouchStart);
element.addEventListener('touchmove', handleTouchMove);
element.addEventListener('touchend', handleTouchEnd);
} else {
// 降级到 Mouse Events
element.addEventListener('mousedown', handleMouseDown);
element.addEventListener('mousemove', handleMouseMove);
element.addEventListener('mouseup', handleMouseUp);
}3. 使用 Polyfill
javascript
// 使用 PEP (Pointer Events Polyfill)
// https://github.com/jquery/PEP六、最佳实践
✅ 推荐做法
- 优先使用 Pointer Events
javascript
// 现代应用应该使用 Pointer Events
element.addEventListener('pointerdown', handler);- 阻止默认触摸行为
javascript
element.addEventListener('touchstart', (e) => {
e.preventDefault(); // 阻止双击缩放、滚动等
}, { passive: false });- 使用 touch-action CSS 属性
css
.draggable {
touch-action: none; /* 禁用所有触摸手势 */
}- 处理指针捕获
javascript
element.addEventListener('pointerdown', (e) => {
element.setPointerCapture(e.pointerId);
});
element.addEventListener('pointerup', (e) => {
element.releasePointerCapture(e.pointerId);
});❌ 避免做法
- 同时监听 touch 和 mouse 事件
javascript
// ❌ 会导致在触摸设备上触发两次
element.addEventListener('touchstart', handler);
element.addEventListener('mousedown', handler);- 不检查指针类型就使用特定属性
javascript
// ❌ 可能在非触控笔设备上出错
element.addEventListener('pointerdown', (e) => {
console.log(e.pressure); // 鼠标可能没有压感
});
// ✅ 正确做法
element.addEventListener('pointerdown', (e) => {
if (e.pointerType === 'pen') {
console.log(e.pressure);
}
});七、总结
| 场景 | 推荐使用 |
|---|---|
| 现代 Web 应用 | Pointer Events ✅ |
| 仅桌面应用 | Mouse Events |
| 仅移动应用 | Touch Events |
| 需要触控笔支持 | Pointer Events ✅ |
| 需要压感检测 | Pointer Events ✅ |
| 需要多点触控 | Pointer Events 或 Touch Events |
| 旧浏览器兼容 | Mouse Events + Touch Events 或使用 Polyfill |
关键要点:
- Pointer Events 是未来趋势,提供统一的 API
- 大多数现代浏览器已支持 Pointer Events
- 使用
touch-actionCSS 属性控制触摸行为 - 注意事件冒泡和默认行为
- 移动端要考虑性能优化(使用
passive监听器)