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.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →The capture workflow
- 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.
- Make every source exportable. Configure Leaflet tile loading with
crossOriginbefore tiles are requested, and use a tile provider that sends an appropriateAccess-Control-Allow-Originresponse. Leaflet describes this option as necessary when you need tile pixel data in a canvas (TileLayer API reference). - 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.
- Export the WebGL surface. Call
toBlob()for a downloadable PNG ortoDataURL()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. - 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.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallExporting 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.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsIf 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. |
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.
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.
Quick Recap
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.

