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 性能优化实践对于高性能要求的应用如游戏或绘图应用触摸事件处理需要特别注意性能节流处理对于高频的touchmove事件可以使用requestAnimationFrame进行节流let ticking false; element.addEventListener(touchmove, function(e) { if (!ticking) { requestAnimationFrame(function() { processTouchMove(e.changedTouches); ticking false; }); ticking true; } });减少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则更适合需要了解全局触摸状态的场景如多点触控的实现。
阅读完成 · 觉得有帮助?