Canvas安卓端单指操作touchend事件不触发问题排查
问题根因
核心bug是触摸点列表读取逻辑错误,属于移动端触摸事件开发的典型误区:
event.touches仅保存当前仍接触屏幕的活跃触摸点。单指操作触发touchend时,拖拽用的手指已经离开屏幕,event.touches长度为0,此时读取event.touches[0].clientX/clientY会直接抛出空对象错误,后续弹射逻辑直接中断,表现为单指操作完全失效。- 双指操作时,松开拖拽的手指后屏幕上还剩另一根未抬起的手指,
event.touches[0]会指向这根剩余手指,不会触发空对象报错,逻辑可以正常执行,因此表现为双指操作可生效。
代码另外还有两个潜在隐患:
- 直接读取
clientX/clientY拿到的是浏览器视口坐标,没有换算为canvas画布内部坐标,如果canvas存在CSS缩放、边距、定位偏移,会出现触摸点和小球位置错位、点选不准的问题。 touchcancel事件没有重置拖拽状态,操作中如果触发系统中断(如弹出通知、手势切应用、来电),会导致拖拽状态锁死,后续操作逻辑混乱。
修复方案
- 触摸事件中,凡是需要获取触发当前事件的触摸点(尤其是
touchend/touchcancel这类手指离开屏幕触发的事件),统一从event.changedTouches列表读取,该列表会保存触发本次事件的所有触摸点,不受手指是否离开屏幕影响。 - 新增视口坐标到canvas内部坐标的换算逻辑,消除布局偏移、缩放带来的坐标错位。
- 补全
touchend/touchcancel的状态重置逻辑,避免状态异常。 - 增加触摸点唯一标识记录,避免多指操作时触摸点串扰。
修复后的完整代码如下:
// 若canvas尺寸/位置会动态变化,可在每次事件触发时重新调用该方法获取最新布局信息 const canvasRect = canvas.getBoundingClientRect(); // 记录触发拖拽的触摸点唯一ID,避免多指操作串扰 let activeTouchId = null; canvas.addEventListener("touchstart",function(event){ event.preventDefault(); const touch = event.changedTouches[0]; // 视口坐标转canvas内部绘制坐标 const touchX = touch.clientX - canvasRect.left; const touchY = touch.clientY - canvasRect.top; if (touchX >= x && touchX <= x + (radius * 2) && touchY >= y && touchY <= y + (radius * 2)) { dx = 0; dy = 0; isindrag = true; activeTouchId = touch.identifier; oldx = x; oldy = y; } }); canvas.addEventListener("touchmove", function (event) { event.preventDefault(); if (!isindrag) return; // 匹配初始拖拽的触摸点,忽略其他手指的动作 const touch = Array.from(event.changedTouches).find(t => t.identifier === activeTouchId); if (!touch) return; const touchX = touch.clientX - canvasRect.left; const touchY = touch.clientY - canvasRect.top; x = touchX; y = touchY; }); canvas.addEventListener("touchend", function (event) { // 匹配对应触摸点的抬起事件 const touch = Array.from(event.changedTouches).find(t => t.identifier === activeTouchId); if (!touch || !isindrag) return; const touchX = touch.clientX - canvasRect.left; const touchY = touch.clientY - canvasRect.top; // 重置拖拽状态 isindrag = false; activeTouchId = null; if (touchX < canvas.width && touchY < canvas.height && touchX > 0 && touchY > 0) { dx = -(x - oldx) / 30; dy = -(y - oldy) / 30; ismoving = true; } }); canvas.addEventListener("touchcancel", function(event){ event.preventDefault(); // 系统中断触发cancel事件时,重置所有拖拽状态 isindrag = false; activeTouchId = null; dx = 0; dy = 0; });
内容的提问来源于stack exchange,提问作者Alireza932
相关产品推荐
相关产品推荐

