Multi-touch Canvas scale with pinch zoom

How to enable pan and pinch zoom for canvas stage?

Inside touchmove callback we can get access to all native properties of touch events with e.evt.touches. So we just need to manually calculate position and scale properties of the stage, when two pointers are used in touchmove.

Note: This lab only works on devices that support multi-touch gestures because it makes use of multiple touch events.

Instructions: Using a mobile device that supports multi-touch gestures, use two fingers to zoom in or out of the stage

Konva Multi-touch Scale Stageview raw
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/[email protected]/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Multi-touch Scale Stage Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
</style>
</head>
<body>
<div id="container"></div>
<script>
// by default Konva prevent some events when node is dragging
// it improve the performance and work well for 95% of cases
// we need to enable all events on Konva, even when we are dragging a node
// so it triggers touchmove correctly
Konva.hitOnDragEnabled = true;

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

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

var layer = new Konva.Layer();

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

var 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,
};
}
var lastCenter = null;
var lastDist = 0;
var dragStopped = false;

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

// we need to restore dragging, if it was cancelled by multi-touch
if (touch1 && !touch2 && !stage.isDragging() && dragStopped) {
stage.startDrag();
dragStopped = false;
}

if (touch1 && touch2) {
// if the stage was under Konva's drag&drop
// we need to stop it, and implement our own pan logic with two pointers
if (stage.isDragging()) {
dragStopped = true;
stage.stopDrag();
}

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

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

var dist = getDistance(p1, p2);

if (!lastDist) {
lastDist = dist;
}

// local coordinates of center point
var pointTo = {
x: (newCenter.x - stage.x()) / stage.scaleX(),
y: (newCenter.y - stage.y()) / stage.scaleX(),
};

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

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

// calculate new position of the stage
var dx = newCenter.x - lastCenter.x;
var dy = newCenter.y - lastCenter.y;

var 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 (e) {
lastDist = 0;
lastCenter = null;
});

layer.add(triangle);
layer.add(circle);
stage.add(layer);
</script>
</body>
</html>
Enjoying Konva? Please consider to support the project.