You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas安卓端单指操作touchend事件不触发问题排查

问题根因

核心bug是触摸点列表读取逻辑错误,属于移动端触摸事件开发的典型误区:

  • event.touches仅保存当前仍接触屏幕的活跃触摸点。单指操作触发touchend时,拖拽用的手指已经离开屏幕,event.touches长度为0,此时读取event.touches[0].clientX/clientY会直接抛出空对象错误,后续弹射逻辑直接中断,表现为单指操作完全失效。
  • 双指操作时,松开拖拽的手指后屏幕上还剩另一根未抬起的手指,event.touches[0]会指向这根剩余手指,不会触发空对象报错,逻辑可以正常执行,因此表现为双指操作可生效。

代码另外还有两个潜在隐患:

  • 直接读取clientX/clientY拿到的是浏览器视口坐标,没有换算为canvas画布内部坐标,如果canvas存在CSS缩放、边距、定位偏移,会出现触摸点和小球位置错位、点选不准的问题。
  • touchcancel事件没有重置拖拽状态,操作中如果触发系统中断(如弹出通知、手势切应用、来电),会导致拖拽状态锁死,后续操作逻辑混乱。
修复方案
  1. 触摸事件中,凡是需要获取触发当前事件的触摸点(尤其是touchend/touchcancel这类手指离开屏幕触发的事件),统一从event.changedTouches列表读取,该列表会保存触发本次事件的所有触摸点,不受手指是否离开屏幕影响。
  2. 新增视口坐标到canvas内部坐标的换算逻辑,消除布局偏移、缩放带来的坐标错位。
  3. 补全touchend/touchcancel的状态重置逻辑,避免状态异常。
  4. 增加触摸点唯一标识记录,避免多指操作时触摸点串扰。

修复后的完整代码如下:

// 若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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 17:36:20