@remotion/transitions 4.0.453 → 4.0.455

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -15,6 +15,7 @@ type SeriesSequenceProps = PropsWithChildren<{
15
15
  readonly stack?: string;
16
16
  } & LayoutBasedProps & Pick<SequencePropsWithoutDuration, 'name'>>;
17
17
  declare const SeriesSequence: ({ children }: SeriesSequenceProps) => import("react/jsx-runtime").JSX.Element;
18
+ export type DrawFunction = (prevImage: ElementImage | null, nextImage: ElementImage | null, progress: number) => void;
18
19
  declare const TransitionSeries: FC<SequencePropsWithoutDuration> & {
19
20
  Sequence: typeof SeriesSequence;
20
21
  Transition: typeof TransitionSeriesTransition;
@@ -22,10 +22,44 @@ const SeriesSequence = ({ children }) => {
22
22
  const TransitionSeriesChildren = ({ children, }) => {
23
23
  const { fps } = (0, remotion_1.useVideoConfig)();
24
24
  const frame = (0, remotion_1.useCurrentFrame)();
25
+ const prevImageRef = (0, react_1.useRef)({});
26
+ const nextImageRef = (0, react_1.useRef)({});
27
+ const flattedChildren = (0, react_1.useMemo)(() => {
28
+ return (0, flatten_children_js_1.flattenChildren)(children);
29
+ }, [children]);
30
+ const drawIfSynced = (0, react_1.useCallback)((index) => {
31
+ var _a, _b, _c, _d, _e, _f, _g, _h;
32
+ var _j, _k, _l, _m, _o, _p;
33
+ const prevImage = (_a = prevImageRef === null || prevImageRef === void 0 ? void 0 : prevImageRef.current) === null || _a === void 0 ? void 0 : _a[index];
34
+ const nextImage = (_b = nextImageRef === null || nextImageRef === void 0 ? void 0 : nextImageRef.current) === null || _b === void 0 ? void 0 : _b[index];
35
+ if (!(nextImage === null || nextImage === void 0 ? void 0 : nextImage.elementImage) && (prevImage === null || prevImage === void 0 ? void 0 : prevImage.elementImage)) {
36
+ (_c = nextImage === null || nextImage === void 0 ? void 0 : nextImage.draw) === null || _c === void 0 ? void 0 : _c.call(nextImage, null, null, 0);
37
+ (_d = prevImage === null || prevImage === void 0 ? void 0 : prevImage.draw) === null || _d === void 0 ? void 0 : _d.call(prevImage, (_j = prevImage === null || prevImage === void 0 ? void 0 : prevImage.elementImage) !== null && _j !== void 0 ? _j : null, null, 0);
38
+ return;
39
+ }
40
+ if (!(prevImage === null || prevImage === void 0 ? void 0 : prevImage.elementImage) && (nextImage === null || nextImage === void 0 ? void 0 : nextImage.elementImage)) {
41
+ (_e = prevImage === null || prevImage === void 0 ? void 0 : prevImage.draw) === null || _e === void 0 ? void 0 : _e.call(prevImage, null, null, 0);
42
+ (_f = nextImage === null || nextImage === void 0 ? void 0 : nextImage.draw) === null || _f === void 0 ? void 0 : _f.call(nextImage, null, (_k = nextImage === null || nextImage === void 0 ? void 0 : nextImage.elementImage) !== null && _k !== void 0 ? _k : null, 0);
43
+ return;
44
+ }
45
+ if ((prevImage && nextImage && prevImage.progress === nextImage.progress) ||
46
+ !(prevImage === null || prevImage === void 0 ? void 0 : prevImage.elementImage) ||
47
+ !(nextImage === null || nextImage === void 0 ? void 0 : nextImage.elementImage)) {
48
+ (_g = prevImage === null || prevImage === void 0 ? void 0 : prevImage.draw) === null || _g === void 0 ? void 0 : _g.call(prevImage, (_l = prevImage === null || prevImage === void 0 ? void 0 : prevImage.elementImage) !== null && _l !== void 0 ? _l : null, (_m = nextImage === null || nextImage === void 0 ? void 0 : nextImage.elementImage) !== null && _m !== void 0 ? _m : null, (_p = (_o = prevImage === null || prevImage === void 0 ? void 0 : prevImage.progress) !== null && _o !== void 0 ? _o : nextImage === null || nextImage === void 0 ? void 0 : nextImage.progress) !== null && _p !== void 0 ? _p : 0);
49
+ (_h = nextImage === null || nextImage === void 0 ? void 0 : nextImage.draw) === null || _h === void 0 ? void 0 : _h.call(nextImage, null, null, 0);
50
+ }
51
+ }, []);
52
+ const onNextElementImage = (0, react_1.useCallback)((elementImage, progress, draw, index) => {
53
+ prevImageRef.current[index] = { elementImage, progress, draw };
54
+ drawIfSynced(index);
55
+ }, [drawIfSynced]);
56
+ const onPrevElementImage = (0, react_1.useCallback)((elementImage, progress, draw, index) => {
57
+ nextImageRef.current[index] = { elementImage, progress, draw };
58
+ drawIfSynced(index);
59
+ }, [drawIfSynced]);
25
60
  const childrenValue = (0, react_1.useMemo)(() => {
26
61
  let transitionOffsets = 0;
27
62
  let startFrame = 0;
28
- const flattedChildren = (0, flatten_children_js_1.flattenChildren)(children);
29
63
  // Collect overlay render info to emit after the main loop
30
64
  const overlayRenders = [];
31
65
  // Track sequence durations for overlay validation
@@ -209,21 +243,35 @@ const TransitionSeriesChildren = ({ children, }) => {
209
243
  const UppercasePrevPresentation = prevPresentation.component;
210
244
  return (jsx_runtime_1.jsx(remotion_1.Sequence
211
245
  // eslint-disable-next-line react/no-array-index-key
212
- , { from: actualStartFrame, durationInFrames: durationInFramesProp, ...passedProps, name: passedProps.name || '<TS.Sequence>', children: jsx_runtime_1.jsx(UppercaseNextPresentation, { passedProps: (_e = nextPresentation.props) !== null && _e !== void 0 ? _e : {}, presentationDirection: "exiting", presentationProgress: nextProgress, presentationDurationInFrames: next.props.timing.getDurationInFrames({ fps }), children: jsx_runtime_1.jsx(context_js_1.WrapInExitingProgressContext, { presentationProgress: nextProgress, children: jsx_runtime_1.jsx(UppercasePrevPresentation, { passedProps: (_f = prevPresentation.props) !== null && _f !== void 0 ? _f : {}, presentationDirection: "entering", presentationProgress: prevProgress, presentationDurationInFrames: prev.props.timing.getDurationInFrames({ fps }), children: jsx_runtime_1.jsx(context_js_1.WrapInEnteringProgressContext, { presentationProgress: prevProgress, children: child }) }) }) }) }, i));
246
+ , { from: actualStartFrame, durationInFrames: durationInFramesProp, ...passedProps, name: passedProps.name || '<TS.Sequence>', children: jsx_runtime_1.jsx(UppercaseNextPresentation, { passedProps: (_e = nextPresentation.props) !== null && _e !== void 0 ? _e : {}, presentationDirection: "exiting", presentationProgress: nextProgress, presentationDurationInFrames: next.props.timing.getDurationInFrames({ fps }), onElementImage: () => {
247
+ throw new Error('Should not call when exiting');
248
+ }, onUnmount: () => {
249
+ throw new Error('Should not call when exiting');
250
+ }, bothEnteringAndExiting: true, children: jsx_runtime_1.jsx(context_js_1.WrapInExitingProgressContext, { presentationProgress: nextProgress, children: jsx_runtime_1.jsx(UppercasePrevPresentation, { passedProps: (_f = prevPresentation.props) !== null && _f !== void 0 ? _f : {}, presentationDirection: "entering", presentationProgress: prevProgress, presentationDurationInFrames: prev.props.timing.getDurationInFrames({ fps }), onElementImage: (elementImage, draw) => {
251
+ onPrevElementImage(elementImage, nextProgress, draw, i + 1);
252
+ onNextElementImage(elementImage, prevProgress, draw, i - 1);
253
+ }, onUnmount: () => {
254
+ onPrevElementImage(null, null, null, i + 1);
255
+ onNextElementImage(null, null, null, i - 1);
256
+ }, bothEnteringAndExiting: true, children: jsx_runtime_1.jsx(context_js_1.WrapInEnteringProgressContext, { presentationProgress: prevProgress, children: child }) }) }) }) }, i));
213
257
  }
214
258
  if (prevProgress !== null && prev) {
215
259
  const prevPresentation = (_g = prev.props.presentation) !== null && _g !== void 0 ? _g : (0, slide_js_1.slide)();
216
260
  const UppercasePrevPresentation = prevPresentation.component;
217
261
  return (jsx_runtime_1.jsx(remotion_1.Sequence
218
262
  // eslint-disable-next-line react/no-array-index-key
219
- , { from: actualStartFrame, durationInFrames: durationInFramesProp, ...passedProps, name: passedProps.name || '<TS.Sequence>', children: jsx_runtime_1.jsx(UppercasePrevPresentation, { passedProps: (_h = prevPresentation.props) !== null && _h !== void 0 ? _h : {}, presentationDirection: "entering", presentationProgress: prevProgress, presentationDurationInFrames: prev.props.timing.getDurationInFrames({ fps }), children: jsx_runtime_1.jsx(context_js_1.WrapInEnteringProgressContext, { presentationProgress: prevProgress, children: child }) }) }, i));
263
+ , { from: actualStartFrame, durationInFrames: durationInFramesProp, ...passedProps, name: passedProps.name || '<TS.Sequence>', children: jsx_runtime_1.jsx(UppercasePrevPresentation, { passedProps: (_h = prevPresentation.props) !== null && _h !== void 0 ? _h : {}, presentationDirection: "entering", presentationProgress: prevProgress, presentationDurationInFrames: prev.props.timing.getDurationInFrames({ fps }), onElementImage: (elementImage, draw) => onNextElementImage(elementImage, prevProgress, draw, i - 1), onUnmount: () => {
264
+ onNextElementImage(null, null, null, i - 1);
265
+ }, bothEnteringAndExiting: false, children: jsx_runtime_1.jsx(context_js_1.WrapInEnteringProgressContext, { presentationProgress: prevProgress, children: child }) }) }, i));
220
266
  }
221
267
  if (nextProgress !== null && next) {
222
268
  const nextPresentation = (_j = next.props.presentation) !== null && _j !== void 0 ? _j : (0, slide_js_1.slide)();
223
269
  const UppercaseNextPresentation = nextPresentation.component;
224
270
  return (jsx_runtime_1.jsx(remotion_1.Sequence
225
271
  // eslint-disable-next-line react/no-array-index-key
226
- , { from: actualStartFrame, durationInFrames: durationInFramesProp, ...passedProps, name: passedProps.name || '<TS.Sequence>', children: jsx_runtime_1.jsx(UppercaseNextPresentation, { passedProps: (_k = nextPresentation.props) !== null && _k !== void 0 ? _k : {}, presentationDirection: "exiting", presentationProgress: nextProgress, presentationDurationInFrames: next.props.timing.getDurationInFrames({ fps }), children: jsx_runtime_1.jsx(context_js_1.WrapInExitingProgressContext, { presentationProgress: nextProgress, children: child }) }) }, i));
272
+ , { from: actualStartFrame, durationInFrames: durationInFramesProp, ...passedProps, name: passedProps.name || '<TS.Sequence>', children: jsx_runtime_1.jsx(UppercaseNextPresentation, { passedProps: (_k = nextPresentation.props) !== null && _k !== void 0 ? _k : {}, presentationDirection: "exiting", presentationProgress: nextProgress, presentationDurationInFrames: next.props.timing.getDurationInFrames({ fps }), onElementImage: (elementImage, draw) => onPrevElementImage(elementImage, nextProgress, draw, i + 1), onUnmount: () => {
273
+ onPrevElementImage(null, null, null, i + 1);
274
+ }, bothEnteringAndExiting: false, children: jsx_runtime_1.jsx(context_js_1.WrapInExitingProgressContext, { presentationProgress: nextProgress, children: child }) }) }, i));
227
275
  }
228
276
  return (jsx_runtime_1.jsx(remotion_1.Sequence
229
277
  // eslint-disable-next-line react/no-array-index-key
@@ -235,7 +283,7 @@ const TransitionSeriesChildren = ({ children, }) => {
235
283
  return (jsx_runtime_1.jsx(remotion_1.Sequence, { from: Math.round(info.overlayFrom), durationInFrames: info.durationInFrames, name: "<TS.Overlay>", layout: "absolute-fill", ...(info.stack ? { stack: info.stack } : {}), children: info.children }, `overlay-${info.index}`));
236
284
  });
237
285
  return [...(mainChildren || []), ...overlayElements];
238
- }, [children, fps, frame]);
286
+ }, [flattedChildren, fps, frame, onPrevElementImage, onNextElementImage]);
239
287
  // eslint-disable-next-line react/jsx-no-useless-fragment
240
288
  return jsx_runtime_1.jsx(jsx_runtime_1.Fragment, { children: childrenValue });
241
289
  };
@@ -123,7 +123,7 @@ var springTiming = (options = {}) => {
123
123
  };
124
124
  };
125
125
  // src/TransitionSeries.tsx
126
- import { Children, useMemo as useMemo3 } from "react";
126
+ import { Children, useCallback, useMemo as useMemo3, useRef } from "react";
127
127
  import { Internals, Sequence, useCurrentFrame, useVideoConfig } from "remotion";
128
128
  import { NoReactInternals as NoReactInternals2 } from "remotion/no-react";
129
129
 
@@ -190,10 +190,40 @@ var TransitionSeriesChildren = ({
190
190
  }) => {
191
191
  const { fps } = useVideoConfig();
192
192
  const frame = useCurrentFrame();
193
+ const prevImageRef = useRef({});
194
+ const nextImageRef = useRef({});
195
+ const flattedChildren = useMemo3(() => {
196
+ return flattenChildren(children);
197
+ }, [children]);
198
+ const drawIfSynced = useCallback((index) => {
199
+ const prevImage = prevImageRef?.current?.[index];
200
+ const nextImage = nextImageRef?.current?.[index];
201
+ if (!nextImage?.elementImage && prevImage?.elementImage) {
202
+ nextImage?.draw?.(null, null, 0);
203
+ prevImage?.draw?.(prevImage?.elementImage ?? null, null, 0);
204
+ return;
205
+ }
206
+ if (!prevImage?.elementImage && nextImage?.elementImage) {
207
+ prevImage?.draw?.(null, null, 0);
208
+ nextImage?.draw?.(null, nextImage?.elementImage ?? null, 0);
209
+ return;
210
+ }
211
+ if (prevImage && nextImage && prevImage.progress === nextImage.progress || !prevImage?.elementImage || !nextImage?.elementImage) {
212
+ prevImage?.draw?.(prevImage?.elementImage ?? null, nextImage?.elementImage ?? null, prevImage?.progress ?? nextImage?.progress ?? 0);
213
+ nextImage?.draw?.(null, null, 0);
214
+ }
215
+ }, []);
216
+ const onNextElementImage = useCallback((elementImage, progress, draw, index) => {
217
+ prevImageRef.current[index] = { elementImage, progress, draw };
218
+ drawIfSynced(index);
219
+ }, [drawIfSynced]);
220
+ const onPrevElementImage = useCallback((elementImage, progress, draw, index) => {
221
+ nextImageRef.current[index] = { elementImage, progress, draw };
222
+ drawIfSynced(index);
223
+ }, [drawIfSynced]);
193
224
  const childrenValue = useMemo3(() => {
194
225
  let transitionOffsets = 0;
195
226
  let startFrame = 0;
196
- const flattedChildren = flattenChildren(children);
197
227
  const overlayRenders = [];
198
228
  const sequenceDurations = [];
199
229
  let pendingOverlayValidation = false;
@@ -352,6 +382,13 @@ var TransitionSeriesChildren = ({
352
382
  presentationDirection: "exiting",
353
383
  presentationProgress: nextProgress,
354
384
  presentationDurationInFrames: next.props.timing.getDurationInFrames({ fps }),
385
+ onElementImage: () => {
386
+ throw new Error("Should not call when exiting");
387
+ },
388
+ onUnmount: () => {
389
+ throw new Error("Should not call when exiting");
390
+ },
391
+ bothEnteringAndExiting: true,
355
392
  children: /* @__PURE__ */ jsx3(WrapInExitingProgressContext, {
356
393
  presentationProgress: nextProgress,
357
394
  children: /* @__PURE__ */ jsx3(UppercasePrevPresentation, {
@@ -359,6 +396,15 @@ var TransitionSeriesChildren = ({
359
396
  presentationDirection: "entering",
360
397
  presentationProgress: prevProgress,
361
398
  presentationDurationInFrames: prev.props.timing.getDurationInFrames({ fps }),
399
+ onElementImage: (elementImage, draw) => {
400
+ onPrevElementImage(elementImage, nextProgress, draw, i + 1);
401
+ onNextElementImage(elementImage, prevProgress, draw, i - 1);
402
+ },
403
+ onUnmount: () => {
404
+ onPrevElementImage(null, null, null, i + 1);
405
+ onNextElementImage(null, null, null, i - 1);
406
+ },
407
+ bothEnteringAndExiting: true,
362
408
  children: /* @__PURE__ */ jsx3(WrapInEnteringProgressContext, {
363
409
  presentationProgress: prevProgress,
364
410
  children: child
@@ -381,6 +427,11 @@ var TransitionSeriesChildren = ({
381
427
  presentationDirection: "entering",
382
428
  presentationProgress: prevProgress,
383
429
  presentationDurationInFrames: prev.props.timing.getDurationInFrames({ fps }),
430
+ onElementImage: (elementImage, draw) => onNextElementImage(elementImage, prevProgress, draw, i - 1),
431
+ onUnmount: () => {
432
+ onNextElementImage(null, null, null, i - 1);
433
+ },
434
+ bothEnteringAndExiting: false,
384
435
  children: /* @__PURE__ */ jsx3(WrapInEnteringProgressContext, {
385
436
  presentationProgress: prevProgress,
386
437
  children: child
@@ -401,6 +452,11 @@ var TransitionSeriesChildren = ({
401
452
  presentationDirection: "exiting",
402
453
  presentationProgress: nextProgress,
403
454
  presentationDurationInFrames: next.props.timing.getDurationInFrames({ fps }),
455
+ onElementImage: (elementImage, draw) => onPrevElementImage(elementImage, nextProgress, draw, i + 1),
456
+ onUnmount: () => {
457
+ onPrevElementImage(null, null, null, i + 1);
458
+ },
459
+ bothEnteringAndExiting: false,
404
460
  children: /* @__PURE__ */ jsx3(WrapInExitingProgressContext, {
405
461
  presentationProgress: nextProgress,
406
462
  children: child
@@ -428,7 +484,7 @@ var TransitionSeriesChildren = ({
428
484
  }, `overlay-${info.index}`);
429
485
  });
430
486
  return [...mainChildren || [], ...overlayElements];
431
- }, [children, fps, frame]);
487
+ }, [flattedChildren, fps, frame, onPrevElementImage, onNextElementImage]);
432
488
  return /* @__PURE__ */ jsx3(Fragment, {
433
489
  children: childrenValue
434
490
  });
@@ -455,10 +511,10 @@ Internals.addSequenceStackTraces(TransitionSeries);
455
511
  Internals.addSequenceStackTraces(SeriesSequence);
456
512
  Internals.addSequenceStackTraces(SeriesOverlay);
457
513
  // src/use-transition-progress.ts
458
- import React4 from "react";
514
+ import React5 from "react";
459
515
  var useTransitionProgress = () => {
460
- const entering = React4.useContext(EnteringContext);
461
- const exiting = React4.useContext(ExitingContext);
516
+ const entering = React5.useContext(EnteringContext);
517
+ const exiting = React5.useContext(ExitingContext);
462
518
  if (!entering && !exiting) {
463
519
  return {
464
520
  isInTransitionSeries: false,
@@ -0,0 +1,395 @@
1
+ // src/html-in-canvas-presentation.tsx
2
+ import { useLayoutEffect, useMemo, useRef, useState, useCallback } from "react";
3
+ import { HtmlInCanvas, useDelayRender } from "remotion";
4
+ import { AbsoluteFill, Internals, useCurrentFrame } from "remotion";
5
+ import { jsx } from "react/jsx-runtime";
6
+ var HtmlInCanvasPresentation = ({
7
+ children,
8
+ onElementImage,
9
+ onUnmount,
10
+ presentationProgress,
11
+ presentationDirection,
12
+ shader,
13
+ _experimentalEffects,
14
+ passedProps,
15
+ bothEnteringAndExiting
16
+ }) => {
17
+ if (!HtmlInCanvas.isHtmlInCanvasSupported()) {
18
+ throw new Error("HTML in Canvas is not supported. Open this page in Chrome Canary with chrome://flags/#canvas-draw-element enabled.");
19
+ }
20
+ const canvasRef = useRef(null);
21
+ const canvasSubtreeStyle = useMemo(() => {
22
+ return {
23
+ width: "100%",
24
+ height: "100%",
25
+ position: "absolute",
26
+ top: 0,
27
+ left: 0,
28
+ right: 0,
29
+ bottom: 0
30
+ };
31
+ }, []);
32
+ const [offscreenCanvas] = useState(() => new OffscreenCanvas(1, 1));
33
+ const passedPropsRef = useRef(passedProps);
34
+ passedPropsRef.current = passedProps;
35
+ const frame = useCurrentFrame();
36
+ const frameRef = useRef(frame);
37
+ frameRef.current = frame;
38
+ const effectsRef = useRef(_experimentalEffects);
39
+ effectsRef.current = _experimentalEffects;
40
+ const [instance] = useState(() => shader());
41
+ useLayoutEffect(() => {
42
+ instance.init(offscreenCanvas);
43
+ return () => {
44
+ instance.cleanup();
45
+ };
46
+ }, [offscreenCanvas, instance]);
47
+ const chainState = Internals.useEffectChainState();
48
+ const { delayRender, continueRender } = useDelayRender();
49
+ const draw = useCallback(async (prevImage, nextImage, progress) => {
50
+ if (!canvasRef.current) {
51
+ throw new Error("Canvas not found");
52
+ }
53
+ const handle = delayRender("onPaint");
54
+ if (!prevImage && !nextImage) {
55
+ continueRender(handle);
56
+ instance.clear();
57
+ return;
58
+ }
59
+ const width = prevImage?.width ?? nextImage?.width ?? 0;
60
+ const height = prevImage?.height ?? nextImage?.height ?? 0;
61
+ if (width === 0 || height === 0) {
62
+ continueRender(handle);
63
+ instance.clear();
64
+ return;
65
+ }
66
+ offscreenCanvas.width = width;
67
+ offscreenCanvas.height = height;
68
+ instance.draw({
69
+ prevImage,
70
+ nextImage,
71
+ width,
72
+ height,
73
+ time: progress,
74
+ passedProps: passedPropsRef.current
75
+ });
76
+ await Internals.runEffectChain({
77
+ state: chainState.get(width, height),
78
+ source: offscreenCanvas,
79
+ effects: effectsRef.current ?? [],
80
+ frame: frameRef.current,
81
+ width,
82
+ height,
83
+ output: canvasRef.current
84
+ });
85
+ continueRender(handle);
86
+ }, [chainState, instance, offscreenCanvas, continueRender, delayRender]);
87
+ const passThrough = bothEnteringAndExiting && presentationDirection === "exiting";
88
+ useLayoutEffect(() => {
89
+ if (passThrough) {
90
+ return;
91
+ }
92
+ const canvas = canvasRef.current;
93
+ if (!canvas) {
94
+ throw new Error("Canvas not found");
95
+ }
96
+ canvas.layoutSubtree = true;
97
+ const onPaint = () => {
98
+ const firstChild = canvas.firstChild;
99
+ if (!firstChild) {
100
+ return;
101
+ }
102
+ const elementImage = canvas.captureElementImage(firstChild);
103
+ onElementImage(elementImage, draw);
104
+ };
105
+ canvas.addEventListener("paint", onPaint);
106
+ return () => {
107
+ canvas.removeEventListener("paint", onPaint);
108
+ };
109
+ }, [onElementImage, presentationDirection, draw, passThrough]);
110
+ useLayoutEffect(() => {
111
+ if (passThrough) {
112
+ return;
113
+ }
114
+ const canvas = canvasRef.current;
115
+ if (!canvas) {
116
+ throw new Error("Canvas not found");
117
+ }
118
+ canvas.requestPaint?.();
119
+ }, [presentationProgress, passThrough]);
120
+ useLayoutEffect(() => {
121
+ if (passThrough) {
122
+ return;
123
+ }
124
+ return () => {
125
+ onUnmount();
126
+ };
127
+ }, [onUnmount, passThrough]);
128
+ useLayoutEffect(() => {
129
+ if (passThrough) {
130
+ return;
131
+ }
132
+ const canvas = canvasRef.current;
133
+ if (!canvas) {
134
+ return;
135
+ }
136
+ const observer = new ResizeObserver(([entry]) => {
137
+ canvas.width = entry.devicePixelContentBoxSize[0].inlineSize;
138
+ canvas.height = entry.devicePixelContentBoxSize[0].blockSize;
139
+ });
140
+ observer.observe(canvas, { box: "device-pixel-content-box" });
141
+ }, [passThrough]);
142
+ if (passThrough) {
143
+ return children;
144
+ }
145
+ return /* @__PURE__ */ jsx(AbsoluteFill, {
146
+ children: /* @__PURE__ */ jsx("canvas", {
147
+ ref: canvasRef,
148
+ style: canvasSubtreeStyle,
149
+ children
150
+ })
151
+ });
152
+ };
153
+ var makeHtmlInCanvasPresentation = (shader) => {
154
+ const CompWithShader = (props) => {
155
+ const { passedProps, ...otherProps } = props;
156
+ const { _experimentalEffects, ...restPassedProps } = props.passedProps;
157
+ return /* @__PURE__ */ jsx(HtmlInCanvasPresentation, {
158
+ shader,
159
+ passedProps: restPassedProps,
160
+ _experimentalEffects,
161
+ ...otherProps
162
+ });
163
+ };
164
+ return (props) => {
165
+ return {
166
+ component: CompWithShader,
167
+ props
168
+ };
169
+ };
170
+ };
171
+
172
+ // src/presentations/zoom-blur.tsx
173
+ var VERTEX_SHADER = `#version 300 es
174
+ in vec2 a_pos;
175
+ out vec2 v_uv;
176
+ void main() {
177
+ v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);
178
+ gl_Position = vec4(a_pos, 0.0, 1.0);
179
+ }`;
180
+ var FRAGMENT_SHADER = `#version 300 es
181
+ precision highp float;
182
+
183
+ uniform sampler2D u_prev;
184
+ uniform sampler2D u_next;
185
+ uniform float u_time;
186
+ uniform float u_aspect;
187
+ uniform float u_max_angle;
188
+
189
+ in vec2 v_uv;
190
+ out vec4 outColor;
191
+
192
+ const int SAMPLES = 16;
193
+ const float STRENGTH = 0.35;
194
+
195
+ vec2 transformUV(vec2 uv, float angle, float scale) {
196
+ vec2 p = uv - 0.5;
197
+ p.x *= u_aspect;
198
+ p /= scale;
199
+ float c = cos(-angle);
200
+ float s = sin(-angle);
201
+ p = vec2(p.x * c - p.y * s, p.x * s + p.y * c);
202
+ p.x /= u_aspect;
203
+ return p + 0.5;
204
+ }
205
+
206
+ float coverScale(float angle) {
207
+ float c = abs(cos(angle));
208
+ float s = abs(sin(angle));
209
+ float ar = max(u_aspect, 1.0 / u_aspect);
210
+ return c + ar * s;
211
+ }
212
+
213
+ vec4 zoomBlur(sampler2D tex, vec2 uv, float strength) {
214
+ vec2 dir = uv - 0.5;
215
+ vec4 acc = vec4(0.0);
216
+ for (int i = 0; i < SAMPLES; i++) {
217
+ float t = float(i) / float(SAMPLES - 1);
218
+ float scale = 1.0 - strength * t;
219
+ acc += texture(tex, 0.5 + dir * scale);
220
+ }
221
+ return acc / float(SAMPLES);
222
+ }
223
+
224
+ void main() {
225
+ float mixT = u_time;
226
+
227
+ float nextAngle = u_max_angle * mixT;
228
+ float prevAngle = -u_max_angle * (1.0 - mixT);
229
+
230
+ vec2 prevUV = transformUV(v_uv, prevAngle, coverScale(prevAngle));
231
+ vec2 nextUV = transformUV(v_uv, nextAngle, coverScale(nextAngle));
232
+
233
+ vec4 prev = zoomBlur(u_prev, prevUV, STRENGTH * (1.0 - mixT));
234
+ vec4 next = zoomBlur(u_next, nextUV, STRENGTH * mixT);
235
+ outColor = mix(prev, next, (1.0 - mixT));
236
+ }`;
237
+ var compileShader = (gl, source, type) => {
238
+ const shader = gl.createShader(type);
239
+ if (!shader) {
240
+ throw new Error("Failed to create shader");
241
+ }
242
+ gl.shaderSource(shader, source);
243
+ gl.compileShader(shader);
244
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
245
+ const log = gl.getShaderInfoLog(shader);
246
+ gl.deleteShader(shader);
247
+ throw new Error(`Failed to compile shader: ${log}`);
248
+ }
249
+ return shader;
250
+ };
251
+ var createProgram = (gl) => {
252
+ const program = gl.createProgram();
253
+ if (!program) {
254
+ throw new Error("Failed to create WebGL program");
255
+ }
256
+ const vs = compileShader(gl, VERTEX_SHADER, gl.VERTEX_SHADER);
257
+ const fs = compileShader(gl, FRAGMENT_SHADER, gl.FRAGMENT_SHADER);
258
+ gl.attachShader(program, vs);
259
+ gl.attachShader(program, fs);
260
+ gl.linkProgram(program);
261
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
262
+ const log = gl.getProgramInfoLog(program);
263
+ gl.deleteProgram(program);
264
+ throw new Error(`Failed to link program: ${log}`);
265
+ }
266
+ return program;
267
+ };
268
+ var createTexture = (gl) => {
269
+ const tex = gl.createTexture();
270
+ if (!tex) {
271
+ throw new Error("Failed to create texture");
272
+ }
273
+ gl.bindTexture(gl.TEXTURE_2D, tex);
274
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
275
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
276
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
277
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
278
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]));
279
+ return tex;
280
+ };
281
+ var zoomBlurShader = () => {
282
+ let state = null;
283
+ const init = (canvas) => {
284
+ const gl = canvas.getContext("webgl2", { premultipliedAlpha: true });
285
+ if (!gl) {
286
+ return () => {};
287
+ }
288
+ const program = createProgram(gl);
289
+ const prevTex = createTexture(gl);
290
+ const nextTex = createTexture(gl);
291
+ const vao = gl.createVertexArray();
292
+ gl.bindVertexArray(vao);
293
+ const buffer = gl.createBuffer();
294
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
295
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
296
+ const aPos = gl.getAttribLocation(program, "a_pos");
297
+ gl.enableVertexAttribArray(aPos);
298
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
299
+ state = {
300
+ gl,
301
+ program,
302
+ prevTex,
303
+ nextTex,
304
+ uTime: gl.getUniformLocation(program, "u_time"),
305
+ uPrev: gl.getUniformLocation(program, "u_prev"),
306
+ uNext: gl.getUniformLocation(program, "u_next"),
307
+ uAspect: gl.getUniformLocation(program, "u_aspect"),
308
+ uMaxAngle: gl.getUniformLocation(program, "u_max_angle")
309
+ };
310
+ return () => {};
311
+ };
312
+ const cleanup = () => {
313
+ if (!state) {
314
+ throw new Error("Zoom blur state not initialized");
315
+ }
316
+ const { gl, program, prevTex, nextTex } = state;
317
+ gl.deleteProgram(program);
318
+ gl.deleteTexture(prevTex);
319
+ gl.deleteTexture(nextTex);
320
+ state = null;
321
+ };
322
+ const clear = () => {
323
+ if (!state) {
324
+ throw new Error("Zoom blur state not initialized");
325
+ }
326
+ const { gl } = state;
327
+ gl.clearColor(0, 0, 0, 0);
328
+ gl.clear(gl.COLOR_BUFFER_BIT);
329
+ };
330
+ const draw = ({
331
+ prevImage,
332
+ nextImage,
333
+ width,
334
+ height,
335
+ time,
336
+ passedProps
337
+ }) => {
338
+ if (!state) {
339
+ throw new Error("Zoom blur state not initialized");
340
+ }
341
+ const { rotation = Math.PI / 6 } = passedProps;
342
+ const {
343
+ gl,
344
+ program,
345
+ prevTex,
346
+ nextTex,
347
+ uTime,
348
+ uPrev,
349
+ uNext,
350
+ uAspect,
351
+ uMaxAngle
352
+ } = state;
353
+ if (!prevImage && !nextImage) {
354
+ return;
355
+ }
356
+ if (prevImage && (prevImage.width === 0 || prevImage.height === 0)) {
357
+ return;
358
+ }
359
+ if (nextImage && (nextImage.width === 0 || nextImage.height === 0)) {
360
+ return;
361
+ }
362
+ const effectiveTime = !prevImage ? 0 : !nextImage ? 1 : time;
363
+ gl.viewport(0, 0, width, height);
364
+ gl.clearColor(0, 0, 0, 0);
365
+ gl.clear(gl.COLOR_BUFFER_BIT);
366
+ gl.useProgram(program);
367
+ gl.activeTexture(gl.TEXTURE0);
368
+ gl.bindTexture(gl.TEXTURE_2D, prevTex);
369
+ if (prevImage) {
370
+ gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, prevImage);
371
+ }
372
+ gl.uniform1i(uPrev, 0);
373
+ gl.activeTexture(gl.TEXTURE1);
374
+ gl.bindTexture(gl.TEXTURE_2D, nextTex);
375
+ if (nextImage) {
376
+ gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, nextImage);
377
+ }
378
+ gl.uniform1i(uNext, 1);
379
+ gl.uniform1f(uTime, effectiveTime);
380
+ gl.uniform1f(uAspect, width / height);
381
+ gl.uniform1f(uMaxAngle, rotation);
382
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
383
+ };
384
+ return {
385
+ init,
386
+ clear,
387
+ draw,
388
+ cleanup
389
+ };
390
+ };
391
+ var zoomBlur = makeHtmlInCanvasPresentation(zoomBlurShader);
392
+ export {
393
+ zoomBlurShader,
394
+ zoomBlur
395
+ };
@@ -0,0 +1,24 @@
1
+ import { type EffectsProp } from 'remotion';
2
+ import type { TransitionPresentation, TransitionPresentationComponentProps } from './types';
3
+ export declare const HtmlInCanvasPresentation: <TPassedProps extends Record<string, unknown>>({ children, onElementImage, onUnmount, presentationProgress, presentationDirection, shader, _experimentalEffects, passedProps, bothEnteringAndExiting, }: TransitionPresentationComponentProps<TPassedProps> & {
4
+ readonly shader: () => HtmlInCanvasShader<TPassedProps>;
5
+ readonly _experimentalEffects?: EffectsProp | undefined;
6
+ }) => string | number | bigint | boolean | import("react/jsx-runtime").JSX.Element | Iterable<import("react").ReactNode> | Promise<string | number | bigint | boolean | Iterable<import("react").ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null | undefined;
7
+ export type HtmlInCanvasShader<TPassedProps> = {
8
+ init: (canvas: OffscreenCanvas) => void;
9
+ clear: () => void;
10
+ draw: (params: {
11
+ prevImage: ElementImage | null;
12
+ nextImage: ElementImage | null;
13
+ width: number;
14
+ height: number;
15
+ time: number;
16
+ passedProps: TPassedProps;
17
+ }) => void;
18
+ cleanup: () => void;
19
+ };
20
+ export declare const makeHtmlInCanvasPresentation: <TPassedProps extends Record<string, unknown>>(shader: () => HtmlInCanvasShader<TPassedProps>) => (props: TPassedProps & {
21
+ _experimentalEffects?: EffectsProp | undefined;
22
+ }) => TransitionPresentation<TPassedProps & {
23
+ _experimentalEffects?: EffectsProp | undefined;
24
+ }>;
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeHtmlInCanvasPresentation = exports.HtmlInCanvasPresentation = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const remotion_1 = require("remotion");
7
+ const remotion_2 = require("remotion");
8
+ const HtmlInCanvasPresentation = ({ children, onElementImage, onUnmount, presentationProgress, presentationDirection, shader, _experimentalEffects, passedProps, bothEnteringAndExiting, }) => {
9
+ if (!remotion_1.HtmlInCanvas.isHtmlInCanvasSupported()) {
10
+ throw new Error('HTML in Canvas is not supported. Open this page in Chrome Canary with chrome://flags/#canvas-draw-element enabled.');
11
+ }
12
+ const canvasRef = (0, react_1.useRef)(null);
13
+ const canvasSubtreeStyle = (0, react_1.useMemo)(() => {
14
+ return {
15
+ width: '100%',
16
+ height: '100%',
17
+ position: 'absolute',
18
+ top: 0,
19
+ left: 0,
20
+ right: 0,
21
+ bottom: 0,
22
+ };
23
+ }, []);
24
+ const [offscreenCanvas] = (0, react_1.useState)(() => new OffscreenCanvas(1, 1));
25
+ const passedPropsRef = (0, react_1.useRef)(passedProps);
26
+ passedPropsRef.current = passedProps;
27
+ const frame = (0, remotion_2.useCurrentFrame)();
28
+ const frameRef = (0, react_1.useRef)(frame);
29
+ frameRef.current = frame;
30
+ const effectsRef = (0, react_1.useRef)(_experimentalEffects);
31
+ effectsRef.current = _experimentalEffects;
32
+ const [instance] = (0, react_1.useState)(() => shader());
33
+ (0, react_1.useLayoutEffect)(() => {
34
+ instance.init(offscreenCanvas);
35
+ return () => {
36
+ instance.cleanup();
37
+ };
38
+ }, [offscreenCanvas, instance]);
39
+ const chainState = remotion_2.Internals.useEffectChainState();
40
+ const { delayRender, continueRender } = (0, remotion_1.useDelayRender)();
41
+ const draw = (0, react_1.useCallback)(async (prevImage, nextImage, progress) => {
42
+ var _a, _b, _c, _d, _e;
43
+ if (!canvasRef.current) {
44
+ throw new Error('Canvas not found');
45
+ }
46
+ const handle = delayRender('onPaint');
47
+ if (!prevImage && !nextImage) {
48
+ continueRender(handle);
49
+ instance.clear();
50
+ return;
51
+ }
52
+ const width = (_b = (_a = prevImage === null || prevImage === void 0 ? void 0 : prevImage.width) !== null && _a !== void 0 ? _a : nextImage === null || nextImage === void 0 ? void 0 : nextImage.width) !== null && _b !== void 0 ? _b : 0;
53
+ const height = (_d = (_c = prevImage === null || prevImage === void 0 ? void 0 : prevImage.height) !== null && _c !== void 0 ? _c : nextImage === null || nextImage === void 0 ? void 0 : nextImage.height) !== null && _d !== void 0 ? _d : 0;
54
+ if (width === 0 || height === 0) {
55
+ continueRender(handle);
56
+ instance.clear();
57
+ return;
58
+ }
59
+ offscreenCanvas.width = width;
60
+ offscreenCanvas.height = height;
61
+ instance.draw({
62
+ prevImage,
63
+ nextImage,
64
+ width,
65
+ height,
66
+ time: progress,
67
+ passedProps: passedPropsRef.current,
68
+ });
69
+ await remotion_2.Internals.runEffectChain({
70
+ state: chainState.get(width, height),
71
+ source: offscreenCanvas,
72
+ effects: (_e = effectsRef.current) !== null && _e !== void 0 ? _e : [],
73
+ frame: frameRef.current,
74
+ width,
75
+ height,
76
+ output: canvasRef.current,
77
+ });
78
+ continueRender(handle);
79
+ }, [chainState, instance, offscreenCanvas, continueRender, delayRender]);
80
+ const passThrough = bothEnteringAndExiting && presentationDirection === 'exiting';
81
+ (0, react_1.useLayoutEffect)(() => {
82
+ if (passThrough) {
83
+ return;
84
+ }
85
+ const canvas = canvasRef.current;
86
+ if (!canvas) {
87
+ throw new Error('Canvas not found');
88
+ }
89
+ canvas.layoutSubtree = true;
90
+ const onPaint = () => {
91
+ const firstChild = canvas.firstChild;
92
+ if (!firstChild) {
93
+ return;
94
+ }
95
+ const elementImage = canvas.captureElementImage(firstChild);
96
+ onElementImage(elementImage, draw);
97
+ };
98
+ canvas.addEventListener('paint', onPaint);
99
+ return () => {
100
+ canvas.removeEventListener('paint', onPaint);
101
+ };
102
+ }, [onElementImage, presentationDirection, draw, passThrough]);
103
+ (0, react_1.useLayoutEffect)(() => {
104
+ var _a;
105
+ if (passThrough) {
106
+ return;
107
+ }
108
+ const canvas = canvasRef.current;
109
+ if (!canvas) {
110
+ throw new Error('Canvas not found');
111
+ }
112
+ (_a = canvas.requestPaint) === null || _a === void 0 ? void 0 : _a.call(canvas);
113
+ }, [presentationProgress, passThrough]);
114
+ (0, react_1.useLayoutEffect)(() => {
115
+ if (passThrough) {
116
+ return;
117
+ }
118
+ return () => {
119
+ onUnmount();
120
+ };
121
+ }, [onUnmount, passThrough]);
122
+ (0, react_1.useLayoutEffect)(() => {
123
+ if (passThrough) {
124
+ return;
125
+ }
126
+ const canvas = canvasRef.current;
127
+ if (!canvas) {
128
+ return;
129
+ }
130
+ // Size the canvas grid to match the device scale factor to prevent blurriness.
131
+ const observer = new ResizeObserver(([entry]) => {
132
+ canvas.width = entry.devicePixelContentBoxSize[0].inlineSize;
133
+ canvas.height = entry.devicePixelContentBoxSize[0].blockSize;
134
+ });
135
+ observer.observe(canvas, { box: 'device-pixel-content-box' });
136
+ }, [passThrough]);
137
+ if (passThrough) {
138
+ return children;
139
+ }
140
+ return (jsx_runtime_1.jsx(remotion_2.AbsoluteFill, { children: jsx_runtime_1.jsx("canvas", { ref: canvasRef, style: canvasSubtreeStyle, children: children }) }));
141
+ };
142
+ exports.HtmlInCanvasPresentation = HtmlInCanvasPresentation;
143
+ const makeHtmlInCanvasPresentation = (shader) => {
144
+ const CompWithShader = (props) => {
145
+ const { passedProps, ...otherProps } = props;
146
+ const { _experimentalEffects, ...restPassedProps } = props.passedProps;
147
+ return (jsx_runtime_1.jsx(exports.HtmlInCanvasPresentation, { shader: shader, passedProps: restPassedProps, _experimentalEffects: _experimentalEffects, ...otherProps }));
148
+ };
149
+ return (props) => {
150
+ return {
151
+ component: CompWithShader,
152
+ props,
153
+ };
154
+ };
155
+ };
156
+ exports.makeHtmlInCanvasPresentation = makeHtmlInCanvasPresentation;
@@ -0,0 +1,10 @@
1
+ import type { HtmlInCanvasShader } from '../html-in-canvas-presentation';
2
+ export type ZoomBlurProps = {
3
+ rotation?: number;
4
+ };
5
+ export declare const zoomBlurShader: () => HtmlInCanvasShader<ZoomBlurProps>;
6
+ export declare const zoomBlur: (props: ZoomBlurProps & {
7
+ _experimentalEffects?: import("remotion").EffectsProp | undefined;
8
+ }) => import("..").TransitionPresentation<ZoomBlurProps & {
9
+ _experimentalEffects?: import("remotion").EffectsProp | undefined;
10
+ }>;
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.zoomBlur = exports.zoomBlurShader = void 0;
4
+ const html_in_canvas_presentation_1 = require("../html-in-canvas-presentation");
5
+ const VERTEX_SHADER = `#version 300 es
6
+ in vec2 a_pos;
7
+ out vec2 v_uv;
8
+ void main() {
9
+ v_uv = vec2(a_pos.x * 0.5 + 0.5, 0.5 - a_pos.y * 0.5);
10
+ gl_Position = vec4(a_pos, 0.0, 1.0);
11
+ }`;
12
+ const FRAGMENT_SHADER = `#version 300 es
13
+ precision highp float;
14
+
15
+ uniform sampler2D u_prev;
16
+ uniform sampler2D u_next;
17
+ uniform float u_time;
18
+ uniform float u_aspect;
19
+ uniform float u_max_angle;
20
+
21
+ in vec2 v_uv;
22
+ out vec4 outColor;
23
+
24
+ const int SAMPLES = 16;
25
+ const float STRENGTH = 0.35;
26
+
27
+ vec2 transformUV(vec2 uv, float angle, float scale) {
28
+ vec2 p = uv - 0.5;
29
+ p.x *= u_aspect;
30
+ p /= scale;
31
+ float c = cos(-angle);
32
+ float s = sin(-angle);
33
+ p = vec2(p.x * c - p.y * s, p.x * s + p.y * c);
34
+ p.x /= u_aspect;
35
+ return p + 0.5;
36
+ }
37
+
38
+ float coverScale(float angle) {
39
+ float c = abs(cos(angle));
40
+ float s = abs(sin(angle));
41
+ float ar = max(u_aspect, 1.0 / u_aspect);
42
+ return c + ar * s;
43
+ }
44
+
45
+ vec4 zoomBlur(sampler2D tex, vec2 uv, float strength) {
46
+ vec2 dir = uv - 0.5;
47
+ vec4 acc = vec4(0.0);
48
+ for (int i = 0; i < SAMPLES; i++) {
49
+ float t = float(i) / float(SAMPLES - 1);
50
+ float scale = 1.0 - strength * t;
51
+ acc += texture(tex, 0.5 + dir * scale);
52
+ }
53
+ return acc / float(SAMPLES);
54
+ }
55
+
56
+ void main() {
57
+ float mixT = u_time;
58
+
59
+ float nextAngle = u_max_angle * mixT;
60
+ float prevAngle = -u_max_angle * (1.0 - mixT);
61
+
62
+ vec2 prevUV = transformUV(v_uv, prevAngle, coverScale(prevAngle));
63
+ vec2 nextUV = transformUV(v_uv, nextAngle, coverScale(nextAngle));
64
+
65
+ vec4 prev = zoomBlur(u_prev, prevUV, STRENGTH * (1.0 - mixT));
66
+ vec4 next = zoomBlur(u_next, nextUV, STRENGTH * mixT);
67
+ outColor = mix(prev, next, (1.0 - mixT));
68
+ }`;
69
+ const compileShader = (gl, source, type) => {
70
+ const shader = gl.createShader(type);
71
+ if (!shader) {
72
+ throw new Error('Failed to create shader');
73
+ }
74
+ gl.shaderSource(shader, source);
75
+ gl.compileShader(shader);
76
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
77
+ const log = gl.getShaderInfoLog(shader);
78
+ gl.deleteShader(shader);
79
+ throw new Error(`Failed to compile shader: ${log}`);
80
+ }
81
+ return shader;
82
+ };
83
+ const createProgram = (gl) => {
84
+ const program = gl.createProgram();
85
+ if (!program) {
86
+ throw new Error('Failed to create WebGL program');
87
+ }
88
+ const vs = compileShader(gl, VERTEX_SHADER, gl.VERTEX_SHADER);
89
+ const fs = compileShader(gl, FRAGMENT_SHADER, gl.FRAGMENT_SHADER);
90
+ gl.attachShader(program, vs);
91
+ gl.attachShader(program, fs);
92
+ gl.linkProgram(program);
93
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
94
+ const log = gl.getProgramInfoLog(program);
95
+ gl.deleteProgram(program);
96
+ throw new Error(`Failed to link program: ${log}`);
97
+ }
98
+ return program;
99
+ };
100
+ const createTexture = (gl) => {
101
+ const tex = gl.createTexture();
102
+ if (!tex) {
103
+ throw new Error('Failed to create texture');
104
+ }
105
+ gl.bindTexture(gl.TEXTURE_2D, tex);
106
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
107
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
108
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
109
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
110
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 0]));
111
+ return tex;
112
+ };
113
+ const zoomBlurShader = () => {
114
+ let state = null;
115
+ const init = (canvas) => {
116
+ const gl = canvas.getContext('webgl2', { premultipliedAlpha: true });
117
+ if (!gl) {
118
+ return () => { };
119
+ }
120
+ const program = createProgram(gl);
121
+ const prevTex = createTexture(gl);
122
+ const nextTex = createTexture(gl);
123
+ const vao = gl.createVertexArray();
124
+ gl.bindVertexArray(vao);
125
+ const buffer = gl.createBuffer();
126
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
127
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
128
+ const aPos = gl.getAttribLocation(program, 'a_pos');
129
+ gl.enableVertexAttribArray(aPos);
130
+ gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
131
+ state = {
132
+ gl,
133
+ program,
134
+ prevTex,
135
+ nextTex,
136
+ uTime: gl.getUniformLocation(program, 'u_time'),
137
+ uPrev: gl.getUniformLocation(program, 'u_prev'),
138
+ uNext: gl.getUniformLocation(program, 'u_next'),
139
+ uAspect: gl.getUniformLocation(program, 'u_aspect'),
140
+ uMaxAngle: gl.getUniformLocation(program, 'u_max_angle'),
141
+ };
142
+ return () => { };
143
+ };
144
+ const cleanup = () => {
145
+ if (!state) {
146
+ throw new Error('Zoom blur state not initialized');
147
+ }
148
+ const { gl, program, prevTex, nextTex } = state;
149
+ gl.deleteProgram(program);
150
+ gl.deleteTexture(prevTex);
151
+ gl.deleteTexture(nextTex);
152
+ state = null;
153
+ };
154
+ const clear = () => {
155
+ if (!state) {
156
+ throw new Error('Zoom blur state not initialized');
157
+ }
158
+ const { gl } = state;
159
+ gl.clearColor(0, 0, 0, 0);
160
+ gl.clear(gl.COLOR_BUFFER_BIT);
161
+ };
162
+ const draw = ({ prevImage, nextImage, width, height, time, passedProps, }) => {
163
+ if (!state) {
164
+ throw new Error('Zoom blur state not initialized');
165
+ }
166
+ const { rotation = Math.PI / 6 } = passedProps;
167
+ const { gl, program, prevTex, nextTex, uTime, uPrev, uNext, uAspect, uMaxAngle, } = state;
168
+ if (!prevImage && !nextImage) {
169
+ return;
170
+ }
171
+ if (prevImage && (prevImage.width === 0 || prevImage.height === 0)) {
172
+ return;
173
+ }
174
+ if (nextImage && (nextImage.width === 0 || nextImage.height === 0)) {
175
+ return;
176
+ }
177
+ // When one side is missing, force the mix to fully show the other.
178
+ // At time=0 the shader outputs nextImage (and nextAngle = 0).
179
+ // At time=1 the shader outputs prevImage (and prevAngle = 0).
180
+ const effectiveTime = !prevImage ? 0 : !nextImage ? 1 : time;
181
+ gl.viewport(0, 0, width, height);
182
+ gl.clearColor(0, 0, 0, 0);
183
+ gl.clear(gl.COLOR_BUFFER_BIT);
184
+ gl.useProgram(program);
185
+ gl.activeTexture(gl.TEXTURE0);
186
+ gl.bindTexture(gl.TEXTURE_2D, prevTex);
187
+ if (prevImage) {
188
+ gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, prevImage);
189
+ }
190
+ gl.uniform1i(uPrev, 0);
191
+ gl.activeTexture(gl.TEXTURE1);
192
+ gl.bindTexture(gl.TEXTURE_2D, nextTex);
193
+ if (nextImage) {
194
+ gl.texElementImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, nextImage);
195
+ }
196
+ gl.uniform1i(uNext, 1);
197
+ gl.uniform1f(uTime, effectiveTime);
198
+ gl.uniform1f(uAspect, width / height);
199
+ gl.uniform1f(uMaxAngle, rotation);
200
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
201
+ };
202
+ return {
203
+ init,
204
+ clear,
205
+ draw,
206
+ cleanup,
207
+ };
208
+ };
209
+ exports.zoomBlurShader = zoomBlurShader;
210
+ exports.zoomBlur = (0, html_in_canvas_presentation_1.makeHtmlInCanvasPresentation)(exports.zoomBlurShader);
package/dist/types.d.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  import type { ComponentType } from 'react';
2
+ import type React from 'react';
3
+ import type { DrawFunction } from './TransitionSeries';
2
4
  export type PresentationDirection = 'entering' | 'exiting';
3
5
  export type TransitionTiming = {
4
6
  getDurationInFrames: (options: {
@@ -24,6 +26,9 @@ export type TransitionPresentationComponentProps<PresentationProps extends Recor
24
26
  presentationDirection: PresentationDirection;
25
27
  passedProps: PresentationProps;
26
28
  presentationDurationInFrames: number;
29
+ onElementImage: (elementImage: ElementImage, draw: DrawFunction) => void;
30
+ onUnmount: () => void;
31
+ bothEnteringAndExiting: boolean;
27
32
  };
28
33
  export type TransitionSeriesOverlayProps = {
29
34
  readonly durationInFrames: number;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/transitions"
4
4
  },
5
5
  "name": "@remotion/transitions",
6
- "version": "4.0.453",
6
+ "version": "4.0.455",
7
7
  "description": "Library for creating transitions in Remotion",
8
8
  "sideEffects": false,
9
9
  "main": "dist/esm/index.mjs",
@@ -23,18 +23,18 @@
23
23
  "url": "https://github.com/remotion-dev/remotion/issues"
24
24
  },
25
25
  "dependencies": {
26
- "remotion": "4.0.453",
27
- "@remotion/shapes": "4.0.453",
28
- "@remotion/paths": "4.0.453"
26
+ "remotion": "4.0.455",
27
+ "@remotion/shapes": "4.0.455",
28
+ "@remotion/paths": "4.0.455"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@happy-dom/global-registrator": "14.5.1",
32
- "remotion": "4.0.453",
32
+ "remotion": "4.0.455",
33
33
  "react": "19.2.3",
34
34
  "react-dom": "19.2.3",
35
- "@remotion/test-utils": "4.0.453",
36
- "@remotion/player": "4.0.453",
37
- "@remotion/eslint-config-internal": "4.0.453",
35
+ "@remotion/test-utils": "4.0.455",
36
+ "@remotion/player": "4.0.455",
37
+ "@remotion/eslint-config-internal": "4.0.455",
38
38
  "eslint": "9.19.0",
39
39
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
40
40
  },
@@ -86,6 +86,12 @@
86
86
  "import": "./dist/esm/clock-wipe.mjs",
87
87
  "require": "./dist/presentations/clock-wipe.js"
88
88
  },
89
+ "./zoom-blur": {
90
+ "types": "./dist/presentations/zoom-blur.d.ts",
91
+ "module": "./dist/esm/zoom-blur.mjs",
92
+ "import": "./dist/esm/zoom-blur.mjs",
93
+ "require": "./dist/presentations/zoom-blur.js"
94
+ },
89
95
  "./none": {
90
96
  "types": "./dist/presentations/none.d.ts",
91
97
  "module": "./dist/esm/none.mjs",
@@ -122,6 +128,9 @@
122
128
  ],
123
129
  "iris": [
124
130
  "dist/presentations/iris.d.ts"
131
+ ],
132
+ "zoom-blur": [
133
+ "dist/presentations/zoom-blur.d.ts"
125
134
  ]
126
135
  }
127
136
  },
package/zoom-blur.js ADDED
@@ -0,0 +1,2 @@
1
+ // For backwards compatibility when you use `esm-wallaby`
2
+ module.exports = require('./dist/presentations/zoom-blur.js');