Generate dynamic map with zoom and pan functionality
by der_robert
HTML
<div id="map-wrapper">
<div id="map-content">
</div>
</div>
CSS
#map-wrapper {
width: 100vw;
height: 100vh;
overflow: hidden;
position: relative;
}
#map-content {
position: absolute;
transform-origin: top left;
}
.map-island, .map-event, .map-ship {
position: absolute;
width: 12px;
height: 12px;
border-radius: 50%;
}
.map-island {
background-color: sandybrown;
}
.map-event.wrack {
background-color: gray;
}
.map-event.storm {
background-color: green;
}
.map-ship {
background-color: white;
}
.map-route {
height: 2px;
background: white;
position: absolute;
transform-origin: 0 0;
opacity: 0.4;
}
.map-route-marker {
width: 16px;
height: 16px;
position: absolute;
pointer-events: none;
}
JavaScript
const wrapper = document.getElementById('map-wrapper');
const content = document.getElementById('map-content');
let scale = 2;
let offsetX = 0;
let offsetY = 0;
const mapData = {
"islands": [
{ "id": 1, "name": "Insel Alpha", "x": 100, "y": 150 },
{ "id": 2, "name": "Insel Beta", "x": 200, "y": 80 },
{ "id": 3, "name": "Insel Gamma", "x": 300, "y": 220 },
{ "id": 4, "name": "Insel Delta", "x": 400, "y": 180 },
{ "id": 5, "name": "Insel Epsilon", "x": 500, "y": 300 },
{ "id": 6, "name": "Insel Zeta", "x": 150, "y": 350 },
{ "id": 7, "name": "Insel Eta", "x": 250, "y": 400 },
{ "id": 8, "name": "Insel Theta", "x": 350, "y": 120 },
{ "id": 9, "name": "Insel Iota", "x": 450, "y": 250 },
{ "id": 10, "name": "Insel Kappa", "x": 550, "y": 100 }
],
"events": [
{ "type": "storm", "x": 130, "y": 330 },
{ "type": "storm", "x": 230, "y": 370 },
{ "type": "storm", "x": 330, "y": 310 },
{ "type": "storm", "x": 430, "y": 290 },
{ "type": "storm", "x": 530, "y": 350 },
{ "type": "wrack", "x": 160, "y": 210 },
{ "type": "wrack", "x": 260, "y": 190 },
{ "type": "wrack", "x": 360, "y": 230 },
{ "type": "wrack", "x": 460, "y": 170 },
{ "type": "wrack", "x": 560, "y": 200 }
],
"ships": [
{
"id": 1,
"from": [100, 100],
"to": [400, 300],
"progress": 0.2
},
{
"id": 2,
"from": [150, 400],
"to": [500, 100],
"progress": 0.75
}
],
"routes": [
{
"from": [100, 100],
"to": [300, 250],
"image": "/assets/icon.png",
"at": [200, 170]
}
]
};
function renderMap(data) {
content.innerHTML = '';
// Wassertextur-Hintergrund
content.style.backgroundImage = 'repeating-radial-gradient(circle at 0 0, #0077aa, #0099cc 20px)';
content.style.backgroundSize = '100px 100px';
// Routen (als Linien mit optionalem Bildpunkt)
data.routes?.forEach(r...