changedTouches为什么包含多个触点时无法正确跟踪触摸移动?

米娅酱~ 阅读 19

我在移动端用touchmove事件处理多点触控时发现,当changedTouches里有多个触点时,怎么都拿不到某个特定触点的移动轨迹?比如用类似这样设置的圆点元素:


.touch-point {
  position: absolute;
  width: 20px;
  height: 20px;
  background: red;
  border-radius: 50%;
}

我尝试用 touches[e.identifier] 和 targetTouches[e.identifier] 都不行,坐标总是跳变,甚至会跟丢触点,是不是哪里理解错了?

我来解答 赞 7 收藏
二维码
手机扫码查看
2 条解答
夏侯正毅
changedTouches 是用来表示当前事件中发生变化的触点列表,但问题是它并不区分具体哪个手指对应哪个元素。如果你直接用它来跟踪特定触点,坐标跳变和跟丢触点是很正常的,因为它只记录变化,不保证持续性。

正确的做法是用 touches 列表,并结合每个触点的唯一标识符 identifier 来跟踪。以下是高效实现的代码示例:

const touchPoints = {};

document.addEventListener('touchstart', (e) => {
e.touches.forEach(touch => {
const { identifier, clientX, clientY } = touch;
const point = document.createElement('div');
point.className = 'touch-point';
point.style.left = ${clientX - 10}px;
point.style.top = ${clientY - 10}px;
document.body.appendChild(point);
touchPoints[identifier] = point; // 保存触点与元素的映射关系
});
});

document.addEventListener('touchmove', (e) => {
e.preventDefault(); // 防止默认行为干扰
e.touches.forEach(touch => {
const { identifier, clientX, clientY } = touch;
const point = touchPoints[identifier];
if (point) {
point.style.left = ${clientX - 10}px;
point.style.top = ${clientY - 10}px;
}
});
});

document.addEventListener('touchend', (e) => {
e.changedTouches.forEach(touch => {
const { identifier } = touch;
const point = touchPoints[identifier];
if (point) {
document.body.removeChild(point); // 移除结束的触点
delete touchPoints[identifier];
}
});
});


重点在于:
1. 使用 identifier 唯一标识每个触点,避免混淆。
2. 把触点和对应的 DOM 元素存到一个对象里,效率更高。
3. touchmove 时直接从 touches 获取数据,而不是 changedTouches

这样就能准确跟踪每个触点的移动轨迹了,不会再出现跟丢或跳变的情况。
点赞 4
2026-02-02 17:08
Dev · 紫瑶
好的,这个问题确实是个常见的坑,尤其是在处理多点触控时。changedTouches 本身的设计就是用来表示状态发生变化的触点集合(比如某个手指开始按下了、移动了、或者抬起了),但它并不适合直接用来跟踪每个触点的完整轨迹。下面我分步骤来解释怎么解决你的问题。

---

### 第一步:理解 toucheschangedTouches 的区别
- touches 是当前屏幕上所有的触点集合,无论这些触点的状态是否发生了变化。
- changedTouches 是那些状态发生变化的触点集合,比如新按下的手指、抬起的手指、或者正在移动的手指。

当你需要跟踪某个特定触点的移动轨迹时,不能仅仅依赖 changedTouches,因为它的设计是动态的,每次事件触发时只会包含“状态变化”的触点,而不是所有触点。所以,如果某些触点没有发生状态变化(比如其他手指静止不动),它们不会出现在 changedTouches 中。

---

### 第二步:如何正确跟踪每个触点
为了跟踪每个触点的移动轨迹,你需要用到以下几个关键点:
1. 每个触点都有一个唯一的标识符 e.identifier,可以用它来区分不同的手指。
2. 使用 touches 而不是 changedTouches 来获取所有当前屏幕上的触点。
3. 维护一个全局的对象或数组,用来存储每个触点的当前位置。

---

### 第三步:代码实现
下面是一个完整的代码示例,展示如何正确跟踪多点触控的移动轨迹:

// 定义一个对象来保存每个触点的位置
const touchPoints = {};

// 添加 touchstart 事件监听器
document.addEventListener('touchstart', (e) => {
e.preventDefault(); // 阻止默认行为

// 遍历 touches 获取每个触点的初始位置
for (let touch of e.touches) {
const id = touch.identifier; // 获取触点的唯一标识符
const { clientX, clientY } = touch; // 获取触点的坐标

// 存储触点的初始位置
touchPoints[id] = { x: clientX, y: clientY };

console.log(Touch ${id} started at (${clientX}, ${clientY}));
}
});

// 添加 touchmove 事件监听器
document.addEventListener('touchmove', (e) => {
e.preventDefault(); // 阻止默认行为

// 遍历 touches 更新每个触点的位置
for (let touch of e.touches) {
const id = touch.identifier; // 获取触点的唯一标识符
const { clientX, clientY } = touch; // 获取触点的新坐标

// 更新触点的位置
touchPoints[id] = { x: clientX, y: clientY };

console.log(Touch ${id} moved to (${clientX}, ${clientY}));
}

// 根据 touchPoints 更新圆点元素的位置
updateCircles();
});

// 添加 touchend 和 touchcancel 事件监听器
document.addEventListener('touchend', (e) => {
e.preventDefault(); // 阻止默认行为

// 遍历 changedTouches 移除结束的触点
for (let touch of e.changedTouches) {
const id = touch.identifier;

console.log(Touch ${id} ended);

// 删除结束的触点
delete touchPoints[id];
}

updateCircles();
});

document.addEventListener('touchcancel', (e) => {
e.preventDefault(); // 阻止默认行为

// 处理 touchcancel(比如用户离开页面)
for (let touch of e.changedTouches) {
const id = touch.identifier;
delete touchPoints[id];
}

updateCircles();
});

// 更新圆点元素的位置
function updateCircles() {
// 假设你有一个 DOM 结构,每个圆点的 id 是 circle-${id}
for (let id in touchPoints) {
const circle = document.getElementById(circle-${id});
if (circle) {
const { x, y } = touchPoints[id];
circle.style.transform = translate(${x - 10}px, ${y - 10}px);
}
}
}


---

### 第四步:解释代码逻辑
1. **touchstart**:当手指按下时,我们遍历 e.touches,获取每个触点的初始位置,并用 e.identifier 作为键存储到 touchPoints 对象中。
2. **touchmove**:在手指移动时,我们同样遍历 e.touches,更新每个触点的最新位置。
3. **touchendtouchcancel**:当手指抬起或被取消时,我们通过 e.changedTouches 找到对应的触点,并从 touchPoints 中删除。
4. **updateCircles**:根据 touchPoints 中存储的坐标,动态更新每个圆点元素的位置。

---

### 第五步:常见问题排查
1. 如果发现坐标跳变,可能是因为你误用了 changedTouches,导致丢失了某些触点的信息。
2. 确保你在 touchmove 中使用的是 e.touches,而不是 e.changedTouches
3. 如果圆点元素没有正确跟随手指移动,检查 CSS 是否设置了正确的 position: absolute;transform

---

希望这段代码和解释能帮你解决问题!如果有其他问题随时问。
点赞 22
2026-01-29 08:04