← Blog

Why your Remotion scene transitions look broken

A fade() between two Remotion scenes leaves both of them on screen at full opacity. Here is why, and the one-line fix.

You wire up <TransitionSeries>, drop a fade() between two scenes, render — and for half a second in the middle, both scenes are on screen at once. Two headlines stacked on the same pixels, unreadable.

It is not a bug in your components, and it is not a timing problem. It is what fade() does.

The exiting scene never fades out

Here is the presentation, from @remotion/transitions:

opacity: isEntering
  ? presentationProgress
  : passedProps.shouldFadeOutExitingScene
    ? 1 - presentationProgress
    : 1,

Read the last branch. When the scene is exiting and you have not passed shouldFadeOutExitingScene, its opacity is 1 — for the entire transition. The arriving scene ramps 0 → 1 on top of a scene that is still fully painted.

So an 18-frame transition means 18 frames where both scenes are inked. If both put something in the middle of the frame — and almost every scene does — you get a double exposure.

Settling earlier does not help

The instinct is to make the outgoing scene finish sooner, so it is "done" before the overlap. That fixes nothing. A finished scene is exactly as opaque as an unfinished one. The problem is not that it is still moving; it is that it is still there.

shouldFadeOutExitingScene: true does not rescue it either. Now both scenes sit at 50% through the middle of the transition — two ghost headlines instead of two solid ones.

Transparency makes it worse

If your scene components render transparent — which is the right default, so the composition owns the background — then every compositing presentation has the same flaw:

PresentationSafe between two centred scenes?
fade()No — exiting scene stays at full opacity
none()No — both stacked, entering one on top
wipe(), iris(), clockWipe()No — the exiting scene shows through the revealed region
slide(), flip()Yes — the scenes are moved apart

A wipe looks like it should work, because it has a hard edge. But it clips the entering scene; it does not remove the exiting one. Wherever the entering scene is transparent, the old one is still visible underneath.

The fix

Use a presentation that moves the scenes apart rather than blending them. Then every pixel belongs to exactly one scene, no matter what either one paints:

import { springTiming, TransitionSeries } from "@remotion/transitions";
import { slide } from "@remotion/transitions/slide";

const beat = {
  presentation: slide({ direction: "from-right" }),
  timing: springTiming({ durationInFrames: 18, config: { damping: 200 } }),
};

<TransitionSeries>
  <TransitionSeries.Sequence durationInFrames={110}>
    <SceneOne />
  </TransitionSeries.Sequence>
  <TransitionSeries.Transition {...beat} />
  <TransitionSeries.Sequence durationInFrames={130}>
    <SceneTwo />
  </TransitionSeries.Sequence>
</TransitionSeries>;

Two details worth keeping:

damping: 200. The default spring overshoots. On a scale or an opacity that reads as bounce; on a full-frame slide it throws the whole scene past centre and drags it back. Critically damping it removes the wobble without making the move feel slow.

direction: "from-right". Remotion's default is from-left, which brings the new scene in from the left and pushes the old one right — the direction a "back" gesture moves. from-right reads as forward.

When a fade is still right

A crossfade is not always wrong. It is right when the outgoing scene is empty by the time the transition starts — a scene that has already animated its own content away, or a full-bleed background with nothing to read. That is a real shot, and it is the only case where the overlap costs you nothing.

Everywhere else, slide.