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/effects.md
1---2name: effects3description: Canvas/WebGL visual effects for Remotion using effects arrays and createEffect().4metadata:5tags: effects, visual-effects, webgl, canvas, video, create-effect6---78Use this rule only when the top-level skill lists an effect that matches the requested look, or when the user asks to create a reusable custom effect.910Docs: https://www.remotion.dev/docs/effects11Custom effect docs: https://www.remotion.dev/docs/create-effect1213## Usage1415Install the package that provides the chosen effect:1617```bash18npx remotion add @remotion/effects19```2021Use `npx remotion add @remotion/light-leaks` for `lightLeak()` and `npx remotion add @remotion/starburst` for `starburst()`.2223Effects are functions passed to the `effects` prop of canvas-based components such as `<Video>` from `@remotion/media`, `<Solid>`, `<CanvasImage>`, and `<HtmlInCanvas>`.2425```tsx26import {Video} from '@remotion/media';27import {blur} from '@remotion/effects/blur';2829<Video src="https://remotion.media/video.mp4" effects={[blur({radius: 8})]} />;30```3132Use the effect docs for exact props and imports. Most `@remotion/effects` imports use `@remotion/effects/<effect-slug>`; `uvTranslate()` and `xyTranslate()` use `@remotion/effects/translate`; `lightLeak()` uses `@remotion/light-leaks`; `starburst()` uses `@remotion/starburst`.3334These effects use WebGL2. During renders, enable WebGL with:3536```ts37import {Config} from '@remotion/cli/config';3839Config.setChromiumOpenGlRenderer('angle');40```4142## Custom effects4344Use `createEffect()` from `remotion` when the user wants a reusable effect factory that works in the same `effects` array as `@remotion/effects`.4546Prefer a custom effect over `<HtmlInCanvas onPaint>` when the transformation should be reusable, parameterized, editable in Studio, or stackable with other effects.4748For quick project-specific effects, keep the effect next to the composition, for example `src/effects/palette-map.ts`. For library effects intended for `@remotion/effects`, follow the repository's `add-effect` skill instead.4950`createEffect()` expects:5152- `type`: stable reverse-DNS identifier, for example `com.example.paletteMap`.53- `label`: Studio label, commonly `paletteMap()`.54- `documentationLink`: URL or `null`.55- `backend`: `"2d"`, `"webgl2"` or `"webgpu"`.56- `calculateKey(params)`: stable string containing every resolved parameter that changes output.57- `setup(target)`: create reusable backend state, or return `null`.58- `apply({source, target, width, height, params, state, flipSourceY})`: draw the transformed result into `target`.59- `cleanup(state)`: free resources created by `setup()`.60- `schema`: an `InteractivitySchema` for Studio controls. `disabled` is added automatically.61- `validateParams(params)`: throw on missing or invalid values.6263Use `backend: "2d"` for simple pixel, filter, drawImage, or image-data effects. Use WebGL2 only when shader math or GPU performance is needed; during renders, enable WebGL as shown above.6465```ts66import {createEffect, type InteractivitySchema} from 'remotion';6768type MyEffectParams = {69readonly amount?: number;70};7172const myEffectSchema = {73amount: {74type: 'number',75min: 0,76max: 1,77step: 0.01,78default: 1,79description: 'Amount',80},81} as const satisfies InteractivitySchema;8283const resolve = (params: MyEffectParams) => ({84amount: params.amount ?? 1,85});8687export const myEffect = createEffect<MyEffectParams, null>({88type: 'com.example.myEffect',89label: 'myEffect()',90documentationLink: null,91backend: '2d',92calculateKey: (params) => {93const {amount} = resolve(params);94return `my-effect-${amount}`;95},96setup: () => null,97apply: ({source, target, width, height, params}) => {98const ctx = target.getContext('2d');99if (!ctx) {100throw new Error('Could not get a 2D context for myEffect().');101}102103const {amount} = resolve(params);104105ctx.clearRect(0, 0, width, height);106ctx.filter = `opacity(${amount * 100}%)`;107ctx.drawImage(source, 0, 0, width, height);108ctx.filter = 'none';109},110cleanup: () => undefined,111schema: myEffectSchema,112validateParams: ({amount = 1}) => {113if (typeof amount !== 'number' || !Number.isFinite(amount) || amount < 0 || amount > 1) {114throw new TypeError('amount must be a number between 0 and 1');115}116},117});118```119120For a WebGL2 effect, compile/link shaders in `setup()`, keep the program, fullscreen quad, texture, and uniform locations in state, upload `source` in `apply()`, and free GPU resources in `cleanup()`. Minimal shape:121122```ts123import {createEffect, type InteractivitySchema} from 'remotion';124125type RgbShiftParams = {126readonly amount?: number;127};128129type RgbShiftState = {130readonly gl: WebGL2RenderingContext;131readonly program: WebGLProgram;132readonly vao: WebGLVertexArrayObject;133readonly vbo: WebGLBuffer;134readonly texture: WebGLTexture;135readonly uSource: WebGLUniformLocation | null;136readonly uOffset: WebGLUniformLocation | null;137};138139const rgbShiftSchema = {140amount: {141type: 'number',142min: 0,143max: 80,144step: 1,145default: 12,146description: 'Amount',147},148} as const satisfies InteractivitySchema;149150export const rgbShift = createEffect<RgbShiftParams, RgbShiftState>({151type: 'com.example.rgbShift',152label: 'rgbShift()',153documentationLink: null,154backend: 'webgl2',155calculateKey: ({amount = 12}) => `rgb-shift-${amount}`,156setup: (target) => {157const gl = target.getContext('webgl2', {158premultipliedAlpha: true,159alpha: true,160preserveDrawingBuffer: true,161});162if (!gl) {163throw new Error('Could not get a WebGL2 context for rgbShift().');164}165166gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);167168// Compile/link shaders, create a fullscreen quad VAO/VBO, create a169// CLAMP_TO_EDGE RGBA texture, and get uSource/uOffset uniform locations.170return createRgbShiftState(gl);171},172apply: ({source, width, height, params, state, flipSourceY}) => {173const amount = params.amount ?? 12;174const {gl} = state;175176gl.viewport(0, 0, width, height);177gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipSourceY);178gl.activeTexture(gl.TEXTURE0);179gl.bindTexture(gl.TEXTURE_2D, state.texture);180gl.texImage2D(181gl.TEXTURE_2D,1820,183gl.RGBA,184gl.RGBA,185gl.UNSIGNED_BYTE,186source as TexImageSource,187);188189gl.bindFramebuffer(gl.FRAMEBUFFER, null);190gl.useProgram(state.program);191if (state.uSource) gl.uniform1i(state.uSource, 0);192if (state.uOffset) gl.uniform2f(state.uOffset, amount / width, 0);193gl.bindVertexArray(state.vao);194gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);195},196cleanup: ({gl, program, vao, vbo, texture}) => {197gl.deleteTexture(texture);198gl.deleteBuffer(vbo);199gl.deleteProgram(program);200gl.deleteVertexArray(vao);201},202schema: rgbShiftSchema,203validateParams: ({amount = 12}) => {204if (typeof amount !== 'number' || !Number.isFinite(amount) || amount < 0 || amount > 80) {205throw new TypeError('amount must be a number between 0 and 80');206}207},208});209```210211For a complete 2D and WebGL2 pair, see `packages/example/src/EffectsTestbed/sample-posterize-2d.ts` and `packages/example/src/EffectsTestbed/sample-rgb-shift-webgl.ts`.212213Use the returned factory in an `effects` array:214215```tsx216import {CanvasImage, staticFile} from 'remotion';217import {myEffect} from './effects/my-effect';218219export const MyComp: React.FC = () => {220return (221<CanvasImage222src={staticFile('image.png')}223effects={[myEffect({amount: 0.8})]}224/>225);226};227```228229When generating a custom effect, also:230231- Include `disabled?: boolean` only through the returned factory; do not add it to the custom params type or schema.232- Validate required parameters at factory-call time with `validateParams`.233- Include all defaults in both `schema` and the `resolve()` helper.234- Reset mutable 2D context state such as `filter`, `globalAlpha`, transforms, and compositing after drawing.235- Preserve alpha unless the requested effect intentionally changes transparency.236