Interactive maps,
without the weight.

CargoCanvas is a JavaScript library for building fast, mobile-friendly interactive maps. No dependencies, a class-based ES module API, and about 40 kB of gzipped JavaScript.

BSD 2-Clause~40 kB gzippedzero dependenciesESM + UMDby AIQIA

Every map on this page is live and running the real build of the library. Open Source under any of them to see the exact code that produced it — the snippet shown is the very string the demo executes, so it cannot drift out of date. All of them are centred on Curitiba, PR.

Prefer a complete file you can copy wholesale? Every topic below also exists as a standalone example page.

Installation

CargoCanvas ships as an ES module with a UMD fallback, plus one stylesheet.

npm

npm install cargocanvas

Script tag

The UMD build exposes a single global, CargoCanvas:

<link rel="stylesheet" href="/dist/cargo-canvas.css" />
<script src="/dist/cargo-canvas-global.js"></script>

<script>
  const map = new CargoCanvas.CargoMap('map', {center: [-25.4284, -49.2733], zoom: 12});
</script>

The stylesheet filename matters. The default marker icon resolves its image path by looking for a <link> whose href ends in cargo-canvas.css. If you rename or inline the stylesheet, set iconUrl on your icons explicitly.

Quick start

A map needs three things: a container with a height, a view, and a source of tiles.

1. Give the container a height

This is the single most common reason a map renders blank — a <div> with no height is zero pixels tall.

<div id="map" style="height: 400px"></div>

2. Create the map

import {CargoMap, TileLayer, Marker} from 'cargocanvas';
import 'cargocanvas/styles.css';

const map = new CargoMap('map', {
  center: [-25.4284, -49.2733],
  zoom: 12,
});

new TileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
  attribution: '&copy; OpenStreetMap contributors',
  maxZoom: 19,
}).addTo(map);

new Marker([-25.4284, -49.2733]).bindPopup('Hello').addTo(map);

There are no factory functions. CargoCanvas is class-based throughout: write new Marker(…), never marker(…). Every class is a named export.

Coordinates

CargoCanvas takes coordinates as [latitude, longitude] — the opposite order from GeoJSON, which uses [longitude, latitude]. The GeoJSON layer handles that conversion for you; everywhere else, latitude comes first.

A map and a tile layer

TileLayer takes a URL template with {z}, {x} and {y} placeholders. Any XYZ raster source works — CargoCanvas is provider-agnostic.

Every snippet below is extracted from the code that produced the map above it. They all share these two constants, declared once at the top of lib/demos.js:

const OSM = 'https://tile.openstreetmap.org/{z}/{x}/{y}.png';
const OSM_ATTR = '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors';

Basic map

Drag to pan, scroll to zoom, double-click to zoom in.

Markers, popups and tooltips

bindPopup opens on click; bindTooltip opens on hover. Both accept a string, an HTMLElement, or a function returning either.

Three markers, three presentations

Default icon with a popup, default icon with a tooltip, and a custom DivIcon.

Vector layers

Paths share a common styling vocabulary: color, weight, opacity, fill, fillColor, fillOpacity. Note that Circle takes a radius in metres, while CircleMarker takes it in pixels.

Circle, polygon, polyline, rectangle

Click any shape for its popup. Zoom out — the circle scales with the map, in metres.

GeoJSON

The GeoJSON layer accepts a Feature, a FeatureCollection or a raw geometry. Three hooks cover almost everything: style for paths, pointToLayer to decide what a Point becomes, and onEachFeature to wire up interactions.

Styled FeatureCollection

Points become CircleMarkers sized by a property; the polygon is styled by its kind.

Controls

The zoom and attribution controls are added automatically — disable them with the zoomControl: false and attributionControl: false map options. Others you add yourself.

Layer switcher and scale bar

Switch base layers with the control at the top right; toggle the coverage overlay.

ControlPurpose
ZoomControlZoom in / out buttons. Added by default.
AttributionControlData attribution line. Added by default.
LayersControlSwitch base layers, toggle overlays.
ScaleControlMetric / imperial scale bar.
ControlBase class — extend it for your own.

Canvas renderer

By default every path is an SVG element. That is convenient but does not scale — a few thousand DOM nodes will stutter. Pass a shared Canvas renderer and the same paths are drawn into a single <canvas>.

2,000 circle markers

Rendered to one canvas element. Pan and zoom stay smooth.

You can also set renderer once as a map option so every path inherits it, rather than passing it to each layer.

Events

Every class extends Evented, so on, once, off and fire are available everywhere — on the map, on layers, on controls.

Click to place, click again to remove

Mouse events carry a latlng; zoom events carry the new view.

EventFired whenPayload
click / dblclickPointer click on the map or a layerlatlng, containerPoint
pointerover / pointerout / pointermovePointer enters, leaves or moves over the targetlatlng, containerPoint
moveendPanning finishes—
zoomendZooming finishes—
layeradd / layerremoveA layer enters or leaves the maplayer
resizeThe container is resizedoldSize, newSize

Logistics toolkit

Purpose-built classes for fleet and delivery applications: geofencing, interactive drawing and editing, route progress and a navigation-style camera. All geodesic — meters in, meters out — and dependency-free.

Geofencing

A Geofence wraps any zone with contains/distanceToEdge — Polygon (holes respected), Circle, or a Corridor (a polyline with a width in meters) — and watches markers. hysteresis keeps GPS jitter on the fence line from firing bursts; dwell tells "arrived" apart from "drove past".

const zone = new Geofence(clientPolygon, {hysteresis: 30, dwell: 60000});
zone.watch(truckMarker);
zone.on('enter', (e) => notifyArrival(e.marker));
zone.on('exit',  (e) => startTrip(e.marker));
zone.on('dwell', (e) => alertLongStop(e.marker));

Drawing and editing

Draw lays polygons, polylines, rectangles, circles and corridors interactively, with a live measurement tooltip (geodesic area, length or radius). Edit puts draggable handles on any vector layer: drag vertices, drag a midpoint to insert one, Alt+click to delete.

const draw = new Draw(map);
draw.start('polygon');
map.on('drawend', (e) => {
  save(e.layer.toGeoJSON());
  new Edit(e.layer).enable();   // editable right away
});

Tracking and route progress

A Tracker glides a marker between GPS fixes (with an optional breadcrumb trail); RouteProgress snaps positions to the planned route, measures done/remaining and fires deviate/return around a corridor width; FollowCamera drives the bearing/pitch camera so the direction of travel points up.

const tracker  = new Tracker(truckMarker, {duration: 900, trail: 50});
const progress = new RouteProgress(routeLine, {corridor: 250});
progress.on('deviate', (e) => alertOffRoute(e.distance));

gps.on('fix', (pos) => { tracker.push(pos); progress.update(pos); });

new FollowCamera(map, tracker, {pitch: 50, zoom: 16}).enable();

Turf.js bridge

For analysis beyond the built-ins — buffers, unions, voronoi territories — the GeoJSON round-trip is the integration: layer.toGeoJSON() → any Turf.js function → new GeoJSON(result).

See it running in three standalone examples: geofencing, drawing & editing and live tracking.

API surface

Everything below is a named export of the cargocanvas module. Detailed per-option documentation lives in the source, as comment blocks next to each class.

Map

  • CargoMap
  • Map (alias)

Tile layers

  • TileLayer
  • WMSTileLayer
  • WebGLTileLayer
  • GridLayer

Markers

  • Marker
  • Icon
  • DefaultIcon
  • DivIcon

Vector

  • Path
  • Polyline
  • Polygon
  • Rectangle
  • Circle
  • CircleMarker
  • Corridor

Logistics

  • Geofence
  • RouteProgress
  • Tracker
  • FollowCamera

Drawing

  • Draw
  • Edit

Renderers

  • Renderer
  • SVG
  • Canvas

Overlays

  • ImageOverlay
  • VideoOverlay
  • SVGOverlay
  • BlanketOverlay

Grouping

  • Layer
  • LayerGroup
  • FeatureGroup
  • GeoJSON

UI

  • Popup
  • Tooltip
  • DivOverlay

Controls

  • Control
  • ZoomControl
  • AttributionControl
  • LayersControl
  • ScaleControl

Geometry

  • LatLng
  • LatLngBounds
  • Point
  • Bounds
  • Transformation
  • LineUtil / PolyUtil
  • GeoUtil

Projection

  • CRS
  • EarthCRS
  • EPSG3857
  • EPSG3395
  • EPSG4326
  • SimpleCRS
  • Projection

Core

  • Class
  • Evented
  • Handler
  • Browser
  • Util
  • DomUtil / DomEvent
  • Draggable
  • PosAnimation

Map handlers

  • DragHandler
  • ScrollWheelZoomHandler
  • DoubleClickZoomHandler
  • BoxZoomHandler
  • KeyboardHandler
  • PinchZoomHandler
  • TapHoldHandler
  • DragRotateHandler

Extending a class

include adds methods to an existing class; mergeOptions adds default options; addInitHook runs code at construction. This is how the built-in controls attach themselves to the map, and how plugins should attach themselves too.

import {Marker, CargoMap} from 'cargocanvas';

Marker.include({
  flash() {
    this.getElement().animate(
      [{opacity: 1}, {opacity: 0.2}, {opacity: 1}],
      {duration: 600},
    );
    return this;
  },
});

CargoMap.mergeOptions({myPluginEnabled: true});

CargoMap.addInitHook(function () {
  if (this.options.myPluginEnabled) { /* … */ }
});

License

CargoCanvas is released under the BSD 2-Clause License. You may use it commercially, modify it, and ship it inside closed-source products, with no fee and no obligation to open-source your own code.

When you redistribute it you must retain the copyright notice and the disclaimer in source distributions, and reproduce them in the documentation or other materials of binary distributions. The bundles in dist/ already carry them in their banner comment, so shipping those unmodified satisfies the requirement.