触摸事件是专门为触摸屏设备设计的事件类型,用于处理手指在触摸屏上的各种交互。与鼠标事件相比,触摸事件提供了更精细的控制和更多关于触摸点的信息。
touchstart // 手指触摸屏幕时触发
touchmove // 手指在屏幕上移动时触发
touchend // 手指离开屏幕时触发
touchcancel // 触摸被意外中断时触发(如来电、弹窗等)
触摸事件对象包含以下重要属性:
event.touches // 当前所有在屏幕上的触摸点列表
event.targetTouches // 当前元素上的触摸点列表
event.changedTouches // 触发当前事件的触摸点列表(与上次事件相比发生变化)
每个触摸点都是一个 Touch 对象,包含:
touch.identifier // 触摸点的唯一标识符
touch.clientX/Y // 相对于视口的坐标
touch.pageX/Y // 相对于文档的坐标
touch.screenX/Y // 相对于屏幕的坐标
touch.target // 触摸开始时的目标元素
touch.radiusX/Y // 触摸区域的椭圆半径
touch.rotationAngle // 旋转角度
touch.force // 压力值(0.0-1.0)
const element = document.getElementById('touch-area');
element.addEventListener('touchstart', (e) => {
e.preventDefault(); // 阻止默认行为(如滚动)
console.log('触摸开始');
});
element.addEventListener('touchmove', (e) => {
e.preventDefault();
const touch = e.touches[0];
console.log(`手指位置: (${touch.clientX}, ${touch.clientY})`);
});
element.addEventListener('touchend', (e) => {
console.log('触摸结束');
});
element.addEventListener('touchcancel', (e) => {
console.log('触摸被取消');
});
element.addEventListener('touchstart', (e) => {
console.log(`触摸点数量: ${e.touches.length}`);
// 遍历所有触摸点
for (let i = 0; i < e.touches.length; i++) {
const touch = e.touches[i];
console.log(`触摸点 ${i}: ID=${touch.identifier}, X=${touch.clientX}, Y=${touch.clientY}`);
}
});
element.addEventListener('touchmove', (e) => {
// 获取特定触摸点
const firstTouch = e.touches[0];
const secondTouch = e.touches[1];
if (e.touches.length === 2) {
// 计算两点距离(用于缩放手势)
const dx = secondTouch.clientX - firstTouch.clientX;
const dy = secondTouch.clientY - firstTouch.clientY;
const distance = Math.sqrt(dx * dx + dy * dy);
console.log(`两点距离: ${distance}`);
}
});
class DraggableElement {
constructor(element) {
this.element = element;
this.isDragging = false;
this.startX = 0;
this.startY = 0;
this.offsetX = 0;
this.offsetY = 0;
this.init();
}
init() {
this.element.style.position = 'absolute';
this.element.addEventListener('touchstart', this.onTouchStart.bind(this));
this.element.addEventListener('touchmove', this.onTouchMove.bind(this));
this.element.addEventListener('touchend', this.onTouchEnd.bind(this));
}
onTouchStart(e) {
e.preventDefault();
const touch = e.touches[0];
this.isDragging = true;
this.startX = touch.clientX;
this.startY = touch.clientY;
// 获取当前元素位置
const rect = this.element.getBoundingClientRect();
this.offsetX = this.startX - rect.left;
this.offsetY = this.startY - rect.top;
this.element.style.cursor = 'grabbing';
}
onTouchMove(e) {
if (!this.isDragging) return;
e.preventDefault();
const touch = e.touches[0];
const deltaX = touch.clientX - this.startX;
const deltaY = touch.clientY - this.startY;
// 更新元素位置
const newX = touch.clientX - this.offsetX;
const newY = touch.clientY - this.offsetY;
this.element.style.left = `${newX}px`;
this.element.style.top = `${newY}px`;
this.startX = touch.clientX;
this.startY = touch.clientY;
}
onTouchEnd() {
this.isDragging = false;
this.element.style.cursor = 'grab';
}
}
// 使用示例
const draggable = new DraggableElement(document.getElementById('draggable'));
class ZoomableElement {
constructor(element) {
this.element = element;
this.scale = 1;
this.initialDistance = 0;
this.init();
}
init() {
this.element.addEventListener('touchstart', this.onTouchStart.bind(this));
this.element.addEventListener('touchmove', this.onTouchMove.bind(this));
}
onTouchStart(e) {
if (e.touches.length === 2) {
e.preventDefault();
this.initialDistance = this.getTouchDistance(e.touches);
}
}
onTouchMove(e) {
if (e.touches.length === 2) {
e.preventDefault();
const currentDistance = this.getTouchDistance(e.touches);
const scaleChange = currentDistance / this.initialDistance;
// 更新缩放比例
this.scale *= scaleChange;
this.scale = Math.max(0.5, Math.min(this.scale, 3)); // 限制缩放范围
this.element.style.transform = `scale(${this.scale})`;
this.initialDistance = currentDistance;
}
}
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);
}
}
class SwipeDetector {
constructor(element, options = {}) {
this.element = element;
this.threshold = options.threshold || 50; // 最小滑动距离
this.restraint = options.restraint || 100; // 最大允许的垂直偏移
this.allowedTime = options.allowedTime || 300; // 最大允许时间
this.startX = 0;
this.startY = 0;
this.startTime = 0;
this.init();
}
init() {
this.element.addEventListener('touchstart', this.onTouchStart.bind(this));
this.element.addEventListener('touchend', this.onTouchEnd.bind(this));
}
onTouchStart(e) {
const touch = e.touches[0];
this.startX = touch.clientX;
this.startY = touch.clientY;
this.startTime = Date.now();
}
onTouchEnd(e) {
const touch = e.changedTouches[0];
const distX = touch.clientX - this.startX;
const distY = touch.clientY - this.startY;
const elapsedTime = Date.now() - this.startTime;
if (elapsedTime <= this.allowedTime) {
// 水平滑动距离足够,垂直偏移在允许范围内
if (Math.abs(distX) >= this.threshold && Math.abs(distY) <= this.restraint) {
if (distX > 0) {
this.onSwipeRight();
} else {
this.onSwipeLeft();
}
}
// 垂直滑动
else if (Math.abs(distY) >= this.threshold && Math.abs(distX) <= this.restraint) {
if (distY > 0) {
this.onSwipeDown();
} else {
this.onSwipeUp();
}
}
}
}
onSwipeLeft() {
console.log('向左滑动');
this.element.dispatchEvent(new CustomEvent('swipeleft'));
}
onSwipeRight() {
console.log('向右滑动');
this.element.dispatchEvent(new CustomEvent('swiperight'));
}
onSwipeUp() {
console.log('向上滑动');
this.element.dispatchEvent(new CustomEvent('swipeup'));
}
onSwipeDown() {
console.log('向下滑动');
this.element.dispatchEvent(new CustomEvent('swipedown'));
}
}
// 使用示例
const swipeElement = document.getElementById('swipe-area');
const detector = new SwipeDetector(swipeElement);
swipeElement.addEventListener('swipeleft', () => {
console.log('检测到向左滑动手势');
});
let lastMoveTime = 0;
const MOVE_THROTTLE = 16; // 约60fps
element.addEventListener('touchmove', (e) => {
const now = Date.now();
if (now - lastMoveTime < MOVE_THROTTLE) return;
lastMoveTime = now;
// 处理触摸移动
e.preventDefault();
// ... 其他逻辑
});
// 对于不会调用preventDefault()的事件,使用passive提高性能
element.addEventListener('touchmove', (e) => {
// 只读取,不阻止默认行为
console.log(e.touches[0].clientX);
}, { passive: true });
document.addEventListener('touchstart', (e) => {
// 通过target判断实际点击的元素
if (e.target.classList.contains('touchable')) {
e.preventDefault();
// 处理触摸
}
});
function setupPointer(element) {
let isTouch = false;
// 触摸事件
element.addEventListener('touchstart', (e) => {
isTouch = true;
handleStart(e.touches[0]);
}, { passive: true });
element.addEventListener('touchmove', (e) => {
if (isTouch) {
handleMove(e.touches[0]);
}
}, { passive: true });
element.addEventListener('touchend', () => {
isTouch = false;
handleEnd();
});
// 鼠标事件
element.addEventListener('mousedown', (e) => {
if (!isTouch) {
handleStart(e);
}
});
// ... 其他鼠标事件
}
// 正确方式:在touchstart中阻止
element.addEventListener('touchstart', (e) => {
// 如果需要完全阻止滚动
e.preventDefault();
}, { passive: false }); // passive必须为false才能调用preventDefault
// 使用fastclick库或以下方案
document.addEventListener('DOMContentLoaded', () => {
if ('ontouchstart' in window) {
// 移除点击延迟
document.addEventListener('touchstart', () => {}, { passive: true });
}
});
let touchStartTime = 0;
let touchStartX = 0;
let touchStartY = 0;
element.addEventListener('touchstart', (e) => {
touchStartTime = Date.now();
touchStartX = e.touches[0].clientX;
touchStartY = e.touches[0].clientY;
});
element.addEventListener('touchend', (e) => {
const touch = e.changedTouches[0];
const elapsedTime = Date.now() - touchStartTime;
const distX = Math.abs(touch.clientX - touchStartX);
const distY = Math.abs(touch.clientY - touchStartY);
// 如果移动距离过大或时间过短,视为滑动而非点击
if (elapsedTime < 200 && distX < 10 && distY < 10) {
// 执行点击操作
handleClick();
}
});
Pointer Events 作为更现代的统一输入API<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>触摸画板</title>
<style>
canvas {
border: 1px solid #ccc;
background: white;
touch-action: none; /* 阻止浏览器默认触摸行为 */
}
.tools {
margin: 10px 0;
}
button {
margin: 0 5px;
padding: 5px 10px;
}
</style>
</head>
<body>
<div class="tools">
<button onclick="setColor('black')">黑色</button>
<button onclick="setColor('red')">红色</button>
<button onclick="setColor('blue')">蓝色</button>
<button onclick="clearCanvas()">清空</button>
<input type="range" id="brushSize" min="1" max="20" value="3" onchange="setBrushSize(this.value)">
</div>
<canvas id="drawingCanvas" width="800" height="500"></canvas>
<script>
const canvas = document.getElementById('drawingCanvas');
const ctx = canvas.getContext('2d');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
let brushColor = 'black';
let brushSize = 3;
// 设置画布尺寸适应屏幕
function resizeCanvas() {
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
clearCanvas();
}
// 开始绘画
function startDrawing(e) {
isDrawing = true;
const touch = e.touches[0];
const rect = canvas.getBoundingClientRect();
lastX = touch.clientX - rect.left;
lastY = touch.clientY - rect.top;
}
// 绘画过程
function draw(e) {
if (!isDrawing) return;
e.preventDefault();
const touch = e.touches[0];
const rect = canvas.getBoundingClientRect();
const x = touch.clientX - rect.left;
const y = touch.clientY - rect.top;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(x, y);
ctx.strokeStyle = brushColor;
ctx.lineWidth = brushSize;
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.stroke();
lastX = x;
lastY = y;
}
// 结束绘画
function stopDrawing() {
isDrawing = false;
}
// 工具函数
function setColor(color) {
brushColor = color;
}
function setBrushSize(size) {
brushSize = parseInt(size);
}
function clearCanvas() {
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
// 事件监听
canvas.addEventListener('touchstart', startDrawing);
canvas.addEventListener('touchmove', draw);
canvas.addEventListener('touchend', stopDrawing);
canvas.addEventListener('touchcancel', stopDrawing);
// 防止滚动
canvas.addEventListener('touchmove', (e) => {
if (isDrawing) {
e.preventDefault();
}
}, { passive: false });
// 初始化
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
clearCanvas();
</script>
</body>
</html>
触摸事件为移动端Web开发提供了强大的交互能力。掌握触摸事件的基本用法和手势识别技术,可以创建出更自然、响应更快的移动端用户体验。在实际开发中,建议:
始终考虑触摸和鼠标输入的兼容性 合理使用preventDefault() 但不要滥用
注意性能优化,特别是 touchmove 事件
考虑使用现有的手势库(如Hammer.js)来处理复杂手势
测试在不同设备和浏览器上的表现