Atlaslog
Atlaslog / Examples / Leaflet / Export Image
Leaflet · Runnable Example

Export Image

Runnable Leaflet example: Export Image. 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 Leaflet examples
/demos/leaflet-export-image.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/leaflet-export-image.html
<!--
  示例:地图导出图片
  要点:按当前视野拼接 OSM 瓦片到离屏 canvas、叠加 canvas 渲染器的矢量层、toDataURL 下载
  依赖:Leaflet 1.9.4 · jsDelivr CDN
-->
<!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/leaflet@1.9.4/dist/leaflet.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}
  .bar{flex:0 0 auto;padding:8px 12px;background:#f6f7f9;border-bottom:1px solid #e3e6ea;
       display:flex;gap:10px;align-items:center;font-size:12px;color:#58606e}
  .bar button{font:inherit;font-size:12px;padding:4px 10px;border-radius:4px;cursor:pointer;
       border:1px solid #ccd1d9;background:#fff;color:#16181d}
  .bar button:hover{background:#eef0f3}
  .bar b{color:#16181d;font-weight:600}
  #map{flex:1 1 auto;min-height:0;position:relative}
  .shot{position:absolute;left:12px;bottom:12px;z-index:600;background:#fff;padding:6px;
    border-radius:6px;box-shadow:0 4px 14px rgba(0,0,0,.2);font-size:11px;color:#58606e}
  .shot img{display:none;width:168px;height:112px;object-fit:cover;border-radius:3px;
    background:#eef1f5;margin-bottom:4px}
  .shot .ph{width:168px;height:112px;border-radius:3px;background:#eef1f5;
    display:flex;align-items:center;justify-content:center;color:#9aa3b0;
    font-size:11px;margin-bottom:4px}
</style>
</head>
<body>
<div class="bar">
  <button id="shot">导出当前视野</button>
  <span id="status">等待导出</span>
  <span>画布 <b id="size">—</b></span>
</div>
<div id="map">
  <div class="shot" id="shotbox">
    <img id="preview" alt="导出预览">
    <div class="ph" id="ph">点击上方按钮生成预览</div>
    预览 · 实际文件为完整分辨率
  </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
(function () {
  'use strict';

  var map = L.map('map', {
    center: [39.907359, 116.391263],
    zoom: 14,
    zoomSnap: 1
  });

  var OSM = 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Street_Map/MapServer/tile/{z}/{y}/{x}';

  L.tileLayer(OSM, {
    maxZoom: 19,
    /* 打开跨域,图片才能画进 canvas 而不污染 */
    crossOrigin: 'anonymous',
    attribution: 'Tiles © Esri'
  }).addTo(map);

  /* padding 设 0,画布尺寸恰好等于地图视野,导出时无需换算偏移 */
  var renderer = L.canvas({ padding: 0 }).addTo(map);

  L.circle([39.907359, 116.391263], {
    radius: 900,
    renderer: renderer,
    color: '#1f5fd0',
    weight: 2,
    fillColor: '#1f5fd0',
    fillOpacity: 0.12
  }).addTo(map);

  L.polygon([
    [39.915, 116.380], [39.921, 116.402],
    [39.912, 116.412], [39.903, 116.398], [39.905, 116.382]
  ], {
    renderer: renderer,
    color: '#c2410c',
    weight: 3,
    dashArray: '7 5',
    fillColor: '#f97316',
    fillOpacity: 0.10
  }).addTo(map);

  var status = document.getElementById('status');
  var preview = document.getElementById('preview');
  var sizeEl = document.getElementById('size');

  function loadTile(x, y, z, tileSize, origin, ctx) {
    return new Promise(function (resolve) {
      var img = new Image();
      img.crossOrigin = 'anonymous';
      img.onload = function () {
        ctx.drawImage(img, x * tileSize - origin.x, y * tileSize - origin.y, tileSize, tileSize);
        resolve();
      };
      img.onerror = function () { resolve(); };
      img.src = OSM.replace('{z}', z).replace('{x}', x).replace('{y}', y);
    });
  }

  map.on('moveend zoomend', function () {
    var s = map.getSize();
    sizeEl.textContent = s.x + ' × ' + s.y;
  });
  setTimeout(function () {
    var s = map.getSize();
    sizeEl.textContent = s.x + ' × ' + s.y;
  }, 0);

  document.getElementById('shot').addEventListener('click', function () {
    var btn = this;
    btn.disabled = true;
    status.textContent = '拼接瓦片中…';

    var size = map.getSize();
    var z = map.getZoom();
    var tileSize = 256;
    var pb = map.getPixelBounds();

    var out = document.createElement('canvas');
    out.width = size.x;
    out.height = size.y;
    var ctx = out.getContext('2d');
    ctx.fillStyle = '#e6ebf1';
    ctx.fillRect(0, 0, size.x, size.y);

    var min = pb.min.divideBy(tileSize).floor();
    var max = pb.max.divideBy(tileSize).floor();
    var jobs = [];

    for (var x = min.x; x <= max.x; x++) {
      for (var y = min.y; y <= max.y; y++) {
        jobs.push(loadTile(x, y, z, tileSize, pb.min, ctx));
      }
    }

    function annotate(c, size, z) {
      c.fillStyle = 'rgba(255, 255, 255, 0.9)';
      c.fillRect(0, size.y - 34, 236, 34);
      c.fillStyle = '#16181d';
      c.font = '600 13px sans-serif';
      c.fillText('北京核心区 · z' + z, 12, size.y - 19);
      c.fillStyle = '#58606e';
      c.font = '11px sans-serif';
      c.fillText('Tiles © Esri', 12, size.y - 6);
    }

    function savePng(c, okText) {
      var url = c.toDataURL('image/png');
      preview.src = url;
      preview.style.display = 'block';
      document.getElementById('ph').style.display = 'none';

      var a = document.createElement('a');
      a.href = url;
      a.download = 'map-' + z + '-' + Date.now() + '.png';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);

      status.textContent = okText;
    }

    Promise.all(jobs).then(function () {
      /* 矢量层是 canvas 渲染器画在同一视野坐标上的,直接按 0,0 叠上去 */
      var layerCanvas = map.getPane('overlayPane').querySelector('canvas');
      if (layerCanvas) ctx.drawImage(layerCanvas, 0, 0);

      /* 图面注记手动绘制:HTML 标记不在 canvas 里 */
      annotate(ctx, size, z);

      try {
        savePng(out, '已导出 ' + size.x + ' × ' + size.y + ' 像素');
      } catch (err) {
        /* 瓦片服务没开跨域时,退化为只导出矢量图层 */
        var plain = document.createElement('canvas');
        plain.width = size.x;
        plain.height = size.y;
        var pc = plain.getContext('2d');
        pc.fillStyle = '#f4f6f8';
        pc.fillRect(0, 0, size.x, size.y);
        if (layerCanvas) pc.drawImage(layerCanvas, 0, 0);
        annotate(pc, size, z);

        try {
          savePng(plain, '瓦片跨域受限,已导出矢量图层');
        } catch (err2) {
          status.textContent = '导出失败:画布无法读取像素';
        }
      }

      btn.disabled = false;
    });
  });
})();
</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 Leaflet examples

Leaflet · EXAMPLEFeature Edit Leaflet · EXAMPLEFlyline Leaflet · EXAMPLEFullscreen