Skip to content

Add a canvas source

Replicates the MapLibre example Add a canvas source. A canvas source has no YAML form — its content is drawn by page JS every frame, so a declarative description would describe nothing. The YAML holds the map; the page animates bouncing circles on a hidden <canvas> and, once mapReady() resolves, adds a canvas source pinned to four corners plus a raster layer to draw it.

Circles bounce inside a canvas draped over Myanmar
add-a-canvas-source.yaml
# yaml-language-server: $schema=https://docs.maplibre-yaml.org/schema/latest/map.schema.json
# Gallery twin of https://maplibre.org/maplibre-gl-js/docs/examples/add-a-canvas-source/
# The YAML half: the map and its camera limits. A `canvas` source has no
# YAML form — its content is drawn by page JS every frame anyway — so the
# source and its raster layer are added in add-a-canvas-source.js.
type: map
id: add-a-canvas-source
config:
center: [95.899147, 18.088694]
zoom: 5
minZoom: 4
mapStyle: "https://demotiles.maplibre.org/style.json"

This markup goes inside <ml-map>; children with a slot ride its corner system.

add-a-canvas-source.html
<!-- Not slotted: the drawing surface stays hidden inside <ml-map>. -->
<canvas data-canvas-source width="400" height="400" hidden></canvas>
add-a-canvas-source.js
// The JavaScript half: a `canvas` source, added through mapReady(). The page
// animates bouncing circles on a hidden <canvas>; MapLibre re-reads it
// every frame (animate: true) and drapes it over four corner coordinates.
const mapEl = document.querySelector("ml-map");
const canvas = document.querySelector("[data-canvas-source]");
const ctx = canvas.getContext("2d");
const size = canvas.width;
const radius = 20;
const circles = Array.from({ length: 5 }, () => ({
x: Math.random() * (size - radius * 2) + radius,
y: Math.random() * (size - radius * 2) + radius,
dx: (Math.random() - 0.5) * 4,
dy: (Math.random() - 0.5) * 4,
color: `hsl(${Math.floor(Math.random() * 360)} 80% 45%)`,
}));
function animate() {
ctx.clearRect(0, 0, size, size);
for (const c of circles) {
if (c.x + radius > size || c.x - radius < 0) c.dx = -c.dx;
if (c.y + radius > size || c.y - radius < 0) c.dy = -c.dy;
c.x += c.dx;
c.y += c.dy;
ctx.beginPath();
ctx.arc(c.x, c.y, radius, 0, Math.PI * 2);
ctx.lineWidth = 3;
ctx.strokeStyle = c.color;
ctx.stroke();
}
requestAnimationFrame(animate);
}
animate();
mapEl.mapReady().then((map) => {
map.addSource("canvas-source", {
type: "canvas",
canvas,
coordinates: [
[91.4461, 21.5006],
[100.3541, 21.5006],
[100.3541, 13.9706],
[91.4461, 13.9706],
],
animate: true, // static canvases should say false: it saves a re-upload per frame
});
map.addLayer({ id: "canvas-layer", type: "raster", source: "canvas-source" });
});
  • animate: true makes MapLibre re-read the canvas every frame; set it to false for a static canvas to save the upload.
  • The <canvas> sits inside <ml-map> without a slot, so the element leaves it where it is — hidden — while the map reads it.
  • Sources added by page JS live outside the document, so mlym emit won’t see them.