Skip to main content

Custom HTML-in-canvas presentationsv4.0.456

Build a presentation that runs the entering and exiting scenes through a shader using the HTML-in-canvas APIs.

warning

HTML-in-canvas requires Chrome Canary with chrome://flags/#canvas-draw-element enabled in the browser.

When to use this

Use this when you cannot implement the presentation in CSS.
Prefer CSS because it has wider browser support.

Concept

Remotion captures both scenes via captureElementImage() and passes them to your shader as two ElementImage handles (prevImage and nextImage) along with the transition time (01). Your shader uploads them as textures via drawElementImage() (or its WebGL/WebGPU equivalents) and writes the blended result to an offscreen WebGL2 canvas, which Remotion then composites into the frame.

You implement a single function — HtmlInCanvasShader<Props> — and pass it to makeHtmlInCanvasPresentation() to get back a TransitionPresentation constructor.

Boilerplate

my-shader.tsx
import type {HtmlInCanvasShader} from '@remotion/transitions'; import {makeHtmlInCanvasPresentation} from '@remotion/transitions'; export type MyShaderProps = { intensity?: number; }; const VERTEX_SHADER = `#version 300 es in vec2 a_pos; out vec2 v_uv; void main() { v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5); gl_Position = vec4(a_pos, 0.0, 1.0); }`; const FRAGMENT_SHADER = `#version 300 es precision highp float; uniform sampler2D u_prev; uniform sampler2D u_next; uniform float u_time; in vec2 v_uv; out vec4 outColor; void main() { // u_time = 1 → fully prev, u_time = 0 → fully next outColor = mix( texture(u_next, v_uv), texture(u_prev, v_uv), u_time ); }`; export const myShader: HtmlInCanvasShader<MyShaderProps> = (canvas) => { const gl = canvas.getContext('webgl2', {premultipliedAlpha: true}); if (!gl) { throw new Error('WebGL2 unavailable'); } // Compile + link program, create textures, bind a fullscreen quad... // (see "Source references" below for a full implementation) return { clear: () => { gl.clearColor(0, 0, 0, 0); gl.clear(gl.COLOR_BUFFER_BIT); }, cleanup: () => { // Release WebGL resources here }, draw: ({prevImage, nextImage, width, height, time, passedProps}) => { // Upload prevImage / nextImage to textures via gl.texElementImage2D // Set uniforms, draw the quad }, }; }; export const myPresentation = makeHtmlInCanvasPresentation(myShader);

Use it like any other presentation:

MyComp.tsx
import {linearTiming, TransitionSeries} from '@remotion/transitions'; import {AbsoluteFill} from 'remotion'; const SceneA: React.FC = () => <AbsoluteFill style={{backgroundColor: '#0b84f3'}} />; const SceneB: React.FC = () => <AbsoluteFill style={{backgroundColor: 'pink'}} />; export const MyComp: React.FC = () => { return ( <TransitionSeries> <TransitionSeries.Sequence durationInFrames={60}> <SceneA /> </TransitionSeries.Sequence> <TransitionSeries.Transition presentation={myPresentation({})} timing={linearTiming({durationInFrames: 30})} /> <TransitionSeries.Sequence durationInFrames={60}> <SceneB /> </TransitionSeries.Sequence> </TransitionSeries> ); };

API reference

The signature of makeHtmlInCanvasPresentation() and the HtmlInCanvasShader<Props> callback contract — clear, cleanup, draw({prevImage, nextImage, width, height, time, passedProps}) — are documented on its own page.

Adapting GLSL transitions from gl-transitions.com

gl-transitions.com is a curated catalog of community-contributed fragment shaders licensed under MIT — a great starting point.

To port one to a Remotion HTML-in-canvas presentation:

Replace getFromColor(uv) with texture(u_prev, uv) and getToColor(uv) with texture(u_next, uv).
2
Map progress to Remotion's reversed convention with float progress = 1.0 - u_time; at the top of main().
3
Wrap the transition() body inside void main() { outColor = transition(v_uv); } (with out vec4 outColor; declared).

Example implementations

See the following example implementations:

See also