news 2026/10/29 23:33:08

移动端触摸事件:changedTouches与touches对象详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
移动端触摸事件:changedTouches与touches对象详解

1. 移动端触控事件中的两个关键对象

在移动端Web开发中,触摸事件处理是每个前端工程师必须掌握的核心技能。当用户的手指在屏幕上滑动时,浏览器会触发一系列触摸事件,而changedTouches和touches这两个对象常常让开发者感到困惑。它们都存储着触摸点的信息,但在不同场景下表现却大不相同。

我清楚地记得第一次处理多点触控时的混乱场景——当两个手指同时在屏幕上滑动时,控制台打印出的触摸点数据让我完全摸不着头脑。经过多次调试和查阅文档,才真正理解了这两个对象的本质区别。本文将结合具体案例,深入剖析changedTouches和touches的异同点。

2. 基础概念解析:触摸事件的生命周期

2.1 触摸事件的三种基本类型

移动端浏览器主要支持三种触摸事件:

  • touchstart:手指触摸屏幕时触发
  • touchmove:手指在屏幕上滑动时触发
  • touchend:手指离开屏幕时触发

每个事件对象都包含三个关键的TouchList集合:

  • touches:当前屏幕上所有触摸点的集合
  • targetTouches:当前元素上所有触摸点的集合
  • changedTouches:引发当前事件的触摸点集合

2.2 touches对象的特性分析

touches对象包含了屏幕上所有处于活动状态的触摸点信息。这个集合的特点是:

  • 在touchstart事件中,包含刚接触屏幕的触摸点
  • 在touchmove事件中,包含所有正在移动的触摸点
  • 在touchend事件中,touches集合将不再包含已离开的触摸点
element.addEventListener('touchmove', function(e) { console.log('当前屏幕触摸点数量:', e.touches.length); // 遍历所有触摸点 for (let i = 0; i < e.touches.length; i++) { console.log(`触摸点${i}坐标:`, e.touches[i].pageX, e.touches[i].pageY); } });

2.3 changedTouches的特殊行为

changedTouches对象只包含引发当前事件的触摸点,它的行为更加特殊:

  • 在touchstart中,包含新接触的触摸点
  • 在touchmove中,包含位置发生变化的触摸点
  • 在touchend中,包含刚刚离开的触摸点

这个特性使得changedTouches在处理特定场景时非常有用,特别是在touchend事件中,它是唯一能获取到已离开触摸点信息的途径。

3. 关键差异对比与使用场景

3.1 数据完整性的差异

在touchend事件中,两者的表现差异最为明显:

  • touches集合中已经移除了离开的触摸点
  • changedTouches仍然包含刚刚离开的触摸点信息

这个特性对于实现滑动结束时的操作非常关键。例如,在实现一个滑动删除功能时,我们需要在手指离开时获取最后的触摸点位置:

element.addEventListener('touchend', function(e) { // 错误做法:touches在touchend事件中可能为空 // let lastTouch = e.touches[0]; // 正确做法:使用changedTouches获取离开的触摸点 let lastTouch = e.changedTouches[0]; console.log('离开时的位置:', lastTouch.pageX, lastTouch.pageY); });

3.2 多点触控处理的差异

当处理多点触控时,两者的区别更加明显。假设两个手指同时触摸屏幕:

  • touches会包含两个触摸点
  • changedTouches在touchstart事件中也会包含两个触摸点

但当其中一个手指移动时:

  • touches仍然包含两个触摸点
  • changedTouches只包含移动的那个触摸点

这个特性对于实现精细的多点触控交互非常有用。例如,在实现图片缩放功能时,我们可以这样处理:

let initialDistance; element.addEventListener('touchstart', function(e) { if (e.touches.length === 2) { initialDistance = getDistance(e.touches[0], e.touches[1]); } }); element.addEventListener('touchmove', function(e) { if (e.touches.length === 2 && e.changedTouches.length === 1) { // 只有一个手指在移动的情况 let currentDistance = getDistance(e.touches[0], e.touches[1]); let scale = currentDistance / initialDistance; element.style.transform = `scale(${scale})`; } }); function getDistance(touch1, touch2) { let dx = touch1.pageX - touch2.pageX; let dy = touch1.pageY - touch2.pageY; return Math.sqrt(dx * dx + dy * dy); }

3.3 性能考量的差异

从性能角度考虑,changedTouches通常比touches更高效:

  • changedTouches只包含变化的触摸点,数据量更小
  • 在只需要处理变化触摸点的场景下,使用changedTouches可以减少不必要的遍历

特别是在高频触发的touchmove事件中,这个差异会更加明显。例如,在处理绘图应用时:

// 更高效的做法 canvas.addEventListener('touchmove', function(e) { let ctx = canvas.getContext('2d'); e.changedTouches.forEach(touch => { ctx.lineTo(touch.pageX, touch.pageY); ctx.stroke(); }); }); // 相对低效的做法 canvas.addEventListener('touchmove', function(e) { let ctx = canvas.getContext('2d'); e.touches.forEach(touch => { // 遍历所有触摸点,包括未移动的 ctx.lineTo(touch.pageX, touch.pageY); ctx.stroke(); }); });

4. 实战中的常见问题与解决方案

4.1 触摸点丢失问题

很多开发者会遇到touchend事件中无法获取触摸点的问题,这是因为:

  • 在touchend事件触发时,离开的触摸点已经从touches集合中移除
  • 必须使用changedTouches才能获取到离开的触摸点信息

解决方案示例:

// 错误示例:无法获取离开的触摸点 element.addEventListener('touchend', function(e) { console.log(e.touches.length); // 输出0 }); // 正确示例:使用changedTouches element.addEventListener('touchend', function(e) { let touch = e.changedTouches[0]; console.log('离开的触摸点:', touch.identifier, touch.pageX, touch.pageY); });

4.2 触摸点追踪问题

在需要追踪特定触摸点的场景下(如实现拖拽功能),identifier属性就变得非常重要。每个触摸点都有一个唯一的identifier,在整个触摸过程中保持不变。

实现方案:

let activeTouchId = null; element.addEventListener('touchstart', function(e) { if (activeTouchId === null) { activeTouchId = e.changedTouches[0].identifier; // 开始拖拽逻辑 } }); element.addEventListener('touchmove', function(e) { // 在touches集合中查找特定identifier的触摸点 for (let i = 0; i < e.touches.length; i++) { if (e.touches[i].identifier === activeTouchId) { // 更新位置 updatePosition(e.touches[i].pageX, e.touches[i].pageY); break; } } }); element.addEventListener('touchend', function(e) { // 检查离开的触摸点是否是正在追踪的那个 for (let i = 0; i < e.changedTouches.length; i++) { if (e.changedTouches[i].identifier === activeTouchId) { activeTouchId = null; // 结束拖拽逻辑 break; } } });

4.3 跨浏览器兼容性问题

虽然大多数现代浏览器都支持触摸事件,但在细节实现上仍有一些差异需要注意:

  • 某些旧版本浏览器可能不会为每个触摸点生成唯一的identifier
  • 部分浏览器在touchmove事件中changedTouches可能包含所有触摸点,而不仅仅是变化的
  • 触摸点坐标系统在不同浏览器中可能有细微差别

兼容性处理建议:

element.addEventListener('touchmove', function(e) { // 使用特性检测确保identifier存在 let useIdentifier = ('identifier' in e.changedTouches[0]); // 保守处理:假设changedTouches可能包含所有触摸点 let changedPoints = e.changedTouches; if (changedPoints.length === e.touches.length) { // 可能需要额外逻辑判断哪些点真正发生了变化 } // 处理坐标系统差异 let x = e.changedTouches[0].pageX - element.offsetLeft; let y = e.changedTouches[0].pageY - element.offsetTop; });

5. 高级应用场景与优化技巧

5.1 手势识别实现

利用changedTouches和touches的差异,我们可以实现更复杂的手势识别。例如,实现一个滑动方向判断:

let startX, startY; element.addEventListener('touchstart', function(e) { startX = e.changedTouches[0].pageX; startY = e.changedTouches[0].pageY; }); element.addEventListener('touchend', function(e) { let endX = e.changedTouches[0].pageX; let endY = e.changedTouches[0].pageY; let dx = endX - startX; let dy = endY - startY; if (Math.abs(dx) > Math.abs(dy)) { if (dx > 0) { console.log('向右滑动'); } else { console.log('向左滑动'); } } else { if (dy > 0) { console.log('向下滑动'); } else { console.log('向上滑动'); } } });

5.2 性能优化实践

对于高性能要求的应用(如游戏或绘图应用),触摸事件处理需要特别注意性能:

  1. 节流处理:对于高频的touchmove事件,可以使用requestAnimationFrame进行节流
let ticking = false; element.addEventListener('touchmove', function(e) { if (!ticking) { requestAnimationFrame(function() { processTouchMove(e.changedTouches); ticking = false; }); ticking = true; } });
  1. 减少DOM操作:缓存DOM查询结果,避免在每次触摸事件中都进行查询
let ctx = canvas.getContext('2d'); let lastPoint = null; canvas.addEventListener('touchmove', function(e) { let touch = e.changedTouches[0]; if (lastPoint) { ctx.beginPath(); ctx.moveTo(lastPoint.x, lastPoint.y); ctx.lineTo(touch.pageX, touch.pageY); ctx.stroke(); } lastPoint = {x: touch.pageX, y: touch.pageY}; });

5.3 与Pointer Events的协同

现代浏览器开始支持Pointer Events API,它统一了鼠标、触摸和触控笔事件。在实际项目中,我们可能需要同时处理Touch Events和Pointer Events:

// 检测浏览器支持情况 if (window.PointerEvent) { // 使用Pointer Events element.addEventListener('pointerdown', handlePointerStart); element.addEventListener('pointermove', handlePointerMove); element.addEventListener('pointerup', handlePointerEnd); } else { // 回退到Touch Events element.addEventListener('touchstart', handleTouchStart); element.addEventListener('touchmove', handleTouchMove); element.addEventListener('touchend', handleTouchEnd); // 可选:添加鼠标事件支持 element.addEventListener('mousedown', handleMouseStart); element.addEventListener('mousemove', handleMouseMove); element.addEventListener('mouseup', handleMouseEnd); } // 统一的事件处理函数 function handleStartEvent(e) { let point = e.changedTouches ? e.changedTouches[0] : e; console.log('接触点:', point.pageX, point.pageY); }

在实际开发中,理解changedTouches和touches的区别可以避免很多难以调试的问题。特别是在处理复杂的触摸交互时,选择合适的触摸点集合往往能达到事半功倍的效果。经过多个移动端项目的实践,我发现changedTouches在大多数情况下都是更安全可靠的选择,特别是在touchend事件处理中。而touches则更适合需要了解全局触摸状态的场景,如多点触控的实现。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/29 23:31:59

手写BP神经网络实现MNIST识别:纯Python从零推导前向与反向传播

简介&#xff1a;本资源是一份面向机器学习初学者与计算机视觉入门者的BP神经网络实践项目&#xff0c;聚焦MNIST手写数字识别任务&#xff0c;完整呈现前向传播、反向误差传播与权重更新的核心实现逻辑。压缩包共2000个文件&#xff0c;主体为20007张MNIST测试/训练样本PNG图像…

作者头像 李华
网站建设 2026/10/29 23:29:27

Go协程调度器架构与时间片调度机制解析

1. Go协程调度器基础架构解析 Go语言的协程调度器采用独特的G-P-M三层次模型&#xff0c;这是实现高效并发调度的核心架构。我们先拆解这个模型的基本组成&#xff1a; G代表goroutine&#xff0c;即Go语言的轻量级线程。每个goroutine初始仅需2KB栈空间&#xff0c;远比传统线…

作者头像 李华
网站建设 2026/10/29 23:26:49

ECharts setOption方法详解与性能优化实践

1. ECharts setOption方法的核心作用ECharts作为百度开源的数据可视化库&#xff0c;其核心能力都通过setOption方法实现。这个方法相当于整个图表的"大脑"&#xff0c;负责将配置数据转化为可视化图形。我在实际项目中发现&#xff0c;90%的图表异常问题都源于对set…

作者头像 李华
网站建设 2026/10/29 23:25:54

在线阅读短信的5种方法:跨设备协同、邮箱转发与自建同步通道

短信这东西&#xff0c;平时躺在手机里不觉得稀奇&#xff0c;真到需要翻出来看的时候——比如换手机忘了迁移、旧设备开不了机、想找一条验证码或者快递通知——才发现它比想象中难搞。标题里说的“在线阅读短信”&#xff0c;本质上不是让你在浏览器里直接打开短信&#xff0…

作者头像 李华
网站建设 2026/10/29 23:24:59

ANSYS预应力模拟:等效荷载法与实体力筋法的力学本质对比

简介&#xff1a;本资源是一份面向结构工程师与ANSYS初/中级仿真用户的预应力混凝土建模分析技术指南&#xff0c;聚焦ANSYS中两种核心预应力施加方法——等效荷载法与实体力筋法的原理辨析、适用边界及实操要点。内容系统对比两类方法在建模复杂度、应力损失模拟、力筋线形适应…

作者头像 李华