Skip to main content

Map Gestures

Last updated: April 29, 2026 | 6 minutes read

This example demonstrates how to detect and handle various touch gestures on the map, including pan, pinch, rotate, swipe, and more.

Live Demo​

Overview​

The example highlights the following gesture detection:

  • Touch and double touch
  • Pan and move
  • Pinch to zoom
  • Rotation
  • Long press
  • Swipe
  • Shove (tilt)
  • Complex multi-touch gestures

Code Implementation​

Register Gesture Callbacks​

import {
GemKit,
GemMap,
PositionService,
} from '@magiclane/maps-sdk';
import { GEMKIT_TOKEN } from './token';

// Icons (Material Design SVGs)
const ICONS = {
touch: `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><path d="M9 11.24V7.5C9 6.12 10.12 5 11.5 5S14 6.12 14 7.5v3.74c1.21-.81 2-2.18 2-3.74C16 5.01 13.99 3 11.5 3S7 5.01 7 7.5c0 1.56.79 2.93 2 3.74zm9.84 4.63l-4.54-2.26c-.17-.07-.35-.11-.54-.11H13v-6c0-.83-.67-1.5-1.5-1.5S10 6.67 10 7.5v10.74l-3.43-.72c-.08-.01-.15-.03-.24-.03-.31 0-.59.13-.79.33l-.79.8 4.94 4.94c.27.27.65.44 1.06.44h6.79c.75 0 1.33-.55 1.44-1.28l.75-5.27c.01-.07.02-.14.02-.2 0-.62-.38-1.16-.91-1.38z"/></svg>`,
rotate: `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><path d="M15.55 5.55L11 1v3.07C7.06 4.56 4 7.92 4 12s3.05 7.44 7 7.93v-2.02c-2.84-.48-5-2.94-5-5.91s2.16-5.43 5-5.91V10l4.55-4.45zM19.93 11c-.17-1.39-.72-2.73-1.62-3.89l-1.42 1.42c.54.75.88 1.6 1.02 2.47h2.02zM13 17.9v2.02c1.39-.17 2.74-.71 3.9-1.61l-1.44-1.44c-.75.54-1.59.89-2.46 1.03zm3.89-2.42l1.42 1.41c.9-1.16 1.45-2.5 1.62-3.89h-2.02c-.14.87-.48 1.72-1.02 2.48z"/></svg>`,
pan: `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><path d="M23 5.5V20c0 2.2-1.8 4-4 4h-7.3c-1.08 0-2.1-.43-2.85-1.19L1 14.83s1.26-1.23 1.3-1.25c.22-.19.49-.29.79-.29.22 0 .42.06.6.16.04.01 4.31 2.46 4.31 2.46V4c0-.83.67-1.5 1.5-1.5S11 3.17 11 4v7h1V1.5c0-.83.67-1.5 1.5-1.5S15 .67 15 1.5V11h1V2.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5V11h1V5.5c0-.83.67-1.5 1.5-1.5s1.5.67 1.5 1.5z"/></svg>`,
longPress: `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>`,
pinch: `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><path d="M6 2.5V1h5v5H9.5V3.56L3.56 9.5H6V11H1V6h1.5v2.44L8.44 2.5H6zm12 0h-2.44l6.94 6.94V6H24v5h-5V9.5h2.44L14.5 2.56V5H13V0h5v1.5h-2zM6 21.5h2.44L1.5 14.56V17H0v-5h5v1.5H2.56L9.5 20.44V18H11v5H6v-1.5zm12 0V23h-5v-5h1.5v2.44l6.94-6.94H18V12h5v5h-1.5v-2.44L14.56 21.5H17z"/></svg>`,
swipe: `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><path d="M12 4l-1.41 1.41L16.17 11H4v2h12.17l-5.58 5.59L12 20l8-8z"/></svg>`,
shove: `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><path d="M16 6l2.29 2.29-4.88 4.88-4-4L2 16.59 3.41 18l6-6 4 4 6.3-6.29L22 12V6z"/></svg>`,
double: `<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><circle cx="12" cy="12" r="5"/></svg>`
};

let map: GemMap | null = null;
let gesturePanel: HTMLDivElement | null = null;
let hideTimeout: ReturnType<typeof setTimeout> | null = null;

// Gesture to icon mapping
function getGestureIcon(gesture: string): string {
if (gesture.includes('Rotate')) return ICONS.rotate;
if (gesture.includes('Touch Pinch')) return ICONS.pinch;
if (gesture.includes('Double Touch')) return ICONS.double;
if (gesture.includes('Two Touches')) return ICONS.double;
if (gesture.includes('Touch')) return ICONS.touch;
if (gesture.includes('Pan')) return ICONS.pan;
if (gesture.includes('Long Press')) return ICONS.longPress;
if (gesture.includes('Pinch Swipe')) return ICONS.pinch;
if (gesture.includes('Pinch')) return ICONS.pinch;
if (gesture.includes('Shove')) return ICONS.shove;
if (gesture.includes('Swipe')) return ICONS.swipe;
return ICONS.touch;
}

// Helper to show gesture panel with modern design
function showGesturePanel(gesture: string) {
if (!gesturePanel) {
gesturePanel = document.createElement('div');
gesturePanel.style.cssText = `
position: fixed;
bottom: 30px;
left: 50%;
transform: translateX(-50%) translateY(100px);
padding: 16px 28px;
background: rgba(33, 33, 33, 0.95);
color: #fff;
border-radius: 50px;
display: flex;
align-items: center;
gap: 12px;
font-size: 16px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
font-weight: 600;
box-shadow: 0 8px 24px rgba(0,0,0,0.25);
backdrop-filter: blur(8px);
z-index: 2000;
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease;
opacity: 0;
letter-spacing: 0.3px;
`;
document.body.appendChild(gesturePanel);
}

// Clear any existing hide timeout
if (hideTimeout) {
clearTimeout(hideTimeout);
}

const icon = getGestureIcon(gesture);
gesturePanel.innerHTML = `
<div style="display: flex; align-items: center; color: #fff;">${icon}</div>
<span>${gesture}</span>
`;

// Animate in
requestAnimationFrame(() => {
if (gesturePanel) {
gesturePanel.style.transform = 'translateX(-50%) translateY(0)';
gesturePanel.style.opacity = '1';
}
});

// Auto-hide after 2 seconds
hideTimeout = setTimeout(() => {
if (gesturePanel) {
gesturePanel.style.transform = 'translateX(-50%) translateY(100px)';
gesturePanel.style.opacity = '0';
}
}, 2000);
}

// Map created callback
function onMapCreated(gemMap: GemMap) {
map = gemMap;

// Register gesture callbacks with descriptive labels
map.registerMapAngleUpdateCallback((angle: number) => {
showGesturePanel('Rotate Map');
console.log('Gesture: onMapAngleUpdate', angle);
});

map.registerTouchCallback((point: any) => {
showGesturePanel('Touch');
console.log('Gesture: onTouch', point);
});

map.registerMoveCallback((point1: any, point2: any) => {
showGesturePanel('Pan Map');
console.log(`Gesture: onMove from (${point1.x} ${point1.y}) to (${point2.x} ${point2.y})`);
});

map.registerLongPressCallback((point: any) => {
showGesturePanel('Long Press');
console.log('Gesture: onLongPress', point);
});

map.registerDoubleTouchCallback((point: any) => {
showGesturePanel('Double Touch');
console.log('Gesture: onDoubleTouch', point);
});

map.registerPinchCallback((p1: any, p2: any, p3: any, p4: any, p5: any) => {
showGesturePanel('Pinch to Zoom');
console.log(`Gesture: onPinch from (${p1.x} ${p1.y}) to (${p2.x} ${p2.y})`);
});

map.registerShoveCallback((degrees: number, p1: any, p2: any, p3: any) => {
showGesturePanel('Shove (Tilt)');
console.log(`Gesture: onShove with ${degrees} angle from (${p1.x} ${p1.y}) to (${p2.x} ${p2.y})`);
});

map.registerSwipeCallback((distX: number, distY: number, speedMMPerSec: number) => {
showGesturePanel('Swipe');
console.log(`Gesture: onSwipe with ${distX} distance in X and ${distY} distance in Y at ${speedMMPerSec} mm/s`);
});

map.registerPinchSwipeCallback((point: any, zoomSpeed: number, rotateSpeed: number) => {
showGesturePanel('Pinch & Rotate');
console.log(`Gesture: onPinchSwipe with zoom speed ${zoomSpeed} and rotate speed ${rotateSpeed}`);
});

map.registerTwoTouchesCallback((point: any) => {
showGesturePanel('Two Touches');
console.log('Gesture: onTwoTouches', point);
});

map.registerTouchPinchCallback((p1: any, p2: any, p3: any, p4: any) => {
showGesturePanel('Touch Pinch');
console.log(`Gesture: onTouchPinch from (${p1.x} ${p1.y}) to (${p2.x} ${p2.y})`);
});
}

SDK Initialization and Map Creation​

import {
GemKit,
GemMap,
PositionService,
} from '@magiclane/maps-sdk';
import { GEMKIT_TOKEN } from './token';

// Main entry
window.addEventListener('DOMContentLoaded', async () => {
const gemKit = await GemKit.initialize(GEMKIT_TOKEN);
await PositionService.instance;

const container = document.getElementById('map-container');
if (!container) throw new Error('Map container not found');

const viewId = 4;
const wrapper = gemKit.createView(viewId, (gemMap: GemMap) => {
onMapCreated(gemMap);
});
if (wrapper) container.appendChild(wrapper);
});

Supported Gestures​

  • Touch: Single tap on the map
  • Double Touch: Quick double tap
  • Long Press: Tap and hold
  • Pan/Move: Drag to move the map
  • Pinch: Two-finger pinch to zoom
  • Rotate: Two-finger rotation
  • Swipe: Quick swipe gesture
  • Shove: Two-finger vertical drag to tilt
  • Pinch Swipe: Combined zoom and rotate
  • Two Touches: Two-finger touch detection

Key Features​

  • Real-time Detection: All gestures are detected in real-time
  • Console Logging: Gesture events are logged with details
  • Visual Feedback: Bottom panel shows the current gesture
  • Multi-touch Support: Complex gestures with multiple touch points

Next Steps​