{"record":{"id":"6a4615c6d9ef1658","repo":"remotion-dev/remotion","slug":"image-has-not-loaded","errorCode":null,"errorMessage":"Image has not loaded","messagePattern":"Image has not loaded","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/studio/src/components/Timeline/get-sequence-context-menu-items.ts","lineNumber":74,"sourceCode":"\nconst copyImageToClipboard = async (element: Element): Promise<void> => {\n\tlet canvas: HTMLCanvasElement;\n\n\tif (element.tagName === 'CANVAS') {\n\t\tconst sourceCanvas = element as HTMLCanvasElement;\n\t\tcanvas = document.createElement('canvas');\n\t\tcanvas.width = sourceCanvas.width;\n\t\tcanvas.height = sourceCanvas.height;\n\t\tconst context = canvas.getContext('2d');\n\t\tif (!context) {\n\t\t\tthrow new Error('Could not create canvas context');\n\t\t}\n\n\t\tcontext.drawImage(sourceCanvas, 0, 0);\n\t} else if (element.tagName === 'IMG') {\n\t\tconst image = element as HTMLImageElement;\n\t\tif (image.naturalWidth === 0 || image.naturalHeight === 0) {\n\t\t\tthrow new Error('Image has not loaded');\n\t\t}\n\n\t\tcanvas = document.createElement('canvas');\n\t\tcanvas.width = image.naturalWidth;\n\t\tcanvas.height = image.naturalHeight;\n\t\tconst context = canvas.getContext('2d');\n\t\tif (!context) {\n\t\t\tthrow new Error('Could not create canvas context');\n\t\t}\n\n\t\tcontext.drawImage(image, 0, 0);\n\t} else if (element.tagName === 'VIDEO') {\n\t\tconst video = element as HTMLVideoElement;\n\t\tif (video.videoWidth === 0 || video.videoHeight === 0) {\n\t\t\tthrow new Error('Video frame has not loaded');\n\t\t}\n\n\t\tcanvas = document.createElement('canvas');","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/remotion-dev/remotion/blob/46a3a6bf13ca367376ff18bbb06dae2e87e0d068/packages/studio/src/components/Timeline/get-sequence-context-menu-items.ts#L56-L92","documentation":"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.","triggerScenarios":"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.","commonSituations":"Clicking copy immediately after scrubbing to a not-yet-loaded frame; broken remote image URLs; offline Studio usage; slow CDN responses.","solutions":["Wait for the frame/thumbnail to finish loading (visible in the Timeline) before copying.","Check the Network tab for a failed image request and fix the asset URL or connection.","Retry the copy after the image loads; the natural size becomes non-zero once loading completes.","If the image never loads, verify the public/ folder or remote host serving the composition assets."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const img = el as HTMLImageElement;\nif (img.tagName === 'IMG' && (img.naturalWidth === 0 || img.naturalHeight === 0)) {\n  await new Promise(r => (img.complete ? r(null) : img.addEventListener('load', r, {once: true})));\n}","typeGuard":"const imageLoaded = (img: HTMLImageElement): boolean =>\n  img.complete && img.naturalWidth > 0 && img.naturalHeight > 0;","tryCatchPattern":"try {\n  await copyImageToClipboard(imgEl);\n} catch (e) {\n  if (e.message === 'Image has not loaded') {\n    // wait for load, then retry the copy\n  } else throw e;\n}","preventionTips":["Wait for thumbnails/frames to render before invoking copy actions.","Fix broken asset URLs flagged in the Network tab promptly.","Preload images referenced by the composition before interactive editing."],"tags":["image-loading","clipboard","timing","studio"],"backgroundTag":"invalid-state-transition","analyzedSha":"46a3a6bf13ca367376ff18bbb06dae2e87e0d068","analyzedAt":"2026-09-18T00:12:20.256Z","contentChangedAt":"2026-09-18T00:12:20.256Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}