Navigate the Map with Game-Like Controls
Use keyboard events to navigate the map like a game: WASD or arrow keys to pan, Q/E to rotate, +/- to zoom.
Controls: W / ↑ = Pan up | S / ↓ = Pan down | A / ← = Pan left | D / → = Pan right | Q = Rotate left | E = Rotate right | + = Zoom in | - = Zoom out
Disable Built-In Keyboard Handler
First, disable the map's default keyboard navigation to avoid conflicts:
javascript
const map = new mapmetricsgl.Map({
container: 'map',
style: '<StyleFile_URL_with_Token>',
keyboard: false, // disable default keyboard handler
});
// Or at runtime
map.keyboard.disable();Track Held Keys
For smooth continuous movement, track which keys are currently held:
javascript
const keys = {};
document.addEventListener('keydown', e => keys[e.key] = true);
document.addEventListener('keyup', e => keys[e.key] = false);Game Loop
Use requestAnimationFrame to act on held keys every frame:
javascript
function gameLoop() {
const speed = 80; // pixels per frame
if (keys['w'] || keys['ArrowUp']) map.panBy([0, -speed], { animate: false });
if (keys['s'] || keys['ArrowDown']) map.panBy([0, speed], { animate: false });
if (keys['a'] || keys['ArrowLeft']) map.panBy([-speed, 0], { animate: false });
if (keys['d'] || keys['ArrowRight']) map.panBy([ speed, 0], { animate: false });
if (keys['q']) map.setBearing(map.getBearing() - 2);
if (keys['e']) map.setBearing(map.getBearing() + 2);
if (keys['+']) map.setZoom(map.getZoom() + 0.05);
if (keys['-']) map.setZoom(map.getZoom() - 0.05);
requestAnimationFrame(gameLoop);
}
map.on('load', () => gameLoop());Key Camera Methods
javascript
map.panBy([dx, dy], options) // pan by pixel offset
map.getBearing() // current bearing (degrees)
map.setBearing(bearing) // set bearing instantly
map.getZoom() // current zoom level
map.setZoom(zoom) // set zoom instantly
map.easeTo({ bearing, zoom, ... }) // smooth transitionComplete Example
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<link href="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.css" rel="stylesheet" />
<script src="https://cdn.mapmetrics-atlas.net/versions/latest/mapmetrics-gl.js"></script>
<style>#map { height: 500px; width: 100%; }</style>
</head>
<body>
<div id="map"></div>
<p>WASD / Arrows = pan Q/E = rotate +/- = zoom</p>
<script>
const map = new mapmetricsgl.Map({
container: 'map',
style: '<StyleFile_URL_with_Token>',
center: [10, 50],
zoom: 4,
keyboard: false
});
const keys = {};
document.addEventListener('keydown', e => { keys[e.key] = true; });
document.addEventListener('keyup', e => { keys[e.key] = false; });
map.on('load', () => {
(function loop() {
if (keys['w'] || keys['ArrowUp']) map.panBy([0, -80], { animate: false });
if (keys['s'] || keys['ArrowDown']) map.panBy([0, 80], { animate: false });
if (keys['a'] || keys['ArrowLeft']) map.panBy([-80, 0], { animate: false });
if (keys['d'] || keys['ArrowRight']) map.panBy([ 80, 0], { animate: false });
if (keys['q']) map.setBearing(map.getBearing() - 2);
if (keys['e']) map.setBearing(map.getBearing() + 2);
if (keys['+'] || keys['=']) map.setZoom(map.getZoom() + 0.05);
if (keys['-']) map.setZoom(map.getZoom() - 0.05);
requestAnimationFrame(loop);
})();
});
</script>
</body>
</html>jsx
import React, { useEffect, useRef } from 'react';
import mapmetricsgl from '@mapmetrics/mapmetrics-gl';
import '@mapmetrics/mapmetrics-gl/dist/mapmetrics-gl.css';
const GameControlsNavigation = () => {
const mapContainer = useRef(null);
const map = useRef(null);
useEffect(() => {
if (map.current) return;
map.current = new mapmetricsgl.Map({
container: mapContainer.current,
style: '<StyleFile_URL_with_Token>',
center: [10, 50],
zoom: 4,
keyboard: false
});
const keys = {};
const onDown = e => { keys[e.key] = true; };
const onUp = e => { keys[e.key] = false; };
document.addEventListener('keydown', onDown);
document.addEventListener('keyup', onUp);
let animId;
map.current.on('load', () => {
const loop = () => {
const m = map.current;
if (!m) return;
if (keys['w'] || keys['ArrowUp']) m.panBy([0, -80], { animate: false });
if (keys['s'] || keys['ArrowDown']) m.panBy([0, 80], { animate: false });
if (keys['a'] || keys['ArrowLeft']) m.panBy([-80, 0], { animate: false });
if (keys['d'] || keys['ArrowRight']) m.panBy([ 80, 0], { animate: false });
if (keys['q']) m.setBearing(m.getBearing() - 2);
if (keys['e']) m.setBearing(m.getBearing() + 2);
if (keys['+'] || keys['=']) m.setZoom(m.getZoom() + 0.05);
if (keys['-']) m.setZoom(m.getZoom() - 0.05);
animId = requestAnimationFrame(loop);
};
loop();
});
return () => {
document.removeEventListener('keydown', onDown);
document.removeEventListener('keyup', onUp);
if (animId) cancelAnimationFrame(animId);
map.current?.remove(); map.current = null;
};
}, []);
return (
<div>
<div ref={mapContainer} style={{ height: '500px', width: '100%' }} />
<p style={{ fontSize: 13, color: '#6b7280' }}>
WASD / Arrows = pan | Q/E = rotate | +/- = zoom
</p>
</div>
);
};
export default GameControlsNavigation;For more information, visit the MapMetrics GitHub repository.