Atlaslog
Atlaslog / Examples / MapLibre GL JS / Popup Card
MapLibre GL JS · Runnable Example

Popup Card

Runnable MapLibre GL JS example: Popup Card. Open the live demo, read the complete source, and copy the file into your project. No build step or API key.

Open standalone → Chinese notes All MapLibre GL JS examples
/demos/maplibre-popup-card.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/maplibre-popup-card.html
<!--
  示例:弹出卡片
  核心逻辑:点击站点圆点后,用 Popup + setDOMContent 挂一张自绘卡片,offset 避开图标
  依赖:MapLibre GL JS 4 · jsDelivr CDN · demotiles 样式
-->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="robots" content="noindex,follow">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>弹出卡片</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/maplibre-gl@4/dist/maplibre-gl.css">
<style>
  html,body{height:100%;margin:0}
  body{display:flex;flex-direction:column;overflow:hidden;
       font:13px/1.6 -apple-system,"Segoe UI","PingFang SC",sans-serif;color:#16181d}
  .bar{flex:0 0 auto;display:flex;gap:12px;align-items:center;padding:8px 12px;
       background:#f6f7f9;border-bottom:1px solid #e3e6ea;font-size:12px;color:#58606e;flex-wrap:wrap}
  .bar b{color:#16181d;font-weight:600}
  #map{flex:1 1 auto;min-height:0}
  .station-popup .maplibregl-popup-content{
    padding:0;border-radius:8px;box-shadow:0 6px 20px rgba(15,30,55,.22);overflow:hidden;
  }
  .station-popup .maplibregl-popup-tip{border-top-color:#ffffff}
  .card-head{background:#1f5fd0;color:#fff;padding:9px 14px;font-weight:600;font-size:13px}
  .card-body{padding:10px 14px 13px;color:#28323f}
  .card-row{display:flex;justify-content:space-between;gap:18px;font-size:12px;margin-top:4px}
  .card-row span:first-child{color:#6b7785}
  .card-val{font-size:22px;font-weight:700;color:#16181d;line-height:1.3}
  .card-val small{font-size:11px;font-weight:400;color:#6b7785;margin-left:3px}
  .card-bar{height:6px;border-radius:3px;background:#e8edf3;margin-top:8px;overflow:hidden}
  .card-bar i{display:block;height:100%;border-radius:3px;background:#e8590c}
</style>
</head>
<body>
<div class="bar">
  <span>点击圆点查看站点卡片</span>
  <span>站点数 <b>6</b></span>
  <span id="hint">卡片随地图缩放一起移动</span>
</div>
<div id="map"></div>
<script src="https://cdn.jsdelivr.net/npm/maplibre-gl@4/dist/maplibre-gl.js"></script>
<script>
(function () {
  'use strict';

  if (typeof maplibregl === 'undefined') {
    document.body.innerHTML =
      '<div style="padding:24px;color:#b42318">MapLibre 加载失败,请检查能否访问 cdn.jsdelivr.net</div>';
    return;
  }

  var stations = {
    type: 'FeatureCollection',
    features: [
      { name: 'Binjiang', lng: 120.21, lat: 30.21, value: 82, max: 100, time: '08:00' },
      { name: 'Xihu', lng: 120.13, lat: 30.26, value: 46, max: 100, time: '08:00' },
      { name: 'Xiaoshan', lng: 120.26, lat: 30.16, value: 64, max: 100, time: '07:45' },
      { name: 'Yuhang', lng: 120.30, lat: 30.42, value: 91, max: 100, time: '08:10' },
      { name: 'Fuyang', lng: 119.95, lat: 30.05, value: 37, max: 100, time: '07:50' },
      { name: 'Linan', lng: 119.72, lat: 30.23, value: 58, max: 100, time: '08:05' }
    ].map(function (s) {
      return {
        type: 'Feature',
        properties: { name: s.name, value: s.value, max: s.max, time: s.time },
        geometry: { type: 'Point', coordinates: [s.lng, s.lat] }
      };
    })
  };

  var map = new maplibregl.Map({
    container: 'map',
    style: 'https://demotiles.maplibre.org/style.json',
    center: [120.0, 30.22],
    zoom: 9.2,
    attributionControl: { compact: true, customAttribution: 'MapLibre demo tiles' }
  });

  map.addControl(new maplibregl.NavigationControl(), 'top-right');

  function buildCard(props) {
    var wrap = document.createElement('div');

    var head = document.createElement('div');
    head.className = 'card-head';
    head.textContent = props.name + ' 观测站';

    var body = document.createElement('div');
    body.className = 'card-body';

    var val = document.createElement('div');
    val.className = 'card-val';
    val.textContent = props.value;
    var unit = document.createElement('small');
    unit.textContent = '/ ' + props.max;
    val.appendChild(unit);

    var bar = document.createElement('div');
    bar.className = 'card-bar';
    var fill = document.createElement('i');
    fill.style.width = (props.value / props.max * 100) + '%';
    bar.appendChild(fill);

    var row = document.createElement('div');
    row.className = 'card-row';
    row.innerHTML = '<span>更新时间</span><span>' + props.time + '</span>';

    body.appendChild(val);
    body.appendChild(bar);
    body.appendChild(row);

    wrap.appendChild(head);
    wrap.appendChild(body);
    return wrap;
  }

  map.on('load', function () {
    map.addSource('stations', { type: 'geojson', data: stations });

    map.addLayer({
      id: 'station-dot',
      type: 'circle',
      source: 'stations',
      paint: {
        'circle-radius': 7,
        'circle-color': '#e8590c',
        'circle-stroke-color': '#ffffff',
        'circle-stroke-width': 2.5
      }
    });

    var popup = new maplibregl.Popup({
      closeButton: false,
      closeOnClick: true,
      maxWidth: '300px',
      offset: 12,
      className: 'station-popup'
    });

    map.on('click', 'station-dot', function (e) {
      var f = e.features[0];
      popup
        .setLngLat(f.geometry.coordinates)
        .setDOMContent(buildCard(f.properties))
        .addTo(map);
      document.getElementById('hint').textContent =
        f.properties.name + ' · 值 ' + f.properties.value;
    });

    map.on('mouseenter', 'station-dot', function () {
      map.getCanvas().style.cursor = 'pointer';
    });
    map.on('mouseleave', 'station-dot', function () {
      map.getCanvas().style.cursor = '';
    });
  });

  map.on('error', function (e) {
    if (e.error) console.warn('地图错误:' + e.error.message);
  });
})();
</script>
</body>
</html>
Language: the code and this page are in English; the full tutorial and API notes for this example are in Chinese. The demo itself is the authoritative version — it runs exactly as shown.

More MapLibre GL JS examples

MapLibre GL JS · EXAMPLEPopup on Click MapLibre GL JS · EXAMPLEPopup on Hover MapLibre GL JS · EXAMPLERaster Tiles