Skip to main content

Sizing the canvas

This is the single most common source of "it looks squashed" or "it looks blurry". It takes one minute to get right and then never bothers you again.

The rule

Size the canvas in CSS. Never set the width/height attributes yourself.

CanvasGlobe reads clientWidth/clientHeight, multiplies by the device pixel ratio (capped at 2), and sets the backing store to match. A ResizeObserver repeats that whenever the element changes size.

#globe { width: 100%; max-width: 520px; aspect-ratio: 1; }

Globe mode

A globe is round. Use a square canvas:

aspect-ratio: 1;

The sphere fills radiusRatio (default 0.4) of the smaller side, so a non-square canvas just adds empty space rather than distorting anything.

Map mode

A flat map is not square, and the right ratio depends on both the projection and the latitude window. Ask the library:

import { mapAspect } from "canvas-globe";

mapAspect(); // 0.386: equirectangular, default latRange
mapAspect([83, -56], "mercator"); // 0.633
mapAspect([90, -90]); // 0.5: full-height equirectangular

mapAspect returns height ÷ width, so CSS aspect-ratio wants its reciprocal:

canvas.style.aspectRatio = String(1 / mapAspect(latRange, projection));
/* equirectangular with the default latRange */
#map { width: 100%; aspect-ratio: 360 / 139; }
Loading globe…

:::info Letterboxing, not stretching If the canvas ratio does not match the projection, CanvasGlobe fits the map uniformly and centres it. The map is never distorted: you just get empty space at the sides or top. :::

Framing a single country

A country has its own shape, so neither square nor world-map ratios suit it. countryAspect() gives you the right one:

const ratio = globe.countryAspect("India"); // height / width
canvas.style.aspectRatio = String(1 / ratio);
globe.resize();
globe.focusOn("India", { isolate: true });
Loading globe…

Retina and DPR

The backing store is cssSize × min(2, devicePixelRatio). Capping at 2 keeps 3× phones from paying for nine times the pixels with no visible gain.

You never deal with this directly: project() and unproject() both speak CSS pixels, as do the positions handed to onHover and onClick.

Resizing

ResizeObserver handles layout changes automatically. Call resize() manually only when you change the canvas size in a way the observer cannot see: for example right after switching the CSS aspect-ratio in the same frame:

canvas.classList.add("flat");
requestAnimationFrame(() => globe.resize());

Common mistakes

SymptomCauseFix
Blurry on a phone or retina screenwidth/height attributes set by handSize it in CSS only
Map looks squashedCanvas ratio does not match the projectionUse mapAspect()
Canvas has zero heightA parent with display: flex and no heightGive the canvas an aspect-ratio
Nothing rendersCanvas is display: none at constructionConstruct it when visible, or call resize() after showing it