Skip to main content

Gesture Events on Canvas Shapes

How to listen to swipe, pinch zoom, rotate and other multi-touch gesture events on canvas shapes?

By default Konva supports only basic touch events such as touchstart, touchmove, touchend.

You have to implement gesture events manually from that touch events.

If you are looking for pan and zoom logic for the whole stage take a look into Multi-touch scale Stage demo.

But I was able to slightly change Hammer.js to make it work with Konva!

You can find modified hammer.js source code here.

Instructions: you can try different gestures on the rectangle such as swipe, rotate, zoom, drag&drop, press. For desktop browsers you can hold Shift key to emulate touch events.

import Konva from 'konva';

// Load required scripts

const loadScript = (src) => {
  return new Promise((resolve, reject) => {
    if (document.querySelector(`script[src="${src}"]`)) {
      resolve();
      return;
    }
    const script = document.createElement('script');
    script.src = src;
    script.onload = resolve;
    script.onerror = reject;
    document.head.appendChild(script);
  });
};

// Initialize the demo after loading dependencies

async function initDemo() {
  try {
    await loadScript('https://cdn.rawgit.com/hammerjs/touchemulator/master/touch-emulator.js');
    await loadScript('https://konvajs.org/js/hammer-konva.js');

    // emulate touches on desktop

    TouchEmulator();
    Konva.hitOnDragEnabled = true;
    Konva.captureTouchEventsEnabled = true;

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

    const layer = new Konva.Layer();
    stage.add(layer);

    const originalAttrs = {
      x: stage.width() / 2,
      y: stage.height() / 2,
      scaleX: 1,
      scaleY: 1,
      draggable: true,
      rotation: 0,
    };

    const group = new Konva.Group(originalAttrs);
    layer.add(group);

    const size = 200;

    const rect = new Konva.Rect({
      width: size,
      height: size,
      fill: 'yellow',
      offsetX: size / 2,
      offsetY: size / 2,
      cornerRadius: 5,
      shadowBlur: 10,
      shadowColor: 'grey',
    });
    group.add(rect);

    const defaultText = 'Try\ndrag, swipe, pinch zoom, rotate, press...';
    const text = new Konva.Text({
      text: defaultText,
      x: -size / 2,
      width: size,
      align: 'center',
    });
    group.add(text);

    // attach modified version of Hammer.js

    // "domEvents" property will allow triggering events on group

    // instead of "hammertime" instance

    const hammertime = new Hammer(group, { domEvents: true });

    // add rotate gesture

    hammertime.get('rotate').set({ enable: true });

    // now attach all possible events

    group.on('swipe', function (ev) {
      text.text('swiping');
      group.to({
        x: group.x() + ev.evt.gesture.deltaX,
        y: group.y() + ev.evt.gesture.deltaY,
        onFinish: function () {
          group.to(Object.assign({}, originalAttrs));
          text.text(defaultText);
        },
      });
    });

    group.on('press', function (ev) {
      text.text('Under press');
      rect.to({
        fill: 'green',
      });
    });

    group.on('touchend', function (ev) {
      rect.to({
        fill: 'yellow',
      });

      setTimeout(() => {
        text.text(defaultText);
      }, 300);
    });

    group.on('dragend', () => {
      group.to(Object.assign({}, originalAttrs));
    });

    let oldRotation = 0;
    let startScale = 0;
    group.on('rotatestart', function (ev) {
      oldRotation = ev.evt.gesture.rotation;
      startScale = rect.scaleX();
      group.stopDrag();
      group.draggable(false);
      text.text('rotating...');
    });

    group.on('rotate', function (ev) {
      const delta = oldRotation - ev.evt.gesture.rotation;
      group.rotate(-delta);
      oldRotation = ev.evt.gesture.rotation;
      group.scaleX(startScale * ev.evt.gesture.scale);
      group.scaleY(startScale * ev.evt.gesture.scale);
    });

    group.on('rotateend rotatecancel', function (ev) {
      group.to(Object.assign({}, originalAttrs));
      text.text(defaultText);
      group.draggable(true);
    });
  } catch (error) {
    console.error('Failed to initialize demo:', error);
  }
}

// Start the demo

initDemo();