Skip to main content

多点触控画布缩放与捏合缩放

如何为画布舞台启用平移和捏合缩放?

touchmove 回调中,我们可以通过 e.evt.touches 访问所有触控事件的原生属性。所以当使用两个指点触控时,我们只需手动计算舞台的位置和缩放属性。

注意:该实验仅在支持多点触控手势的设备上工作,因为它使用了多个触控事件。

说明: 使用支持多点触控手势的移动设备,用两个手指放大或缩小舞台。

import Konva from 'konva';

// 默认情况下,Konva在节点拖动时会阻止某些事件
// 这提高了性能并在95%的情况下工作良好
// 我们需要在Konva上启用所有事件,即使在拖动节点时
// 这样可以正确触发touchmove
Konva.hitOnDragEnabled = true;

const width = window.innerWidth;
const height = window.innerHeight;

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

const layer = new Konva.Layer();

const triangle = new Konva.RegularPolygon({
  x: 190,
  y: stage.height() / 2,
  sides: 3,
  radius: 80,
  fill: 'green',
  stroke: 'black',
  strokeWidth: 4,
});

const circle = new Konva.Circle({
  x: 380,
  y: stage.height() / 2,
  radius: 70,
  fill: 'red',
  stroke: 'black',
  strokeWidth: 4,
});

function getDistance(p1, p2) {
  return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}

function getCenter(p1, p2) {
  return {
    x: (p1.x + p2.x) / 2,
    y: (p1.y + p2.y) / 2,
  };
}

let lastCenter = null;
let lastDist = 0;
let dragStopped = false;

stage.on('touchmove', function (e) {
  e.evt.preventDefault();
  const touch1 = e.evt.touches[0];
  const touch2 = e.evt.touches[1];

  // 如果多点触控取消了拖动,我们需要恢复拖动
  if (touch1 && !touch2 && !stage.isDragging() && dragStopped) {
    stage.startDrag();
    dragStopped = false;
  }

  if (touch1 && touch2) {
    // 如果舞台在Konva的拖放中
    // 我们需要停止它,并用两个指点实现我们自己的平移逻辑
    if (stage.isDragging()) {
      dragStopped = true;
      stage.stopDrag();
    }

    const p1 = {
      x: touch1.clientX,
      y: touch1.clientY,
    };
    const p2 = {
      x: touch2.clientX,
      y: touch2.clientY,
    };

    if (!lastCenter) {
      lastCenter = getCenter(p1, p2);
      return;
    }
    const newCenter = getCenter(p1, p2);

    const dist = getDistance(p1, p2);

    if (!lastDist) {
      lastDist = dist;
    }

    // 中心点的局部坐标
    const pointTo = {
      x: (newCenter.x - stage.x()) / stage.scaleX(),
      y: (newCenter.y - stage.y()) / stage.scaleX(),
    };

    const scale = stage.scaleX() * (dist / lastDist);

    stage.scaleX(scale);
    stage.scaleY(scale);

    // 计算舞台的新位置
    const dx = newCenter.x - lastCenter.x;
    const dy = newCenter.y - lastCenter.y;

    const newPos = {
      x: newCenter.x - pointTo.x * scale + dx,
      y: newCenter.y - pointTo.y * scale + dy,
    };

    stage.position(newPos);

    lastDist = dist;
    lastCenter = newCenter;
  }
});

stage.on('touchend', function () {
  lastDist = 0;
  lastCenter = null;
});

layer.add(triangle);
layer.add(circle);
stage.add(layer);