Skip to content

@remotion/web-renderer: Allow HTML-in-Canvas API to be used#7011

Merged
JonnyBurger merged 18 commits intomainfrom
feat/web-renderer-html-in-canvas-rb5
Apr 6, 2026
Merged

@remotion/web-renderer: Allow HTML-in-Canvas API to be used#7011
JonnyBurger merged 18 commits intomainfrom
feat/web-renderer-html-in-canvas-rb5

Conversation

@JonnyBurger
Copy link
Copy Markdown
Member

Summary

  • When the experimental HTML-in-Canvas API is available in Chromium (drawElementImage, requestPaint, layoutSubtree), use it instead of the built-in DOM composer for capturing frames.
  • Adds allowHtmlInCanvas option (default true) to renderMediaOnWeb() and renderStillOnWeb() to opt out.
  • Adds a checkbox toggle in the Studio render dialog under the "Other" tab.
  • Logs a warning when using the native path, and explains why when falling back.
  • Uses requestPaint() + paint event to ensure fresh paint records before drawElementImage, relying on Remotion's existing waitForReady for frame readiness.

Closes #6998

Test plan

  • Open Chrome Canary with chrome://flags/#canvas-draw-element enabled
  • Render a video via Studio — confirm the "Using Chromium experimental HTML-in-Canvas" warning appears
  • Render same video with allowHtmlInCanvas: false — confirm fallback message and software compose path is used
  • On a browser without the API (e.g. Firefox), confirm graceful fallback with diagnostic message
  • Verify the "Allow HTML-in-Canvas" checkbox appears in the Studio render dialog "Other" tab

Made with Cursor

…ll frames when available.

When Chromium experimental API is present (chrome://flags/#canvas-draw-element), capture full composition layers by reparenting the scaffold root under a layoutsubtree canvas and using drawElementImage, then copy to OffscreenCanvas. Fall back to the existing compose() walk for partial cutouts, SVG roots, or on failure.

Made-with: Cursor
…ive capture

- Default true; set false to force software DOM composer
- Log one warning per renderStillOnWeb / renderMediaOnWeb when drawElementImage path is used
- Document option on render still and render media docs pages

Made-with: Cursor
- Report HtmlInCanvasLayerOutcome for full-frame captures (cutout mismatch,
  option off, missing drawElementImage, or drawElementImage error)
- Use warn-level messages so they appear when logLevel is warn (not only on success)
- Export HtmlInCanvasLayerOutcome type; document logging in web-renderer docs

Made-with: Cursor
…wElementImage

- Double rAF after reparenting to flush layout
- One paint pass without drawing so Chromium can record layout
- Retry drawElementImage on further paints when "No cached paint record" persists

Made-with: Cursor
…in-canvas

Instead of reparenting the scaffold div into a throwaway canvas on every frame
(which never gives Chromium time to build a paint record), set up the
layoutsubtree canvas once in createScaffold and keep the scaffold div as its
permanent child for the lifetime of the render.

- setupHtmlInCanvas(): wrapper > canvas[layoutsubtree] > div
- drawWithHtmlInCanvas(): just ctx.drawElementImage() per frame — no reparenting
- teardownHtmlInCanvas(): restore original DOM on dispose
- createLayer: accepts HtmlInCanvasContext from scaffold, tries native draw,
  falls back to compose() on error
- Scaffold reports why html-in-canvas is unavailable at setup time

Made-with: Cursor
…html-in-canvas

Two issues prevented Chromium from building a paint record for the
layoutsubtree canvas children:

1. The wrapper div has visibility:hidden which is inherited by the canvas.
   Override with visibility:visible on the layoutCanvas — the spec says
   layoutsubtree children aren't visible to the user anyway, and the wrapper
   is already behind everything (z-index:-9999, pointer-events:none).

2. The first drawElementImage call happens synchronously after flushSync,
   before the browser has had any paint cycle. Add a warmup retry loop:
   on "No cached paint record" errors, wait for requestAnimationFrame and
   retry up to 10 times. After the first success, mark warmedUp=true so
   subsequent frames are synchronous with no overhead.

Made-with: Cursor
…vas and add Studio toggle

Made-with: Cursor
…etry loop for html-in-canvas

Made-with: Cursor
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 6, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bugs Ready Ready Preview, Comment Apr 6, 2026 11:33am
remotion Ready Ready Preview, Comment Apr 6, 2026 11:33am

Request Review

…rawElementImage is available but fails

Made-with: Cursor
@JonnyBurger JonnyBurger changed the title @remotion/web-renderer: Use HTML-in-Canvas API for faster client-side rendering @remotion/web-renderer: Allow HTML-in-Canvas API for faster client-side rendering Apr 6, 2026
Use singular `<Option>` component in web-renderer docs, add
html-in-canvas doc page, and remove redundant warning text about
pixel differences and software rasterization fallback.

Made-with: Cursor
Use HTML-in-canvas consistently in logs, Studio, CLI, and docs; refresh
the HTML-in-canvas social card and articles entry; drop the Options alias
from MDX; point allow-html-in-canvas at the client-side-rendering guide.

Made-with: Cursor
@JonnyBurger JonnyBurger changed the title @remotion/web-renderer: Allow HTML-in-Canvas API for faster client-side rendering @remotion/web-renderer: Allow HTML-in-Canvas API to be used Apr 6, 2026
@JonnyBurger JonnyBurger marked this pull request as ready for review April 6, 2026 10:19
Copy link
Copy Markdown
Contributor

@pullfrog pullfrog bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

Two issues found — one bug and one robustness concern. Overall the integration is clean and well-plumbed through the stack.

1. Broken markdown link in packages/docs/docs/performance.mdx

The change on line 33 introduces a broken markdown link. The original was:

Both [`<Html5Video>`](/docs/html5-video) (formerly just called `<Video>`, from the `remotion` package) and ...

The new version wraps everything in [...] without a (url):

Both [`<Html5Video>` (formerly just called `<Video>`, from the `remotion` package)] and ...

This produces an unresolved reference, not a link. Should be either restored or converted to plain inline code.

2. waitForPaint in packages/web-renderer/src/html-in-canvas.ts has no timeout

Lines 89-96 — waitForPaint returns a promise that resolves only when the paint event fires. If the event never fires (plausible with an experimental API), the promise hangs forever. The outer delayRenderTimeoutInMilliseconds only applies to waitForReady, not to the capture step, so this would stall an entire render with no diagnostic.

A simple safety timeout (e.g. 5s) that reject()s would allow the existing try/catch in createLayer to catch it and fall back to the DOM composer — no render would break.

Minor: PR description says default is true

Adds allowHtmlInCanvas option (default true)

The code correctly defaults to false everywhere (?? false in both renderMediaOnWeb and renderStillOnWeb). The docs are also correct. Just the PR description is wrong.

Pullfrog  | Fix it ➔View workflow run | Using Big Pickle (free) | 𝕏

@JonnyBurger JonnyBurger merged commit 424c588 into main Apr 6, 2026
18 checks passed
@JonnyBurger JonnyBurger deleted the feat/web-renderer-html-in-canvas-rb5 branch April 6, 2026 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Enable HTML-in-Canvas features in Chrome Canary for client side rendering boost.

1 participant