Skip to main content

Custom HTML-in-canvas presentationsv4.0.456

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

warning

HTML-in-canvas requires Chrome Canary with chrome://flags/#canvas-draw-element enabled in the browser. For server-side renders, pass --allow-html-in-canvas (or Config.setAllowHtmlInCanvasEnabled(true)). It does not work in Firefox or Safari.

When to use this

Use a custom HTML-in-canvas presentation when:

  • Your effect needs both scenes blended pixel-by-pixel through a fragment shader (zoom blur, dissolves, displacement maps, ripple, page curl, etc.)
  • The CSS-only approach in Custom presentations cannot express the effect

If a CSS clip-path, transform, filter or mask can express the effect, prefer the CSS-based custom presentation. It works in every browser and renderer.

Concept

Remotion captures both scenes via drawElementImage() and passes them to your shader as two ElementImage textures (prevImage and nextImage) along with the transition time (01). Your shader 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