Skip to content

Pointer Events vs Mouse Events vs Touch Events

这三种事件用于处理不同类型的用户输入,让我详细对比它们:


一、基本概念

Mouse Events(鼠标事件)

  • 专门处理鼠标输入
  • 最早的事件类型
  • 只能处理鼠标操作

Touch Events(触摸事件)

  • 专门处理触摸屏输入
  • 为移动设备设计
  • 支持多点触控

Pointer Events(指针事件)

  • 统一的事件模型
  • 可以处理鼠标、触摸、触控笔等所有指针输入
  • 现代推荐使用

二、事件对比表

事件类型Mouse EventsTouch EventsPointer 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

六、最佳实践

✅ 推荐做法

  1. 优先使用 Pointer Events
javascript
// 现代应用应该使用 Pointer Events
element.addEventListener('pointerdown', handler);
  1. 阻止默认触摸行为
javascript
element.addEventListener('touchstart', (e) => {
  e.preventDefault(); // 阻止双击缩放、滚动等
}, { passive: false });
  1. 使用 touch-action CSS 属性
css
.draggable {
  touch-action: none; /* 禁用所有触摸手势 */
}
  1. 处理指针捕获
javascript
element.addEventListener('pointerdown', (e) => {
  element.setPointerCapture(e.pointerId);
});

element.addEventListener('pointerup', (e) => {
  element.releasePointerCapture(e.pointerId);
});

❌ 避免做法

  1. 同时监听 touch 和 mouse 事件
javascript
// ❌ 会导致在触摸设备上触发两次
element.addEventListener('touchstart', handler);
element.addEventListener('mousedown', handler);
  1. 不检查指针类型就使用特定属性
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-action CSS 属性控制触摸行为
  • 注意事件冒泡和默认行为
  • 移动端要考虑性能优化(使用 passive 监听器)