` with the given `width` and `height`.
-Do not nest `
` inside another ``. Nesting is not supported and throws:
-
-```
- effects cannot be nested together. Chrome will only display the outer effect. Consider merging the effects into one if you can.
-```
+Nesting `` inside another `` is supported from .
+The inner canvas paints to its own `OffscreenCanvas` and then asks the outer canvas to repaint so it captures the freshly composited bitmap on the next frame.
### `onPaint?`
diff --git a/packages/example/src/HtmlInCanvas/index.tsx b/packages/example/src/HtmlInCanvas/index.tsx
index 2133dc0f9ad..dcb36ecc6d0 100644
--- a/packages/example/src/HtmlInCanvas/index.tsx
+++ b/packages/example/src/HtmlInCanvas/index.tsx
@@ -34,6 +34,7 @@ export {
} from './linear-blur-doc';
export {HtmlInCanvasDocsMinimalWebGL} from './minimal-docs-webgl';
export {HtmlInCanvasDocsMinimalWebGPU} from './minimal-docs-webgpu';
+export {HtmlInCanvasNestedEffects} from './nested-effects';
export {HtmlInCanvasPrivacy} from './privacy';
export {HtmlInCanvasReactSvg} from './react-svg';
export {RippleTransitionDoc, RippleTransitionDocThumb} from './ripple-doc';
diff --git a/packages/example/src/HtmlInCanvas/nested-effects.tsx b/packages/example/src/HtmlInCanvas/nested-effects.tsx
new file mode 100644
index 00000000000..294de0d6fd3
--- /dev/null
+++ b/packages/example/src/HtmlInCanvas/nested-effects.tsx
@@ -0,0 +1,96 @@
+import React, {useCallback} from 'react';
+import {
+ AbsoluteFill,
+ HtmlInCanvas,
+ type HtmlInCanvasOnPaint,
+ useCurrentFrame,
+ useVideoConfig,
+} from 'remotion';
+
+const paintBlur: HtmlInCanvasOnPaint = ({canvas, element, elementImage}) => {
+ const ctx = canvas.getContext('2d');
+ if (!ctx) {
+ throw new Error('Failed to acquire 2D context');
+ }
+ ctx.reset();
+ ctx.filter = 'blur(6px)';
+ const transform = ctx.drawElementImage(elementImage, 0, 0);
+ element.style.transform = transform.toString();
+};
+
+export const HtmlInCanvasNestedEffects: React.FC = () => {
+ const frame = useCurrentFrame();
+ const {width, height} = useVideoConfig();
+
+ const paintRotation: HtmlInCanvasOnPaint = useCallback(
+ ({canvas, element, elementImage}) => {
+ const ctx = canvas.getContext('2d');
+ if (!ctx) {
+ throw new Error('Failed to acquire 2D context');
+ }
+ ctx.reset();
+ const angle = (frame / 60) * Math.PI * 2;
+ ctx.translate(canvas.width / 2, canvas.height / 2);
+ ctx.rotate(angle);
+ ctx.translate(-canvas.width / 2, -canvas.height / 2);
+ const transform = ctx.drawElementImage(elementImage, 0, 0);
+ element.style.transform = transform.toString();
+ },
+ [frame],
+ );
+
+ const paintTint: HtmlInCanvasOnPaint = useCallback(
+ ({canvas, element, elementImage}) => {
+ const ctx = canvas.getContext('2d');
+ if (!ctx) {
+ throw new Error('Failed to acquire 2D context');
+ }
+ ctx.reset();
+ const transform = ctx.drawElementImage(elementImage, 0, 0);
+ element.style.transform = transform.toString();
+ ctx.globalCompositeOperation = 'multiply';
+ ctx.fillStyle = '#00aaff';
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
+ ctx.globalCompositeOperation = 'source-over';
+ },
+ [],
+ );
+
+ return (
+
+
+
+
+
+
+ B
+
+
+
+
+
+
+ );
+};
diff --git a/packages/example/src/Root.tsx b/packages/example/src/Root.tsx
index f7e71a106fe..366909273de 100644
--- a/packages/example/src/Root.tsx
+++ b/packages/example/src/Root.tsx
@@ -67,6 +67,7 @@ import {
HtmlInCanvasDocsDemo2DBlur,
HtmlInCanvasDocsMinimalWebGL,
HtmlInCanvasDocsMinimalWebGPU,
+ HtmlInCanvasNestedEffects,
HtmlInCanvasPrivacy,
HtmlInCanvasReactSvg,
LinearBlurTransitionDoc,
@@ -1033,6 +1034,14 @@ export const Index: React.FC = () => {
width={1920}
durationInFrames={120}
/>
+