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

如何用JavaScript实现捏合缩放及双指平移功能?

实现带平移的双指捏合缩放功能

需要用JavaScript实现捏合缩放功能,目前已能通过touch事件检测手势,但需要一套完善的算法,同时处理缩放和平移,确保缩放中心始终在双指之间;另外当双指保持等距移动时,要实现和单指拖拽一样的纯平移效果。

完整实现代码

HTML 结构

<div id="zoom-container">
  <img id="zoom-img" src="替换为你的图片地址" alt="可缩放图片" />
</div>

CSS 样式

#zoom-container {
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  position: relative;
}

#zoom-img {
  position: absolute;
  transform-origin: center center;
  transition: transform 0.05s ease-out; /* 让缩放/平移更平滑 */
  cursor: grab;
}

JavaScript 核心逻辑

const zoomContainer = document.getElementById('zoom-container');
const zoomImg = document.getElementById('zoom-img');

// 记录手势状态,避免每次操作从头计算
let initialScale = 1;
let currentScale = 1;
let initialTranslate = { x: 0, y: 0 };
let currentTranslate = { x: 0, y: 0 };
let initialFingerDistance = 0;
let initialMidPoint = { x: 0, y: 0 };

// 获取元素在视口中的位置信息
function getElementRect(el) {
  return el.getBoundingClientRect();
}

// 计算双指的中点坐标
function getMidPoint(touch1, touch2) {
  return {
    x: (touch1.pageX + touch2.pageX) / 2,
    y: (touch1.pageY + touch2.pageY) / 2
  };
}

// 计算双指之间的距离
function getFingerDistance(touch1, touch2) {
  return Math.hypot(touch1.pageX - touch2.pageX, touch1.pageY - touch2.pageY);
}

// 更新元素的变换样式
function updateTransform() {
  zoomImg.style.transform = `translate(${currentTranslate.x}px, ${currentTranslate.y}px) scale(${currentScale})`;
}

// 触摸开始:初始化状态
zoomContainer.addEventListener('touchstart', (e) => {
  e.preventDefault(); // 阻止默认的页面滚动行为
  const touches = e.targetTouches;

  if (touches.length === 2) {
    // 双指触摸,初始化缩放相关参数
    initialFingerDistance = getFingerDistance(touches[0], touches[1]);
    initialMidPoint = getMidPoint(touches[0], touches[1]);
    initialScale = currentScale;
    initialTranslate = { ...currentTranslate };

    // 把变换原点设为双指中点,确保缩放围绕这个点进行
    const imgRect = getElementRect(zoomImg);
    const relativeMidX = (initialMidPoint.x - imgRect.left) / imgRect.width;
    const relativeMidY = (initialMidPoint.y - imgRect.top) / imgRect.height;
    zoomImg.style.transformOrigin = `${relativeMidX * 100}% ${relativeMidY * 100}%`;
  } else if (touches.length === 1) {
    // 单指触摸,初始化平移参数
    initialTranslate = { ...currentTranslate };
    initialMidPoint = { x: touches[0].pageX, y: touches[0].pageY };
  }
});

// 触摸移动:处理缩放和平移
zoomContainer.addEventListener('touchmove', (e) => {
  e.preventDefault();
  const touches = e.targetTouches;

  if (touches.length === 2) {
    const currentFingerDistance = getFingerDistance(touches[0], touches[1]);
    const currentMidPoint = getMidPoint(touches[0], touches[1]);

    // 计算当前缩放比例:新距离/初始距离 * 初始缩放值
    const scaleFactor = currentFingerDistance / initialFingerDistance;
    currentScale = initialScale * scaleFactor;

    // 计算平移偏移:让图片跟随双指中点移动
    const deltaX = currentMidPoint.x - initialMidPoint.x;
    const deltaY = currentMidPoint.y - initialMidPoint.y;
    currentTranslate.x = initialTranslate.x + deltaX;
    currentTranslate.y = initialTranslate.y + deltaY;

    updateTransform();
  } else if (touches.length === 1) {
    // 单指拖拽:纯平移逻辑
    const deltaX = touches[0].pageX - initialMidPoint.x;
    const deltaY = touches[0].pageY - initialMidPoint.y;
    currentTranslate.x = initialTranslate.x + deltaX;
    currentTranslate.y = initialTranslate.y + deltaY;

    updateTransform();
  }
});

// 触摸结束:重置变换原点,避免后续操作异常
zoomContainer.addEventListener('touchend', (e) => {
  if (e.targetTouches.length === 0) {
    zoomImg.style.transformOrigin = 'center center';
  }
});

关键逻辑说明

  • 状态持久化:用变量记录每次手势的初始状态,确保缩放和平移都是基于上一次操作的结果计算,不会出现跳变
  • 缩放中心锁定:双指触摸时将transformOrigin设为双指中点,让缩放始终围绕双指中间进行,符合直觉
  • 平移同步处理:双指移动时,同步计算中点的偏移量并叠加到平移值上,当双指等距移动时,缩放比例不变,自然实现纯平移效果
  • 单指兼容:保留单指拖拽逻辑,和双指操作无缝切换,提升交互体验

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 03:55:25