4

Using the sample code from Konvajs.org as a base (https://konvajs.org/docs/sandbox/Multi-touch_Scale_Stage.html), I have added a large SVG to a layer (4096 x 3444) to experiment with zoom / pan of a vector-based map, base64 encoded SVG in this instance. Initial impressions are good however during testing I experience an odd bug where during a pinch the view of the map would snap to a different location on the map not the area that I centred on.

Here is the code (map base64 code removed due to length):

// 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,
});

let bg = new Konva.Image({
    width: 4096,
    height: 3444
});
layer.add(bg);

var image = new Image();
image.onload = function() {
    bg.image(image);
    layer.draw();
};

image.src = 'data:image/svg+xml;base64,...';

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;

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

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


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

I am unsure if this is due to the large size of the image and / or canvas or an inherent flaw in the example code from Konvas.js. This has been tested, with the same results, on 2 models of iPad Pro, iPhone X & 11, Android Pixel 3, 5 and 6 Pro.

Here is the code on codepen as an example: https://codepen.io/mr-jose/pen/WNXgbdG

Any help would be appreciated, thanks!

Jose
  • 43
  • 2
  • Hi Jose - that's a well written question for a new contributor. I can't answer your question specifically, but I wanted to mention that with use-cases like maps with heavy zoom-in-and-out there is usually a tiling approach in use whereby a zoom-in might causes another 'deeper' image to load into the viewer, thus giving the level of detail the user expects as a payoff for zooming. So what I'm saying is that ultimately you might want to consider that in your follow-on dev on the project. Otherwise I'm interested to see the answer too! – Vanquished Wombat Feb 28 '22 at 19:45
  • Thanks @VanquishedWombat I actually tried that approach with exactly the same results. I split a map into 16 1028 x 1028 and 2048 x 2048 png tiles. The size of the tiles had no perceivable impact. – Jose Mar 01 '22 at 08:32
  • 2
    Hi @Jose. Were you able to solve this problem? If so could you please share? – Mykhailo K. Nov 28 '22 at 12:46
  • 1
    @MykhailoK. the solution from andy203 works a treat. – Jose Jan 25 '23 at 11:25

1 Answers1

0

I faced the same issue and discovered that it was caused by the dragging functionality of the stage. Everytime if (stage.isDragging()) evaluated to true, the jump happened.

For me what worked was setting draggable to false while pinch zooming and back to true on touch end.

stage.on('touchmove', function (e) {
  ...
  if (touch1 && touch2) {
    stage.draggable(false);
    ....
  }
});

stage.on('touchend', function (e) {
  ...
  stage.draggable(true);
});
andy203
  • 16
  • 1