Atlaslog
Atlaslog / Examples / MapLibre GL JS / Offline Cache
MapLibre GL JS · Runnable Example

Offline Cache

Runnable MapLibre GL JS example: Offline Cache. 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-offline-cache.htmlLIVE
single HTML file · CDN dependencies onlyinterface may contain Chinese labels

Source

demos/maplibre-offline-cache.html
<!--
  示例:离线缓存
  核心逻辑:addProtocol 注册 offline 协议,读 Cache API;未命中则回源并写入缓存
  依赖:MapLibre GL JS 4 · jsDelivr CDN · demotiles 样式 · CARTO 栅格瓦片 · Cache API
-->
<!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:10px;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}
  .btn{border:1px solid #cdd3db;background:#fff;border-radius:4px;padding:3px 9px;
       font:12px/1.5 inherit;color:#16181d;cursor:pointer}
  .btn[disabled]{opacity:.5;cursor:default}
  #map{flex:1 1 auto;min-height:0}
</style>
</head>
<body>
<div class="bar">
  <button class="btn" id="warm">缓存当前视野</button>
  <button class="btn" id="clear">清空缓存</button>
  <span>命中 <b id="hit">0</b></span>
  <span>回源 <b id="net">0</b></span>
  <span>条目 <b id="cnt">0</b></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 CACHE = 'maplibre-offline-v1';
  var REMOTE = 'https://basemaps.cartocdn.com/light_all/';
  var hits = 0, nets = 0;

  function show(id, v) { document.getElementById(id).textContent = v; }

  function updateCount() {
    if (!window.caches) return;
    caches.open(CACHE).then(function (c) {
      return c.keys();
    }).then(function (keys) {
      show('cnt', keys.length);
    });
  }

  function openCache() {
    return window.caches ? caches.open(CACHE) : Promise.resolve(null);
  }

  /* 自定义协议:offline://{z}/{x}/{y}.png */
  maplibregl.addProtocol('offline', function (params) {
    var path = params.url.replace('offline://', '');
    var url = REMOTE + path;

    return openCache().then(function (cache) {
      if (!cache) return fetchTile(url);
      return cache.match(url).then(function (hit) {
        if (hit) {
          hits++;
          show('hit', hits);
          return hit.arrayBuffer().then(function (buf) { return { data: buf }; });
        }
        return fetchTile(url, cache);
      });
    });
  });

  function fetchTile(url, cache) {
    nets++;
    show('net', nets);
    return fetch(url, { mode: 'cors' }).then(function (res) {
      if (!res.ok) throw new Error('HTTP ' + res.status);
      var copy = res.clone();
      if (cache) cache.put(url, copy).then(updateCount);
      return res.arrayBuffer().then(function (buf) { return { data: buf }; });
    });
  }

  var map = new maplibregl.Map({
    container: 'map',
    style: 'https://demotiles.maplibre.org/style.json',
    center: [104.2, 35.5],
    zoom: 3.4,
    attributionControl: { compact: true, customAttribution: 'MapLibre demo tiles · © CARTO' }
  });

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

  map.on('load', function () {
    map.addSource('cached', {
      type: 'raster',
      tiles: ['offline://{z}/{x}/{y}.png'],
      tileSize: 256,
      maxzoom: 19
    });

    map.addLayer({
      id: 'cached',
      type: 'raster',
      source: 'cached',
      paint: { 'raster-opacity': 1, 'raster-fade-duration': 0 }
    });

    if (!window.caches) {
      show('cnt', '不可用');
    }
  });

  function tileXY(lng, lat, z) {
    var n = Math.pow(2, z);
    var x = Math.floor((lng + 180) / 360 * n);
    var rad = lat * Math.PI / 180;
    var y = Math.floor((1 - Math.log(Math.tan(rad) + 1 / Math.cos(rad)) / Math.PI) / 2 * n);
    return [x, y];
  }

  document.getElementById('warm').addEventListener('click', function () {
    if (!window.caches) return;
    var btn = this;
    var z = Math.max(0, Math.min(19, Math.round(map.getZoom())));
    var c = map.getCenter();
    var t = tileXY(c.lng, c.lat, z);
    var jobs = [];

    for (var dx = -1; dx <= 1; dx++) {
      for (var dy = -1; dy <= 1; dy++) {
        var x = t[0] + dx, y = t[1] + dy;
        if (x < 0 || y < 0 || x >= Math.pow(2, z) || y >= Math.pow(2, z)) continue;
        jobs.push(fetchTile(REMOTE + z + '/' + x + '/' + y + '.png'));
      }
    }

    btn.textContent = '缓存中…';
    btn.disabled = true;

    Promise.all(jobs).then(function () {
      updateCount();
      btn.textContent = '缓存当前视野';
      btn.disabled = false;
    }).catch(function () {
      btn.textContent = '缓存失败';
      btn.disabled = false;
    });
  });

  document.getElementById('clear').addEventListener('click', function () {
    if (!window.caches) return;
    caches.delete(CACHE).then(function () {
      hits = 0;
      nets = 0;
      show('hit', 0);
      show('net', 0);
      updateCount();
      document.getElementById('warm').textContent = '缓存当前视野';
      document.getElementById('warm').disabled = false;
    });
  });

  map.on('error', function (e) {
    if (e.error) console.warn('地图错误:' + e.error.message);
  });

  updateCount();
})();
</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 · EXAMPLEPadding Offset MapLibre GL JS · EXAMPLEPath Animation MapLibre GL JS · EXAMPLEPerformance