Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Apply best practices for creating programmatic videos with Remotion and React
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
rules/html-in-canvas.md
1# Using `<HtmlInCanvas>` in Remotion23Renders children into a `<canvas>` so you can post-process them with the Canvas 2D API or WebGL.45Only works in Chrome 149+ with the `chrome://flags/#canvas-draw-element` flag enabled.6Give the user a notice.78## Nesting910Do not nest `<HtmlInCanvas>` inside another `<HtmlInCanvas>`. Remotion throws:1112```13<HtmlInCanvas> effects cannot be nested together. Chrome will only display the outer effect. Consider merging the effects into one if you can.14```1516## Enabling WebGL during renders1718If you make use of WebGL during renders, you need to enable it:1920From the CLI:2122```bash23npx remotion render --gl=angle24```2526Set it as the default for Studio and CLI (advised):2728```ts29import { Config } from "@remotion/cli/config";3031Config.setChromiumOpenGlRenderer("angle");32```3334## Basic usage3536By default, draws to canvas with no effect applied:3738```tsx39import { HtmlInCanvas } from "remotion";4041export const MyComp = () => {42return (43<HtmlInCanvas width={1280} height={720}>44<div style={{ fontSize: 80 }}>Hello</div>45</HtmlInCanvas>46);47};48```4950## 2D effect with `onPaint`5152`onPaint` runs whenever the content updates. Call `ctx.drawElementImage(elementImage, 0, 0)` to draw the captured DOM, and assign the returned transform to `element.style.transform` so DOM selection still aligns with the painted output.5354```tsx55import {56AbsoluteFill,57HtmlInCanvas,58type HtmlInCanvasOnPaint,59useCurrentFrame,60useVideoConfig,61} from "remotion";62import { useCallback } from "react";6364export const Blur = () => {65const frame = useCurrentFrame();66const { width, height, fps } = useVideoConfig();6768const onPaint: HtmlInCanvasOnPaint = useCallback(69({ canvas, element, elementImage }) => {70const ctx = canvas.getContext("2d");71if (!ctx) throw new Error("Failed to acquire 2D context");7273const blurPx = 4 + 18 * (0.5 + 0.5 * Math.sin((frame / fps) * Math.PI));7475ctx.reset();76ctx.filter = `blur(${blurPx}px)`;77const transform = ctx.drawElementImage(elementImage, 0, 0);78element.style.transform = transform.toString();79},80[frame, fps],81);8283return (84<HtmlInCanvas width={width} height={height} onPaint={onPaint}>85<AbsoluteFill style={{ justifyContent: "center", alignItems: "center", fontSize: 120 }}>86<h1>Hello</h1>87</AbsoluteFill>88</HtmlInCanvas>89);90};91```9293## WebGL effects9495For WebGL, set up the context, program, and texture in `onInit` and return a cleanup function. Inside `onPaint`, upload the captured DOM with `gl.texElementImage2D(...)` and draw.9697```tsx98const onInit: HtmlInCanvasOnInit = useCallback(({ canvas }) => {99const gl = canvas.getContext("webgl2", { alpha: true, premultipliedAlpha: true });100if (!gl) {101throw new Error(102"WebGL2 unavailable. Try rendering with the --gl=angle option. See https://remotion.dev/docs/gl-options.",103);104}105gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);106// compile program, create texture, set up VAO...107return () => {108// delete program, texture, buffers...109};110}, []);111112const onPaint: HtmlInCanvasOnPaint = useCallback(({ elementImage }) => {113gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, elementImage);114gl.drawArrays(gl.TRIANGLES, 0, 6);115}, []);116```117118For a fully working minimal example, see https://github.com/remotion-dev/remotion/blob/main/packages/docs/components/demos/HtmlInCanvasDocsDemoWebGL.tsx.119120## Async `onPaint`121122`onPaint` may be `async`. Remotion holds the frame open via `delayRender()` until the promise resolves. Useful for multi-pass effects with `createImageBitmap`.123