Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Capture a Leaflet WebGL heatmap from the canvas created by its plugin, not from Leaflet’s ordinary SVG or Canvas export alone. Wait until the heatmap has rendered, export that WebGL canvas with toBlob() or toDataURL(), and composite it with a CORS-approved basemap on a destination canvas when you need one flattened image. If any tile or image was loaded without CORS approval, the browser will taint the canvas and block pixel export.

Why a Leaflet WebGL heatmap needs a separate capture path

Leaflet renders vector paths as SVG by default. Setting preferCanvas: true or supplying a Canvas renderer moves those paths to a Leaflet-managed canvas, but it does not automatically absorb a heatmap rendered by a separate WebGL plugin. The plugin may create its own <canvas>, WebGL context, and render loop.

The Leaflet plugin directory lists a WebGL heatmap entry compatible with Leaflet 1, not Leaflet 2, and does not document a universal image-export method. The exact canvas handle and render-completion event therefore depend on the plugin and version you installed. Check the plugin source and documentation before relying on property names. The listing is at Leaflet’s WebGL heatmap plugin page; one implementation is leaflet-webgl-heatmap.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The capture workflow

  1. Identify the heatmap canvas. Inspect the map container in developer tools and find the canvas owned by the WebGL layer. Prefer a documented plugin property or method over a selector that may change.
  2. Make every source exportable. Configure Leaflet tile loading with crossOrigin before tiles are requested, and use a tile provider that sends an appropriate Access-Control-Allow-Origin response. Leaflet describes this option as necessary when you need tile pixel data in a canvas (TileLayer API reference).
  3. Wait for a finished frame. Use the plugin’s documented update or render event if it has one. Otherwise wait for the data update, tile load, and at least one animation frame; a timeout alone is less reliable than an event.
  4. Export the WebGL surface. Call toBlob() for a downloadable PNG or toDataURL() for a data URL. If the plugin’s WebGL context was created without a readable drawing buffer, the result can be blank or stale; the plugin must expose a suitable context setting.
  5. Composite when necessary. Draw the basemap, heatmap, vector layers, and any other canvases onto a destination canvas in display order. Match width, height, device-pixel ratio, map bounds, and layer offsets exactly.

A complete browser example

The following example shows the export mechanics with a plugin canvas supplied through a placeholder function. Replace getHeatmapCanvas() and waitForHeatmapFrame() with the API documented by your installed WebGL heatmap plugin. The rest is standard browser code.

#1 Best Overall
const map = L.map('map', {
  preferCanvas: true
}).setView([40.72, -74.0], 11);

const tiles = L.tileLayer(
  'https://your-cors-enabled-provider/{z}/{x}/{y}.png',
  {
    crossOrigin: 'anonymous',
    attribution: 'Your tile provider attribution'
  }
).addTo(map);

// Add your WebGL heatmap plugin layer here.
const heatmap = createYourWebGLHeatmapLayer({
  // plugin-specific options
}).addTo(map);

async function waitForMapAndHeatmap() {
  await new Promise(resolve => {
    if (map.loaded()) resolve();
    else map.once('load', resolve);
  });

  // Prefer the plugin's documented event, for example:
  // await new Promise(resolve => heatmap.once('render', resolve));
  await new Promise(requestAnimationFrame);
  await new Promise(requestAnimationFrame);
}

function getHeatmapCanvas() {
  // Replace this with the documented canvas accessor for your plugin.
  // Do not assume map.getCanvas() exists in Leaflet.
  return heatmap.getCanvas();
}

function canvasBlob(canvas, type = 'image/png') {
  return new Promise((resolve, reject) => {
    canvas.toBlob(blob => blob ? resolve(blob) : reject(new Error('Canvas export returned null')), type);
  });
}

async function captureHeatmap() {
  await waitForMapAndHeatmap();
  const heatCanvas = getHeatmapCanvas();
  if (!heatCanvas) throw new Error('Heatmap canvas was not found');

  // Export the heatmap alone.
  const heatBlob = await canvasBlob(heatCanvas);
  const heatUrl = URL.createObjectURL(heatBlob);
  const link = document.createElement('a');
  link.href = heatUrl;
  link.download = 'heatmap.png';
  link.click();
  URL.revokeObjectURL(heatUrl);

  // Composite a flattened image. The map container canvases are ordered
  // according to DOM stacking; adapt this to your layer structure.
  const output = document.createElement('canvas');
  const rect = map.getContainer().getBoundingClientRect();
  const scale = window.devicePixelRatio || 1;
  output.width = Math.round(rect.width * scale);
  output.height = Math.round(rect.height * scale);
  const ctx = output.getContext('2d');
  ctx.scale(scale, scale);

  for (const canvas of map.getContainer().querySelectorAll('canvas')) {
    if (canvas === heatCanvas) continue;
    const r = canvas.getBoundingClientRect();
    ctx.drawImage(canvas, r.left - rect.left, r.top - rect.top,
      r.width, r.height);
  }
  const hr = heatCanvas.getBoundingClientRect();
  ctx.drawImage(heatCanvas, hr.left - rect.left, hr.top - rect.top,
    hr.width, hr.height);

  const finalBlob = await canvasBlob(output);
  const finalUrl = URL.createObjectURL(finalBlob);
  const finalLink = document.createElement('a');
  finalLink.href = finalUrl;
  finalLink.download = 'leaflet-heatmap-composite.png';
  finalLink.click();
  URL.revokeObjectURL(finalUrl);
}

document.querySelector('#capture').addEventListener('click', captureHeatmap);

For a production implementation, replace the placeholder functions with the plugin’s documented canvas accessor and render signal. If the plugin redraws continuously, capture immediately after the frame that contains the final data rather than after an arbitrary long delay.

Cross-origin images and the canvas security rule

Canvas pixel access is governed by the browser’s same-origin policy. MDN states: “As soon as you draw into a canvas any data that was loaded from another origin without CORS approval, the canvas becomes tainted.” A tainted canvas causes toDataURL() and toBlob() to throw a SecurityError or fail to return usable pixels (MDN’s CORS canvas guidance).

Set crossOrigin: 'anonymous' on the Leaflet TileLayer before it starts loading, but remember that the server must also send a permitting CORS header. Check the actual tile response in the Network panel. A provider can support tiles visually while still forbidding canvas readback. Verify its token rules, allowed origins, attribution requirements, and permitted use.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Exporting only the heatmap versus the whole map

Heatmap-only image

Exporting the plugin canvas directly is simplest and preserves the heatmap’s own transparency. It is useful when another system already supplies the basemap or when you need a transparent overlay.

Flattened basemap and heatmap

Composite canvases onto a destination canvas in the same visual order as the map. Draw the basemap first, then Leaflet Canvas vectors, then the WebGL heatmap and any overlays. Use bounding rectangles rather than assuming every canvas starts at coordinate (0, 0), especially when controls or panes alter positioning.

Controls, legends, and markers

HTML controls, legends, popups, and divIcon markers are DOM elements, not canvas pixels. leaflet-image explicitly omits HTML-based map content. Render those elements separately, draw an equivalent legend yourself, or use a browser screenshot when DOM fidelity is more important than a clean data image.

Using leaflet-image

leaflet-image can export Leaflet layers when tile and marker sources are CORS-capable and vectors use Canvas in Leaflet 1.x. Configure preferCanvas: true or L.canvas() for those vectors. Its documentation does not establish support for an independent WebGL heatmap canvas, so treat it as a conditional option rather than a guaranteed solution. Read its constraints at the npm package page and repository.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If the heatmap is absent while ordinary Leaflet layers appear, that is expected when the exporter only visits Leaflet-managed surfaces. Capture the plugin canvas yourself and composite it, or choose a DOM screenshot method if controls must be included.

WebGL-specific blank or stale captures

Some WebGL engines do not preserve the drawing buffer after presenting a frame. Mapbox GL JS documents a preserveDrawingBuffer option; when true, its canvas can be exported with map.getCanvas().toDataURL() (Mapbox GL JS Map API). This is a Mapbox example, not a Leaflet heatmap guarantee. Do not assume a Leaflet plugin accepts that option. Check the plugin’s context creation code or documented option, and capture at the correct render event.

Troubleshooting checklist

Symptom Likely cause Fix
SecurityError on export A tile, marker, or image tainted the canvas. Enable crossOrigin before loading and confirm the provider’s CORS response for every image source.
Basemap exports but heatmap is missing The heatmap uses a separate WebGL canvas. Get the plugin canvas directly and composite it after the basemap.
Blank or old heatmap Capture occurred before rendering, or the drawing buffer is not preserved. Use the plugin’s render/update event, wait for a frame, and verify its WebGL context settings.
Controls or legend absent They are HTML outside canvas. Draw equivalents on the output canvas or use a browser screenshot.
Tiles missing in the export Tile requests failed, were blocked, or were not loaded yet. Inspect Network requests, wait for tile load, and verify URL, token, attribution, and usage policy.
Heatmap is offset or clipped Canvas CSS size, device-pixel ratio, or pane position differs from output coordinates. Use each canvas’s bounding rectangle and set destination dimensions from the map container.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Attribution and provider obligations

Keep required attribution in the exported image or in the surrounding published context. Leaflet’s Quick Start guide says attribution is obligatory when using OpenStreetMap and points production users to its tile usage policy. Other providers can impose different attribution, token, caching, and rate conditions. A technically successful export is not permission to republish tiles without meeting those terms.

Or skip the browser setup

If you need a website screenshot rather than a programmatic heatmap layer export, ScreenshotNeo provides a single-request screenshot API. It can accept cookie and consent banners before capture and remove more than 60 known consent platforms, newsletter popups, and chat widgets. Only clean shots are billed; bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

For a rendered map page, the call is:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/map -o map.webp

See the ScreenshotNeo API documentation for options such as viewport and device presets, full-page capture, custom JavaScript and CSS, waits, selectors, headers, cookies, geolocation, PDF output, caching, signed links, asynchronous jobs, and bulk capture. You get 1,000 screenshots each month free with no card; paid plans start at $5 for 3,000. Create a free ScreenshotNeo account.

JavaScript and alternative client calls

Browser-side fetch

const url = new URL('https://api.screenshotneo.com/v1/shot');
url.search = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/map'
});
const response = await fetch(url);
if (!response.ok) throw new Error(`Screenshot failed: ${response.status}`);
const image = await response.blob();

Python

import requests
r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/map"},
    timeout=90,
)
r.raise_for_status()
open("map.webp", "wb").write(r.content)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com/map' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('map.webp', Buffer.from(await res.arrayBuffer()));

Frequently Asked Questions

Can I export a Leaflet WebGL heatmap with only leaflet-image?

Not reliably. leaflet-image’s documented path targets Leaflet-managed Canvas layers and does not establish support for an independent WebGL heatmap canvas; capture and composite the plugin canvas instead.

Why does setting preferCanvas to true not fix the missing heatmap?

That option changes Leaflet vector rendering. A separately created WebGL canvas remains a distinct surface until you explicitly export or composite it.

Should I use toDataURL() or toBlob()?

Both can export readable pixels. toBlob() avoids creating a large in-memory data URL and is generally convenient for downloads; either fails when the canvas is tainted or the WebGL buffer is unavailable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.