移动端怎么正确识别捏合手势缩放?

打工人俊娜 阅读 3

我在做移动端图片预览功能,想用原生 JS 实现捏合缩放,但试了 touchstart 和 touchmove 事件,两个手指的距离计算好像不对,缩放时特别卡顿还容易误触。

我用的是 event.touches.length === 2 判断双指,然后算两点间距离,但有时候会突然跳变。有没有更稳定的实现方式?

function getDistance(touch1, touch2) {
  const dx = touch2.clientX - touch1.clientX;
  const dy = touch2.clientY - touch1.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}
我来解答 赞 2 收藏
二维码
手机扫码查看
1 条解答
东方小倩
第一步,我们得解决触摸事件的问题。你说的 touchstart 和 touchmove 是正确的选择,但是确实需要处理好双指操作。你已经判断了 event.touches.length === 2 来确保是双指操作,这是对的。

第二步,计算两点之间的距离。你写的 getDistance 函数是没问题的,但是确实会出现跳变的情况。这是因为触摸点的位置变化可能会导致计算不连续。我们需要在每次触摸移动的时候记录上一次触摸点的位置,然后计算两次触摸位置的变化量,这样可以减少跳变。

第三步,处理缩放逻辑。我们可以使用一个 scale 变量来存储当前的缩放比例,并且在 touchmove 事件中更新这个值。同时,我们需要限制缩放的比例范围,避免用户把图片缩放到太小或者太大。

第四步,应用缩放效果。我们可以使用 CSS 的 transform 属性来实现缩放效果,这样可以利用浏览器的硬件加速,让动画更加流畅。

下面是一个完整的代码示例,包含了以上所有的逻辑:


// 获取图片元素
const image = document.getElementById('image');

// 定义一些变量来存储触摸状态
let isPinching = false; // 是否正在捏合
let initialDistance = 0; // 初始两指间的距离
let currentScale = 1; // 当前缩放比例
let lastScale = 1; // 上次的缩放比例
let originX = 0; // 图片左上角相对于视口的x坐标
let originY = 0; // 图片左上角相对于视口的y坐标

// 计算两点之间的距离
function getDistance(touch1, touch2) {
const dx = touch2.clientX - touch1.clientX;
const dy = touch2.clientY - touch1.clientY;
return Math.sqrt(dx * dx + dy * dy);
}

// 计算两点的中点
function getMidpoint(touch1, touch2) {
return {
x: (touch1.clientX + touch2.clientX) / 2,
y: (touch1.clientY + touch2.clientY) / 2
};
}

// 触摸开始事件
image.addEventListener('touchstart', function(event) {
if (event.touches.length === 2) {
isPinching = true;
// 记录初始距离
initialDistance = getDistance(event.touches[0], event.touches[1]);
// 记录初始的缩放比例
lastScale = currentScale;
// 记录图片的初始位置
originX = parseFloat(image.style.transform.match(/translateX(([d.-]+)px)/)?.[1] || 0);
originY = parseFloat(image.style.transform.match(/translateY(([d.-]+)px)/)?.[1] || 0);
}
});

// 触摸移动事件
image.addEventListener('touchmove', function(event) {
if (isPinching && event.touches.length === 2) {
// 计算当前距离
const distance = getDistance(event.touches[0], event.touches[1]);
// 计算缩放比例的变化量
const deltaScale = distance / initialDistance;
// 更新当前缩放比例
currentScale = lastScale * deltaScale;

// 限制缩放比例范围,比如 0.5 到 3 倍
currentScale = Math.max(0.5, Math.min(currentScale, 3));

// 计算缩放中心
const midpoint = getMidpoint(event.touches[0], event.touches[1]);

// 计算新的位置
const newOriginX = midpoint.x - (midpoint.x - originX) * (currentScale / lastScale);
const newOriginY = midpoint.y - (midpoint.y - originY) * (currentScale / lastScale);

// 应用变换
image.style.transform = translate(${newOriginX}px, ${newOriginY}px) scale(${currentScale});
}
});

// 触摸结束事件
image.addEventListener('touchend', function() {
if (event.touches.length < 2) {
isPinching = false;
}
});


这段代码里,我们做了以下几个事情:
1. 监听 touchstart 事件,当检测到两个触摸点时,开始记录捏合状态和相关参数。
2. 在 touchmove 事件中,计算两个触摸点之间的距离变化,并更新图片的缩放比例和位置。
3. 在 touchend 事件中,检查触摸点数量,如果少于两个,则结束捏合状态。

这样处理后,应该能有效减少缩放过程中的跳变和卡顿问题。希望这个示例对你有帮助!
点赞
2026-03-25 05:02