remotion-dev/remotion · error · Error

Image has not loaded

Error message

Image has not loaded

What it means

When copying an <img>-based frame to the clipboard, copyImageToClipboard checks image.naturalWidth/naturalHeight. Both are 0 until an image has finished loading (or if it failed), so the copy is refused with this error rather than producing a blank clipboard image.

Solutions

  1. Wait for the frame/thumbnail to finish loading (visible in the Timeline) before copying.
  2. Check the Network tab for a failed image request and fix the asset URL or connection.
  3. Retry the copy after the image loads; the natural size becomes non-zero once loading completes.
  4. If the image never loads, verify the public/ folder or remote host serving the composition assets.
Defensive patterns

Strategy: validation

Validate before calling

const img = el as HTMLImageElement;
if (img.tagName === 'IMG' && (img.naturalWidth === 0 || img.naturalHeight === 0)) {
  await new Promise(r => (img.complete ? r(null) : img.addEventListener('load', r, {once: true})));
}

Type guard

const imageLoaded = (img: HTMLImageElement): boolean =>
  img.complete && img.naturalWidth > 0 && img.naturalHeight > 0;

Try / catch

try {
  await copyImageToClipboard(imgEl);
} catch (e) {
  if (e.message === 'Image has not loaded') {
    // wait for load, then retry the copy
  } else throw e;
}

Prevention

When it happens

Trigger: Right-click > copy image on a Timeline frame backed by an <img> whose src has not finished loading, failed to load (404/network), or was removed from the DOM mid-action.

Common situations: Clicking copy immediately after scrubbing to a not-yet-loaded frame; broken remote image URLs; offline Studio usage; slow CDN responses.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of remotion-dev/remotion@46a3a6bf13 (2026-09-18). Data as JSON: /api/errors/6a4615c6d9ef1658. Report an issue: GitHub.

Appendix: source

Thrown at packages/studio/src/components/Timeline/get-sequence-context-menu-items.ts:74

const copyImageToClipboard = async (element: Element): Promise<void> => {
	let canvas: HTMLCanvasElement;

	if (element.tagName === 'CANVAS') {
		const sourceCanvas = element as HTMLCanvasElement;
		canvas = document.createElement('canvas');
		canvas.width = sourceCanvas.width;
		canvas.height = sourceCanvas.height;
		const context = canvas.getContext('2d');
		if (!context) {
			throw new Error('Could not create canvas context');
		}

		context.drawImage(sourceCanvas, 0, 0);
	} else if (element.tagName === 'IMG') {
		const image = element as HTMLImageElement;
		if (image.naturalWidth === 0 || image.naturalHeight === 0) {
			throw new Error('Image has not loaded');
		}

		canvas = document.createElement('canvas');
		canvas.width = image.naturalWidth;
		canvas.height = image.naturalHeight;
		const context = canvas.getContext('2d');
		if (!context) {
			throw new Error('Could not create canvas context');
		}

		context.drawImage(image, 0, 0);
	} else if (element.tagName === 'VIDEO') {
		const video = element as HTMLVideoElement;
		if (video.videoWidth === 0 || video.videoHeight === 0) {
			throw new Error('Video frame has not loaded');
		}

		canvas = document.createElement('canvas');

View on GitHub (pinned to 46a3a6bf13)