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

Konva舞台捏合缩放时如何基于Pinch点居中且避免位置跳变?

解决Konva舞台捏合缩放时基于中心点定位的问题

这个问题我之前在做Konva交互式项目时也踩过坑——默认缩放以舞台左上角为原点,再加上draggable: true的自动位置更新,确实会导致缩放时位置跳变。核心矛盾是draggable的自动位置调整和我们手动基于捏合中心的位置修正冲突,下面是一套亲测可行的解决方案:

核心思路

  • 在捏合缩放开始时,临时禁用舞台的draggable,避免自动位置干扰
  • 计算捏合中心在舞台本地坐标系下的位置
  • 根据缩放比例,反向计算舞台需要偏移的距离,保证捏合中心始终在屏幕原位置
  • 捏合结束后,恢复舞台的draggable功能

具体实现代码

const stage = new Konva.Stage({
  container: 'container',
  width: window.innerWidth,
  height: window.innerHeight,
  draggable: true
});

// 记录捏合状态和初始参数
let isPinching = false;
let initialScale = 1;
let initialStagePos = { x: 0, y: 0 };
let initialPinchCenter = { x: 0, y: 0 };

// 监听touchstart,判断是否进入捏合状态
stage.container().addEventListener('touchstart', (e) => {
  if (e.touches.length === 2) {
    isPinching = true;
    // 临时禁用draggable,避免自动位置逻辑干扰
    stage.draggable(false);
    // 保存缩放前的初始状态
    initialScale = stage.scaleX();
    initialStagePos = { x: stage.x(), y: stage.y() };
    // 计算初始捏合中心的屏幕坐标,再转成舞台本地坐标
    const touch1 = e.touches[0];
    const touch2 = e.touches[1];
    const globalCenterX = (touch1.clientX + touch2.clientX) / 2;
    const globalCenterY = (touch1.clientY + touch2.clientY) / 2;
    initialPinchCenter = stage.getPointerPosition({
      clientX: globalCenterX,
      clientY: globalCenterY
    });
  }
});

// 监听touchmove,处理缩放和位置调整
stage.container().addEventListener('touchmove', (e) => {
  if (!isPinching || e.touches.length !== 2) return;
  
  e.preventDefault(); // 阻止页面默认滚动行为
  
  // 计算当前捏合的距离与初始距离的比例,得到缩放倍数
  const touch1 = e.touches[0];
  const touch2 = e.touches[1];
  const currentDistance = Math.sqrt(
    Math.pow(touch2.clientX - touch1.clientX, 2) +
    Math.pow(touch2.clientY - touch1.clientY, 2)
  );
  const initialDistance = Math.sqrt(
    Math.pow(touch2.clientX - touch1.clientX, 2) +
    Math.pow(touch2.clientY - touch1.clientY, 2)
  );
  const scale = initialScale * (currentDistance / initialDistance);
  
  // 计算缩放后,为保持捏合中心位置不变,舞台需要偏移的量
  const newStageX = initialStagePos.x + initialPinchCenter.x * (initialScale - scale);
  const newStageY = initialStagePos.y + initialPinchCenter.y * (initialScale - scale);
  
  // 更新舞台的缩放比例和位置
  stage.scale({ x: scale, y: scale });
  stage.position({ x: newStageX, y: newStageY });
  stage.draw();
});

// 监听touchend,恢复draggable功能
stage.container().addEventListener('touchend', (e) => {
  if (isPinching) {
    isPinching = false;
    stage.draggable(true);
  }
});

关键细节解释

  • 临时禁用draggable:捏合过程中必须关掉自动拖拽,否则Konva内置的draggable逻辑会自动修改stage的x/y值,和我们手动计算的位置冲突,直接导致跳变。
  • 坐标转换:stage.getPointerPosition()是核心——它能把屏幕上的捏合中心坐标转换成舞台内部的相对坐标,只有拿到这个值,才能准确计算缩放后的舞台偏移量。
  • 偏移量计算逻辑:initialPinchCenter.x * (initialScale - scale)的本质是:缩放后,捏合中心在舞台上的相对位置会跟着缩放比例变化,我们需要反向移动舞台,让这个中心在屏幕上的视觉位置保持不变。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:09:06