Skip to main content

Troubleshooting

Nothing renders

CauseFix
Canvas was display: none at constructionConstruct when visible, or call resize() after showing it
Canvas has no heightGive it an aspect-ratio; a flex child can collapse to zero
The element is not a <canvas>createGlobe throws a TypeError: check the selector

It looks blurry

You set width/height as HTML attributes. Size it in CSS only and let CanvasGlobe manage the backing store for the device pixel ratio. See Sizing the canvas.

The map looks squashed

The canvas aspect ratio does not match the projection. Use mapAspect():

canvas.style.aspectRatio = String(1 / mapAspect(latRange, projection));

CanvasGlobe letterboxes rather than stretching, so a mismatch shows as empty space, never distortion.

A marker is in the sea

Almost always the coastline, not the marker. Bundled geometry is Natural Earth 1:110m, decimated to roughly 5 km, so a harbourside city can sit just off the drawn shore. The projection itself is exact to floating-point precision.

Natural Earth 1:110m also omits microstates: Singapore, Malta, Monaco, Bahrain, Maldives, Hong Kong, Andorra, Barbados and Mauritius are not in the dataset, so markers there land on water or a neighbour. Pass higher-detail GeoJSON via world if that matters.

A marker vanished

latRange defaults to [83, -56], which trims the polar caps. Anything below −56° projects outside the drawn map:

latRange: [90, -90];

In globe mode, markers on the far side are hidden by design: project() returns null for them.

The viewer pin is nowhere near me

Expected, and documented at length. A time zone locates you to a region, not a street. India is a single time zone, so everyone in it resolves to the same anchor. That is why a dashed accuracy circle is drawn: it is telling the truth about the uncertainty.

Ask for GPS when you need real precision:

const found = await globe.locateViewer({ precise: true });

On a desktop with no GPS the browser falls back to network positioning, which often lands on your ISP's city. Check accuracyMeters before trusting it.

Countries stay dim after I change something

You still have a focus set. focusOn dims or hides everything else by design.

globe.clearFocus();

clearFocus() also restores the maxZoom that focusOn raised to frame the country.

Zoom is stuck at a strange maximum

focusOn deliberately lifts maxZoom: framing India needs about 10.6× against a default of 8. clearFocus() puts it back, and setting maxZoom yourself takes over permanently.

A GIF is frozen on the first frame

Browsers only animate images attached to the document. CanvasGlobe parks GIF elements off-screen automatically, so this normally works. If you passed an HTMLImageElement you created and never added to the DOM, it will not animate. Pass the URL string instead and let the library manage it.

A video or texture is blank

Cross-origin media is read with drawImage/getImageData, so it needs permissive CORS headers. Same-origin paths and data URLs always work. Check the console for a security error.

Arcs stutter or feel slow

You are probably rebuilding the arc array every render. Great-circle points are cached per arc object, so new objects mean a cache miss every frame.

const arcs = useMemo(() => routes.map(toArc), [routes]);

Recording does nothing

Check support first: Safari's MediaRecorder coverage is narrower than Chrome's:

import { canRecord } from "canvas-globe";
if (!canRecord()) showFallback();

exportImage returns null

There is no document: you are in Node or a worker. Run it in a headless browser for build-time OG images. See Exporting.

Memory grows in a single-page app

You are not calling destroy(). Each un-destroyed instance keeps a requestAnimationFrame loop, listeners, a ResizeObserver and any media alive.

The React component and custom element handle this for you.

SSR errors

Importing is safe: nothing touches the DOM until you construct. If you see an error, something is constructing during render. In Next.js App Router, add "use client" or load the component with dynamic(..., { ssr: false }).

India's boundary

India is drawn on the Survey of India boundary: Jammu and Kashmir, Ladakh and Aksai Chin included. That is not an option or an overlay; it is what the bundled geometry says, the same way the geometry says where France is.

Most world datasets, Natural Earth included, depict de-facto administrative lines instead. Rather than patch that at render time, the two sources are reconciled when the data is generated: India carries Datameet's CC-0 india-composite outline, and that area is subtracted from the neighbouring countries so no two shapes claim the same ground. The result is one continuous boundary at every land style, theme and zoom level, with nothing overlapping and no special cases in the renderer.

India behaves like any other country throughout: choropleth, country media, labels, focusOn, tooltips and hit testing. Key it as IN, 356 or India.

If you need a different depiction, replace the geometry:

createGlobe(canvas, { world: myGeoJson });

Still stuck?

Open an issue with the option object you passed and what you expected. A reproduction on the playground will help reproduce the problem.