Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions packages/core/src/HtmlInCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,10 @@ export const isHtmlInCanvasSupported = (): boolean => {
return cachedSupport;
};

/** Shown when {@link isHtmlInCanvasSupported} is false: APIs are absent (old Chrome and/or flag off). */
export const HTML_IN_CANVAS_UNSUPPORTED_MESSAGE =
'HTML in Canvas is not supported. Two common causes: Chrome is older than version 148 (update Chrome), or the HTML-in-Canvas flag is disabled at chrome://flags/#canvas-draw-element (enable it and restart Chrome).';

export type HtmlInCanvasOnPaint = (
params: HtmlInCanvasOnPaintParams,
) => void | Promise<void>;
Expand Down Expand Up @@ -299,11 +303,7 @@ const HtmlInCanvasInner = forwardRef<
const {continueRender, cancelRender} = useDelayRender();

if (!isHtmlInCanvasSupported()) {
cancelRender(
new Error(
'HTML in Canvas is not supported. Open this page in Chrome Canary with chrome://flags/#canvas-draw-element enabled.',
),
);
cancelRender(new Error(HTML_IN_CANVAS_UNSUPPORTED_MESSAGE));
}

const {durationInFrames: videoDuration} = useVideoConfig();
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export type {
export type {SolidProps} from './effects/Solid.js';
export {
HtmlInCanvas,
HTML_IN_CANVAS_UNSUPPORTED_MESSAGE,
isHtmlInCanvasSupported,
type HtmlInCanvasOnInit,
type HtmlInCanvasOnInitCleanup,
Expand Down
2 changes: 1 addition & 1 deletion packages/dockerfiles/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,6 @@ Located in `packages/example/src/BrowserTest/index.tsx`, it tests:

## html-in-canvas composition

Located in `packages/example/src/HtmlInCanvas/index.tsx`, it tests the experimental [WICG html-in-canvas](https://github.com/WICG/html-in-canvas) `CanvasRenderingContext2D.drawElementImage()` API. If the API is unavailable in the bundled Chrome (e.g. without `chrome://flags/#canvas-draw-element` or Chrome Canary), the composition fails the render with an error.
Located in `packages/example/src/HtmlInCanvas/index.tsx`, it tests the experimental [WICG html-in-canvas](https://github.com/WICG/html-in-canvas) `CanvasRenderingContext2D.drawElementImage()` API. If the API is unavailable in the bundled Chrome (for example without `chrome://flags/#canvas-draw-element` enabled), the composition fails the render with an error.

The bundle is built from `packages/example` and copied into each Docker container during build.
8 changes: 4 additions & 4 deletions packages/docs/docs/client-side-rendering/html-in-canvas.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This is not the same as the [`<HtmlInCanvas>`](/docs/html-in-canvas) component f
On supported Chromium-based browsers, [`@remotion/web-renderer`](/docs/web-renderer) can optionally capture full frames using the experimental [HTML-in-canvas](/docs/html-in-canvas) APIs.

:::info
As of April 2026, this feature requires [Chrome Canary](https://www.google.com/chrome/canary/). Regular Chrome has partial support ([`drawElementImage`](https://github.com/WICG/html-in-canvas#2-drawelementimage-and-webglwebgpu-equivalents)) but is missing `canvas.requestPaint()`, which can cause rendering issues such as frames being repeated. See [Compatibility](#compatibility) for details.
Use a recent version of Google Chrome or another Chromium-based browser with `chrome://flags/#canvas-draw-element` enabled. Current Chrome stable releases ship `canvas.requestPaint()` together with [`drawElementImage`](https://github.com/WICG/html-in-canvas#2-drawelementimage-and-webglwebgpu-equivalents), which is required for correct frame capture. See [Compatibility](#compatibility) for details.
:::

Enabling the option does not guarantee that this path runs. If the browser does not have HTML-in-canvas enabled, it falls back to the [default frame capturing mechanism](/docs/client-side-rendering/how-it-works).
Expand Down Expand Up @@ -82,7 +82,7 @@ If HTML-in-canvas is available, and allowed, but not used, a warning will be pri
Not using HTML-in-canvas: drawElementImage is available but canvas.requestPaint() is missing. Use a Chromium version that ships requestPaint.
```

This can happen for example if enabling the feature in Chrome rather than Chrome Canary (as of time of writing in April 2026).
This can happen for example when using an older Chromium build that does not ship `canvas.requestPaint()`.

If HTML-in-canvas is not available, or not allowed, no warning will be printed.

Expand All @@ -102,9 +102,9 @@ If HTML-in-canvas is not available, or not allowed, no warning will be printed.
hideServers
/>

HTML-in-canvas depends on Chromium flag `chrome://flags/#canvas-draw-element` which needs to be explicitly enabled as of time of writing in April 2026.
HTML-in-canvas depends on Chromium flag `chrome://flags/#canvas-draw-element`, which must be enabled explicitly.

As of April 2026, [Chrome Canary](https://www.google.com/chrome/canary/) is required for full support. Regular Chrome exposes [`drawElementImage`](https://github.com/WICG/html-in-canvas#2-drawelementimage-and-webglwebgpu-equivalents) when the flag is enabled, but does not yet ship `canvas.requestPaint()`. Without `requestPaint()`, the renderer cannot synchronize paint operations, which can lead to repeated or incorrect frames.
Use a recent Chrome or Chromium version with the flag enabled for full support. Very old Chromium builds may expose [`drawElementImage`](https://github.com/WICG/html-in-canvas#2-drawelementimage-and-webglwebgpu-equivalents) but omit `canvas.requestPaint()`. Without `requestPaint()`, the renderer cannot synchronize paint operations, which can lead to repeated or incorrect frames.

## See also

Expand Down
2 changes: 1 addition & 1 deletion packages/docs/docs/contributing/presentation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ If you want to contribute the presentations back into the `@remotion/transitions
<Step>4</Step> Handle the boundary cases inside the JS <code>draw()</code> function: when <code>prevImage</code> is <code>null</code>, force <code>time = 0</code>; when <code>nextImage</code> is <code>null</code>, force <code>time = 1</code>.<br />
<Step>5</Step> Set <code>serverSideRendering</code>, <code>nodejs</code>, <code>bun</code> and <code>serverlessFunctions</code> to <code>true</code> in the <code>&lt;CompatibilityTable /&gt;</code> on your docs page — Remotion's bundled Chromium for Lambda already includes the required APIs. Set <code>firefox</code> and <code>safari</code> to <code>false</code>.<br />
<Step>6</Step> The table-of-contents preview should always render the pre-rendered fallback video — the live <code>&lt;Player&gt;</code> would crash for readers without HTML-in-canvas enabled, and a tiny TOC tile gains nothing from running the real shader. See <a href="https://github.com/remotion-dev/remotion/blob/main/packages/docs/components/transitions/zoom-blur-toc-preview.tsx"><code>zoom-blur-toc-preview.tsx</code></a> for the pattern. For the in-page <code>&lt;Demo&gt;</code>, branch on <code>HtmlInCanvas.isSupported()</code> via the <code>useHtmlInCanvasDocsDemoBranch()</code> hook so supporting browsers see the real shader and others fall back to the video — see <a href="https://github.com/remotion-dev/remotion/blob/main/packages/docs/components/demos/ZoomBlurDemo.tsx"><code>ZoomBlurDemo.tsx</code></a>.<br />
<Step>7</Step> Render the fallback videos from a composition under <code>packages/example/src/HtmlInCanvas/</code> using <code>npx remotion render &lt;id&gt; --browser-executable=&lt;path-to-Chrome-Canary&gt; --allow-html-in-canvas</code>. Drop a high-res 1920×1080 file at <code>packages/docs/static/img/&lt;name&gt;.mp4</code> for the docs page and a low-res 540×280 file at <code>&lt;name&gt;-thumb.mp4</code> for the TOC tile.<br />
<Step>7</Step> Render the fallback videos from a composition under <code>packages/example/src/HtmlInCanvas/</code> using <code>npx remotion render &lt;id&gt; --browser-executable=&lt;path-to-chrome&gt; --allow-html-in-canvas</code>. Drop a high-res 1920×1080 file at <code>packages/docs/static/img/&lt;name&gt;.mp4</code> for the docs page and a low-res 540×280 file at <code>&lt;name&gt;-thumb.mp4</code> for the TOC tile.<br />
<Step>8</Step> Add an <code>&lt;HtmlInCanvasLabel /&gt;</code> next to the presentation name in the table-of-contents tile to flag the requirement.

## See also
Expand Down
12 changes: 6 additions & 6 deletions packages/docs/docs/html-in-canvas-guide.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ crumb: 'Designing visuals'

Regular video editors are canvas-based which allows for complex effects and compositing that is not possible with plain CSS.

[HTML-in-canvas](https://github.com/WICG/html-in-canvas) is an experimental browser API currently only available in Chrome Canary behind a flag, which allows you to draw a live DOM node into a `<canvas>` and post-process it with the Canvas 2D API, WebGL or WebGPU.
[HTML-in-canvas](https://github.com/WICG/html-in-canvas) is an experimental browser API available in Chrome and other Chromium-based browsers behind a flag, which allows you to draw a live DOM node into a `<canvas>` and post-process it with the Canvas 2D API, WebGL or WebGPU.

Remotion offers a [`<HtmlInCanvas>`](/docs/remotion/html-in-canvas) component, [transitions](#with-remotiontransitions) and HTML-in-canvas-based client-side rendering.

## Enabling the flag in Chrome Canary
## Enabling the flag in Chrome

To preview HTML-in-canvas effects in the [Remotion Studio](/docs/studio), you need to use [Chrome Canary](https://www.google.com/chrome/canary/) v149 and enable a flag:
To preview HTML-in-canvas effects in the [Remotion Studio](/docs/studio), use [Google Chrome](https://www.google.com/chrome/) v149 or later and enable a flag:

1. Install [Chrome Canary](https://www.google.com/chrome/canary/).
1. Install or update [Google Chrome](https://www.google.com/chrome/).
2. Open `chrome://flags/#canvas-draw-element`.
3. Set the `HTML-in-Canvas` flag to `Enabled` and restart.

Expand Down Expand Up @@ -90,7 +90,7 @@ You can also build your own — see [Making a custom HTML-in-canvas presentation

## Limitations

HTML-in-canvas is only available in [Chrome Canary](https://www.google.com/chrome/canary/) (Chrome 149 and later) with the `chrome://flags/#canvas-draw-element` flag enabled.
HTML-in-canvas is only available in Chrome 149 and later with the `chrome://flags/#canvas-draw-element` flag enabled.
The API is unstable - Chrome may change the API or even remove it in the future.

Nesting `<HtmlInCanvas>` inside another `<HtmlInCanvas>` is not supported. Chrome would only display the outer effect, so this is invalid in Remotion. It will throw an error.
Expand All @@ -100,7 +100,7 @@ If you need to combine multiple effects, try to merge them into a single `onPain

From [`v4.0.455`](https://github.com/remotion-dev/remotion/releases/tag/v4.0.455), render is supported locally via [`npx remotion render`](/docs/cli/render) and Studio, on [Lambda](/docs/lambda), on [Vercel](/docs/vercel) and through [server-side rendering](/docs/ssr) APIs.

We compiled a version of Chrome Canary and made it the default, and enabled the flag for you.
We compiled a version of Chrome and made it the default, and enabled the flag for you.

If your effect uses a WebGL shader, pass [`--gl=angle`](/docs/gl-options) to make the render work:

Expand Down
2 changes: 1 addition & 1 deletion packages/docs/docs/remotion/html-in-canvas.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ crumb: 'API'

This component renders its children into a `<canvas>` using the browser’s [HTML-in-canvas](https://github.com/WICG/html-in-canvas) API and allows you to draw an effect using the Canvas 2D API, WebGL or WebGPU.

For an overview, examples and how to enable the Chrome Canary flag, see the [HTML-in-canvas guide](/docs/html-in-canvas).
For an overview, examples and how to enable the Chrome flag, see the [HTML-in-canvas guide](/docs/html-in-canvas).

HTML-in-canvas is only available in Chrome 149 and later and if the `chrome://flags/#canvas-draw-element` flag is enabled.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ const myPresentation = makeHtmlInCanvasPresentation(myShader);
</details>

:::warning
HTML-in-canvas requires [Chrome Canary](https://www.google.com/chrome/canary/) with `chrome://flags/#canvas-draw-element` enabled.
HTML-in-canvas requires [Google Chrome](https://www.google.com/chrome/) with `chrome://flags/#canvas-draw-element` enabled.
:::

## Arguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ title: 'Custom HTML-in-canvas presentations'
Build a presentation that runs the entering and exiting scenes through a shader using the [HTML-in-canvas](/docs/html-in-canvas) APIs.

:::warning
HTML-in-canvas requires [Chrome Canary](https://www.google.com/chrome/canary/) with `chrome://flags/#canvas-draw-element` enabled in the browser.
HTML-in-canvas requires [Google Chrome](https://www.google.com/chrome/) with `chrome://flags/#canvas-draw-element` enabled in the browser.
:::

## When to use this
Expand Down
4 changes: 2 additions & 2 deletions packages/docs/docs/transitions/presentations/zoom-blur.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A presentation where the outgoing scene zooms out and rotates while the incoming
<Demo type="zoom-blur" />

:::warning
This presentation is built with [HTML-in-canvas](/docs/remotion/html-in-canvas) and currently requires [Chrome Canary](https://www.google.com/chrome/canary/) with `chrome://flags/#canvas-draw-element` enabled. It does not work in Firefox or Safari.
This presentation is built with [HTML-in-canvas](/docs/remotion/html-in-canvas) and requires [Google Chrome](https://www.google.com/chrome/) with `chrome://flags/#canvas-draw-element` enabled. It does not work in Firefox or Safari.
:::

## Example
Expand Down Expand Up @@ -85,7 +85,7 @@ Defaults to `Math.PI / 6` (30°).
studio
/>

For Preview, Chrome Canary with the `chrome://flags/#canvas-draw-element` flag enabled is required.
For Preview, Chrome with the `chrome://flags/#canvas-draw-element` flag enabled is required.

## See also

Expand Down
4 changes: 2 additions & 2 deletions packages/docs/docs/transitions/presentations/zoom-in-out.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A presentation where the outgoing scene zooms in toward the viewer and crossfade
<Demo type="zoom-in-out" />

:::warning
This presentation is built with [HTML-in-canvas](/docs/remotion/html-in-canvas) and currently requires [Chrome Canary](https://www.google.com/chrome/canary/) with `chrome://flags/#canvas-draw-element` enabled. It does not work in Firefox or Safari.
This presentation is built with [HTML-in-canvas](/docs/remotion/html-in-canvas) and requires [Google Chrome](https://www.google.com/chrome/) with `chrome://flags/#canvas-draw-element` enabled. It does not work in Firefox or Safari.
:::

## Example
Expand Down Expand Up @@ -79,7 +79,7 @@ const BasicTransition = () => {
studio
/>

For Preview, Chrome Canary with the `chrome://flags/#canvas-draw-element` flag enabled is required.
For Preview, Chrome with the `chrome://flags/#canvas-draw-element` flag enabled is required.

## Credits

Expand Down
2 changes: 1 addition & 1 deletion packages/skills-evals/src/app/comparison.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export const renderComparison = (comparisonData: ComparisonWithManifests) => {
<main className="grid min-w-0 gap-4">
<div className="grid grid-cols-2 gap-3 max-lg:grid-cols-1">
<RunPanel
label="Before"
label={`Before (${comparison.before.gitRef ?? comparison.before.source})`}
manifest={beforeManifest}
manifestPath={comparison.before.manifestPath}
/>
Expand Down
6 changes: 3 additions & 3 deletions packages/skills-evals/src/app/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ export const renderHome = async () => {
<section className="max-w-3xl">
<h2 className="text-sm font-semibold text-zinc-700">Workflow</h2>
<p className="mt-1 text-sm leading-6 text-zinc-500">
When skills match HEAD, run a scenario as visual validation. When
you have local skill changes, run a comparison to see how those
changes affect the resulting videos.
When skills match the configured base ref, run a scenario as
visual validation. When you have skill changes, run a comparison
to see how those changes affect the resulting videos.
</p>
</section>
<section className="grid grid-cols-[repeat(auto-fit,minmax(300px,1fr))] gap-3">
Expand Down
6 changes: 5 additions & 1 deletion packages/skills-evals/src/app/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@ export const getActiveJob = (scenarioId: string) =>
(job) => job.scenarioId === scenarioId && job.status === 'running',
);

export const startComparison = (scenario: SkillEvalScenario) => {
export const startComparison = (
scenario: SkillEvalScenario,
options: {beforeGitRef?: string} = {},
) => {
const existingJob = getActiveJob(scenario.id);

if (existingJob) {
Expand Down Expand Up @@ -163,6 +166,7 @@ export const startComparison = (scenario: SkillEvalScenario) => {
};

const comparisonPromise = runSkillEvalComparison(scenario, {
beforeGitRef: options.beforeGitRef,
onEvent: handleEvent,
})
.then((result) => {
Expand Down
13 changes: 11 additions & 2 deletions packages/skills-evals/src/app/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,23 @@ export const routes = {
'/': async () => htmlResponse(await renderHome()),

'/api/compare/:scenarioId': {
POST: (request: BunRequest<'/api/compare/:scenarioId'>) => {
POST: async (request: BunRequest<'/api/compare/:scenarioId'>) => {
const scenario = getScenario(request.params.scenarioId);

if (!scenario) {
return json({error: 'Unknown scenario'}, {status: 404});
}

return json(startComparison(scenario));
const body = await request.json().catch(() => null);
const beforeGitRef =
body &&
typeof body === 'object' &&
'beforeGitRef' in body &&
typeof body.beforeGitRef === 'string'
? body.beforeGitRef
: undefined;

return json(startComparison(scenario, {beforeGitRef}));
},
},

Expand Down
Loading
Loading