多点触控手势怎么监听缩放操作?

IT人艳丽 阅读 94

我在移动端做图片查看器,想实现双指缩放,但 touchstart 和 touchmove 事件里拿到的 touches 长度有时候不对,缩放时经常触发两次甚至更多次处理,逻辑乱了。

我试过用两个 touch 点的距离计算缩放比例,但手指刚接触屏幕时 touches 数量变化太快,导致初始距离算错。下面是我写的部分代码:

let startDistance = 0;

function handleTouchStart(e) {
  if (e.touches.length === 2) {
    const dx = e.touches[0].pageX - e.touches[1].pageX;
    const dy = e.touches[0].pageY - e.touches[1].pageY;
    startDistance = Math.sqrt(dx * dx + dy * dy);
  }
}

function handleTouchMove(e) {
  if (e.touches.length === 2) {
    // 这里有时拿不到正确的 startDistance
    const dx = e.touches[0].pageX - e.touches[1].pageX;
    const dy = e.touches[0].pageY - e.touches[1].pageY;
    const currentDistance = Math.sqrt(dx * dx + dy * dy);
    const scale = currentDistance / startDistance;
    console.log('scale:', scale);
  }
}
我来解答 赞 5 收藏
二维码
手机扫码查看
2 条解答
Top丶子荧
哈,这个问题我去年做图片查看器时踩过一模一样的坑。别走弯路了,直接告诉你解决方案。

关键问题在于手指刚接触屏幕时,两个 touch 事件不是同时到达的。有时候第一个手指的 touchstart 先触发,这时候 e.touches.length 是 1,第二个手指的 touchstart 才把长度变成 2。

解决方法是用一个标志位来标记是否已经准备好缩放,等两个手指都稳定了才开始计算。我改过的版本是这样的:

let startDistance = 0;
let isReady = false;

function handleTouchStart(e) {
if (e.touches.length === 2) {
const dx = e.touches[0].pageX - e.touches[1].pageX;
const dy = e.touches[0].pageY - e.touches[1].pageY;
startDistance = Math.sqrt(dx dx + dy dy);
isReady = true;
} else {
isReady = false;
}
}

function handleTouchMove(e) {
if (!isReady || e.touches.length !== 2) return;

const dx = e.touches[0].pageX - e.touches[1].pageX;
const dy = e.touches[0].pageY - e.touches[1].pageY;
const currentDistance = Math.sqrt(dx dx + dy dy);
const scale = currentDistance / startDistance;
console.log('scale:', scale);
}

另外建议加点防抖处理,不然缩放时控制台会被 log 刷屏。我当时还加了个最小移动距离的阈值,小于 5px 的移动直接忽略,这样体验会好很多。

这个坑最烦人的是不同机型表现还不一样,有些安卓机会疯狂触发 touchmove。如果要做更复杂的手势识别,建议直接用现成的库比如 hammer.js,自己处理这些边界情况太折磨人了。
点赞
2026-03-06 13:15
夏侯佳妮
兄弟,touchstart 在每个手指触摸时都会触发一次,所以第一个手指下去时 touches.length 是 1,第二个手指才变成 2,你的初始化时机不对。正确的做法是在 touchmove 里检测是否刚进入双指状态,如果是就重新计算初始距离。

let startDistance = 0;
let isScaling = false;

function getDistance(touches) {
const dx = touches[0].pageX - touches[1].pageX;
const dy = touches[0].pageY - touches[1].pageY;
return Math.sqrt(dx * dx + dy * dy);
}

function handleTouchStart(e) {
if (e.touches.length === 2) {
startDistance = getDistance(e.touches);
isScaling = true;
}
}

function handleTouchMove(e) {
if (e.touches.length === 2) {
if (!isScaling) {
startDistance = getDistance(e.touches);
isScaling = true;
}
const currentDistance = getDistance(e.touches);
const scale = currentDistance / startDistance;
console.log('scale:', scale);
}
}

function handleTouchEnd(e) {
if (e.touches.length < 2) {
isScaling = false;
}
}


记得把三个事件都绑上,touchend 也要处理,不然 isScaling 状态会乱。凌晨三点调试这种问题确实让人头秃。
点赞 4
2026-03-01 09:13