Files

2.2 KiB

Leaflet Map Integration for PWA Draft Games

When to add a map view

When a fantasy game's draft room needs to show geographic regions, a map view is more intuitive than a card grid. Use Leaflet.js + OpenStreetMap tiles for free, no-API-key map rendering.

CDN

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>

Implementation pattern

  1. Map container: Single <div id="leafletMap"> in the HTML
  2. On view switch: Destroy old instance (window._leafletMap.remove()), create new L.map() centered on [20, 0] with zoom 2, minZoom 2, maxZoom 5
  3. Tiles: L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png')
  4. Markers: L.circleMarker([lat, lng], {radius, color, fillColor, fillOpacity}) for each region
  5. Popups: marker.bindPopup(content) with region stats, name, and action button
  6. Color coding: Available=teal/#0ea5e9, Your pick=amber/#f59e0b, Taken=red/#dc2626, Waiting=gray
  7. Toggle view: Map | List toggle button that calls renderMapView() or shows card grid

Pitfalls

  • Destroy before recreating: Always remove the previous Leaflet instance (window._leafletMap.remove()) before initializing a new one. Multiple instances on the same div cause visual glitches.
  • Coordinate format: Leaflet uses [lat, lng] in that order (not [lng, lat] like GeoJSON).
  • Dark theme: Override leaflet-container, .leaflet-popup-content-wrapper, and .leaflet-control-zoom CSS to match dark theme.
  • Popup content: Leaflet's bindPopup() accepts raw HTML — use it for region stats, action buttons, etc. Don't use custom overlay modals for map popups.
  • Mobile responsiveness: Leaflet handles viewport resizing automatically, but set height: 500px on the container and wrap in a responsive parent.
  • Prevent scrolling off-world: Without bounds locking, users can pan left/right until all markers disappear. Fix with:
    const map = L.map('leafletMap', {
      maxBounds: [[-60, -180], [80, 180]],
      maxBoundsViscosity: 0.8,  // 0 = hard wall, 1 = bounce
    });
    
    Viscosity of 0.8 lets it feel natural (not a hard wall) while keeping markers in view.