跳到主要内容

Canvas 图形上的手势事件

如何监听 Canvas 图形上的轻扫、双指缩放、旋转和其他多点触控手势事件?

默认情况下,Konva 只支持 touchstarttouchmovetouchend 等基本触摸事件。

必须基于这些触摸事件手动实现手势事件。

如果要了解整个舞台的平移和缩放逻辑,请参阅多点触控缩放舞台示例

不过,我稍微修改了 Hammer.js,使其可以与 Konva 配合使用。

可以在此处查看修改后的 hammer.js 源代码

操作说明:可以在矩形上尝试轻扫、旋转、缩放、拖放和长按等不同手势。在桌面浏览器中,可以按住 Shift 键来模拟触摸事件。

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.jsdelivr.net/gh/hammerjs/touchemulator@eed95bf676877a1394c4538d849802061156681a/touch-emulator.js');
await loadScript('https://konvajs.org/js/hammer-konva.js');

// emulate touches on desktop
TouchEmulator();
Konva.hitOnDragEnabled = true;
Konva.capturePointerEventsEnabled = 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();