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

TouchEvent开发问题:多指捏合时如何判断抬起的手指索引?

触摸手势中处理部分手指抬起的解决方案

核心原理

每个Touch对象自带唯一的identifier属性,从触摸开始到结束(或取消)全程不变,这是识别具体触摸点的关键。另外,TouchList是只读类数组对象,无法直接使用数组方法(如splice),所以需要将其转换为普通数组缓存。

双指场景修正代码

// 改为数组类型存储,方便修改
let cachedStartTouches: Touch[] = [];
let cachedMoveTouches: Touch[] = [];

function onStart(ev: TouchEvent) {
  // 将TouchList转为数组缓存
  cachedStartTouches = Array.from(ev.touches);
}

function onMove(ev: TouchEvent) {
  // 更新缓存时,按identifier匹配对应触摸点,而非直接替换整个数组
  const currentTouches = Array.from(ev.touches);
  cachedMoveTouches = cachedMoveTouches.map(cachedTouch => {
    const match = currentTouches.find(t => t.identifier === cachedTouch.identifier);
    return match || cachedTouch;
  });
  // 补充新加入的触摸点(适配多指逐步按下的场景)
  const newTouches = currentTouches.filter(t => !cachedMoveTouches.some(c => c.identifier === t.identifier));
  cachedMoveTouches.push(...newTouches);
}

function onEnd(ev: TouchEvent) {
  // 获取本次结束的触摸点集合
  const endedTouches = Array.from(ev.changedTouches);
  
  // 逐个移除缓存中对应的触摸点
  endedTouches.forEach(endedTouch => {
    // 找到缓存中匹配identifier的索引
    const startIdx = cachedStartTouches.findIndex(t => t.identifier === endedTouch.identifier);
    const moveIdx = cachedMoveTouches.findIndex(t => t.identifier === endedTouch.identifier);
    
    if (startIdx !== -1) cachedStartTouches.splice(startIdx, 1);
    if (moveIdx !== -1) cachedMoveTouches.splice(moveIdx, 1);
  });

  // 剩余触摸点数量:cachedStartTouches.length
}

N指捏合的通用处理逻辑

  1. 初始化与缓存:

    • 触摸开始时,将所有Touch转为数组缓存,同时记录每个触摸点的初始位置、时间等手势所需基础数据。
    • 建议维护自定义数据结构(而非直接存Touch对象),避免浏览器内部对象的潜在问题,示例结构参考下方代码。
  2. 触摸移动更新:

    • 每次移动事件触发时,用当前ev.touches中的identifier匹配缓存项,更新对应触摸点的实时数据。
    • 适配逐步按下的多指场景:如果当前触摸点存在缓存中没有的identifier,将其加入缓存并记录初始数据。
  3. 手指抬起/取消处理:

    • 同时监听touchEnd和touchCancel事件,通过ev.changedTouches获取本次结束的所有触摸点。
    • 遍历这些触摸点,用identifier在缓存数组中找到对应项并移除。
    • 根据剩余触摸点数量决定后续逻辑:比如N指捏合变为N-1指时,调整缩放/旋转计算逻辑;若剩余触摸点不足,则结束当前手势。
  4. 自定义缓存结构示例

// 自定义缓存结构,存储手势所需核心数据
interface TouchGestureData {
  identifier: number;
  startX: number;
  startY: number;
  currentX: number;
  currentY: number;
}

let gestureTouches: TouchGestureData[] = [];

function onStart(ev: TouchEvent) {
  Array.from(ev.touches).forEach(touch => {
    const existing = gestureTouches.find(g => g.identifier === touch.identifier);
    if (!existing) {
      gestureTouches.push({
        identifier: touch.identifier,
        startX: touch.clientX,
        startY: touch.clientY,
        currentX: touch.clientX,
        currentY: touch.clientY
      });
    }
  });
}

function onMove(ev: TouchEvent) {
  Array.from(ev.touches).forEach(touch => {
    const gestureData = gestureTouches.find(g => g.identifier === touch.identifier);
    if (gestureData) {
      gestureData.currentX = touch.clientX;
      gestureData.currentY = touch.clientY;
    }
  });
  // 执行N指手势计算(如缩放比例、旋转角度)
  calculateMultiFingerGesture(gestureTouches);
}

function onEndOrCancel(ev: TouchEvent) {
  const changedIds = Array.from(ev.changedTouches).map(t => t.identifier);
  gestureTouches = gestureTouches.filter(g => !changedIds.includes(g.identifier));
  
  // 如果剩余触摸点不足,结束手势;否则继续
  if (gestureTouches.length < 2) {
    endGesture();
  }
}

function calculateMultiFingerGesture(touches: TouchGestureData[]) {
  // 实现N指缩放、旋转等逻辑
  if (touches.length >= 2) {
    // 计算两点距离、中心等
  }
}

内容的提问来源于stack exchange,提问作者eczn

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 04:07:02