Skip to content

Transitions

TransitionSeries is Remotion’s <TransitionSeries>: sequences one after another, with a transition between two of them. A transition overlaps its two sequences by its duration, so both show during it and the series is shorter than its sequences by the sum of the transitions. The entering sequence is drawn on top. Gallery scenes: TransitionSeries, TransitionProgress, BeforeOverlap, SceneOverlap.

TransitionSeries demo, first frame
TransitionSeries 2.2 s
TransitionProgress demo, first frame
TransitionProgress 2.2 s
BeforeOverlap demo, first frame
BeforeOverlap 1.5 s
SceneOverlap demo, first frame
SceneOverlap 2 s
Item or component Props What it does
TransitionSeries key: Option<&str>, items the series; key keeps clip ids apart when two series share a frame
TransitionSeries::sequence(seconds, children) a sequence; inside, time starts at 0 and ui.duration is its length
TransitionSeries::transition(timing, presentation) a transition between the sequences on either side
Fade should_fade_out_exiting_scene (false) the entering sequence fades in over the exiting one (Remotion fade())
Slide direction (FromLeft), size (1920 x 1080) the entering sequence pushes the exiting one out (slide())
Wipe direction (FromLeft), size an edge moves across and uncovers the entering sequence (wipe())
Flip direction (FromLeft), size the exiting sequence turns half a turn and the entering one is on its back (flip()); drawn flat as a scale, so direction only picks the axis and there is no perspective
ClockWipe size a hand sweeps clockwise from twelve o’clock and uncovers the entering sequence (clockWipe())
Iris size the entering sequence shows through a circle that opens from the middle (iris())
CircleWipe center (0.5, 0.5, a share of size), color (colors.title), size a disc of color grows from center over the exiting sequence, then the entering one opens from the same point
SlantWipe color (colors.background), edge (colors.highlight), back (colors.accent), skew (320), size a slanted panel with a stripe in front and behind sweeps across, covers everything at the middle and leaves the entering sequence behind it
NoTransition a hard cut at the middle of the overlap (none()); the series keeps the overlap, so the sequences can animate it themselves with ui.transition_progress()
PushCut cut_progress (5/11), outgoing_scale (1.04), incoming_start_scale (1.04), incoming_end_scale (1.07), origin (0.5, 0.5), flash_color (#f5f2ed), flash_opacity (0.2), flash_frames (2), size a hard editorial cut with a punch-in on both sides and a short flash at the edit (pushCut()); use a linear timing
ZoomInOut scale (1.5), size the exiting sequence zooms in and cross-fades into the entering one, which zooms back out (zoomInOut()); scale is the kit’s, Remotion has no options
DreamyZoom rotation (6°), scale (1.2), flash_color (white), blur (12), size both sides zoom, turn and blur through a flash at the middle (dreamyZoom())
LinearBlur intensity (0.1, a share of the width), axes (1, 1), size the entering sequence fades in while both blur, most at the middle (linearBlur()); axes weighs the blur along x and y, a slanted direction is not available
BlurSlide direction (FromLeft), blur (0.5, a share of the size), size both sides pan like a fast camera move with a motion blur along it (blurSlide())
CrossZoom strength (0.4), size both sides zoom about a centre that moves across and blur, cross-fading at the middle (crossZoom()); an approximation: the blur is even, not along rays
Dissolve line_width (0.1), spread_color (#ff0000), hot_color (#e6e633), pow (5), intensity (1), seed, frequency (0.004), size the exiting sequence burns away with a glowing edge (dissolve()); the burn follows seeded feTurbulence noise, not the luminance of the pixels as in Remotion
FilmBurn seed (2.31), blur (6), size a burning film overlay flares over a blurred cross-fade (filmBurn()); an approximation of the GL original from a radial gradient and noise
SwapCards reflection (0.4), perspective (0.2), depth (3), floor (colors.background), size the two sequences trade places like cards on a floor, with reflections (swap()); a 2D approximation of the 3D original. Manim’s Swap is another thing
Timing::Linear duration, easing: RateFunc Remotion’s linearTiming; the theme default is motion.duration.slow with motion.easing.standard
Timing::Spring mass, stiffness, damping, duration: Option<f32> springTiming: lasts until the spring settles, or is squeezed into duration
SceneFade fade_in, fade_out, rate_func, children fades at both edges of the enclosing sequence or scene; SceneFade::new(ui, children) takes the theme’s motion.duration.fast

Gallery scenes: Fade, Slide, Wipe, Flip, ClockWipe, Iris, CircleWipe, SlantWipe, NoTransition, PushCut, ZoomInOut, DreamyZoom, LinearBlur, BlurSlide, CrossZoom, Dissolve, FilmBurn, SwapCards, SceneFade.

Fade demo, first frame
Fade 2.2 s
Slide demo, first frame
Slide 2.2 s
Wipe demo, first frame
Wipe 2.2 s
Flip demo, first frame
Flip 2.2 s
ClockWipe demo, first frame
ClockWipe 2.2 s
Iris demo, first frame
Iris 2.2 s
CircleWipe demo, first frame
CircleWipe 2.2 s
SlantWipe demo, first frame
SlantWipe 2.2 s
NoTransition demo, first frame
NoTransition 2.2 s
PushCut demo, first frame
PushCut 2.2 s
ZoomInOut demo, first frame
ZoomInOut 2.2 s
DreamyZoom demo, first frame
DreamyZoom 2.2 s
LinearBlur demo, first frame
LinearBlur 2.2 s
BlurSlide demo, first frame
BlurSlide 2.2 s
CrossZoom demo, first frame
CrossZoom 2.2 s
Dissolve demo, first frame
Dissolve 2.2 s
FilmBurn demo, first frame
FilmBurn 2.2 s
SwapCards demo, first frame
SwapCards 2.2 s
SceneFade demo, first frame
SceneFade 2 s

direction is FromLeft, FromRight, FromTop or FromBottom. size is the canvas in view-box units. Change it when the view box is not 1920 x 1080. A transition that is longer than a sequence next to it is shortened to that sequence. A transition that is first, last or next to another transition is skipped. inspect warns about both.

use fframes_kit::prelude::*;
fn panel<'a>(ui: &mut Ui<'a, '_, '_>, background: Color) -> Svgr<'a> {
ui.show(Fill { background: Some(background), children: children(|_| Svgr::empty()) })
}
fn fading_scene<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
ui.show(SceneFade::new(ui, children(|ui| ui.show(Circle { ..ui.defaults() }))))
}
/// Two 1.5 s panels with a 0.8 s wipe: 2.2 s in all.
fn wipe<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
let c = ui.theme.colors;
let timing: Timing = ui.defaults();
ui.show(TransitionSeries {
key: None,
items: vec![
TransitionSeries::sequence(1.5, children(move |ui| panel(ui, c.surface))),
TransitionSeries::transition(timing, Wipe { direction: Direction::FromRight, ..Wipe::default() }),
TransitionSeries::sequence(1.5, children(move |ui| panel(ui, c.accent))),
],
})
}

The blurring presentations (DreamyZoom, LinearBlur, BlurSlide, CrossZoom, FilmBurn) run an feGaussianBlur over each side during the transition, up to about 60 ms more a frame than a fade on the CPU backend at 1080p. They blur only what is on the canvas (size), so the CPU and Skia backends draw the same, and they keep the alpha of the blurred side, so the canvas does not darken at its edges. They expect sequences that cover the canvas, as a Fill does: a translucent sequence turns opaque where the blur leaves it a quarter opaque.

Not yet available, because they need pixel shaders: crosswarp, ripple, bookFlip, cube and custom-html-in-canvas.

Inside a sequence, ui.transition_progress() is Remotion’s useTransitionProgress(): entering runs from 0 to 1 while the sequence comes in and is 1 otherwise, exiting runs from 0 to 1 while it goes away and is 0 otherwise, and in_series is false outside a series (1 and 0 then). ui.transition_seconds(side) is the length of a running transition.

The kit cannot change fn audio, so it gives the times to fade a sequence’s sound with instead: audio_fades(&lengths, fps) (or series.audio_fades(fps)) returns, per sequence, where it starts and ends and how long its fades are, in seconds from the series start. fade.apply(track) sets them on an AudioTrack.

use fframes::{AudioMap, AudioTimestamp::Second, AudioTrack};
use fframes_kit::prelude::*;
const SERIES_START: f32 = 2.0;
fn audio() -> AudioMap<'static> {
let fades = audio_fades(
&[
SeriesLength::Sequence(4.0),
SeriesLength::Transition(Timing::default()),
SeriesLength::Sequence(4.0),
],
30,
);
let track = |file, fade: &AudioFade| {
let range = Second(SERIES_START + fade.start)..Second(SERIES_START + fade.end);
fade.apply(AudioTrack::new(file, range))
};
AudioMap::from([track("one.wav", &fades[0]), track("two.wav", &fades[1])])
}
/// A bar that fills while the sequence comes in.
fn bar<'a>(ui: &mut Ui<'a, '_, '_>) -> Svgr<'a> {
let width = 1200.0 * ui.transition_progress().entering;
svgr!(<rect x="96" y="700" width={width.max(1.0)} height="24" fill="#fff" />)
}

A presentation of your own implements Presentation. apply gets the progress, 0 when the transition starts and 1 when it ends, for both sides. A spring may overshoot 1. side is Side::Entering or Side::Exiting, and id is unique to the transition in the frame, for clip paths, masks and gradients.

use fframes_kit::prelude::*;
/// The entering sequence grows from the center of the canvas.
struct Zoom;
impl Presentation for Zoom {
fn apply<'a>(&self, progress: f32, side: Side, content: Svgr<'a>, _ui: &Ui<'a, '_, '_>, _id: &str) -> Svgr<'a> {
if side == Side::Exiting {
return content;
}
let s = motion::lerp(0.6, 1.0, progress);
let shift = Transform::translate(960.0 * (1.0 - s), 540.0 * (1.0 - s));
svgr!(
<g transform={shift} opacity={motion::clamp01(progress)}>
<g transform={Transform::scale(s)}>{content}</g>
</g>
)
}
}
let spring = Timing::Spring { mass: 1.0, stiffness: 100.0, damping: 10.0, duration: None };
assert!(spring.duration() > 0.3);
assert_eq!(spring.progress(0.0, spring.duration()), 0.0);
assert_eq!(spring.progress(spring.duration(), spring.duration()), 1.0);

Core scenes can transition without a series through their Overlap. In render_frame, wrap the content with scene_transition(ui, &presentation, timing, side, key, content), using a Ui made with Ui::for_scene. Side::Entering runs over the scene’s first timing.duration() seconds, so give the scene Overlap::Previous of the same length. Side::Exiting runs over the last seconds, and goes on the scene before it.

use fframes_kit::prelude::*;
#[derive(Debug)]
struct Second {
theme: Theme,
}
const SLIDE: Timing = Timing::Linear { duration: 0.5, easing: RateFunc::Smooth };
impl Scene for Second {
fn duration(&self) -> Duration<'_> {
Duration::Seconds(3.0)
}
fn overlap(&self) -> Overlap {
Overlap::Previous(0.5) // starts under the end of the scene before
}
fn render_frame<'a>(&'a self, frame: Frame, ctx: &FFramesContext<'a, '_>) -> Svgr<'a> {
let mut ui = Ui::for_scene(frame, ctx, &self.theme, self);
let content = ui.show(Fill { background: Some(ui.theme.colors.accent), children: children(|_| Svgr::empty()) });
let slide = Slide { direction: Direction::FromRight, ..Slide::default() };
scene_transition(&ui, &slide, SLIDE, Side::Entering, Some("second"), content)
}
}

Components are plain structs. Set the fields you need and fill the rest with ..ui.defaults(): the defaults below come from the current theme (theme. values are fields of Theme).

Sequences one after another with transitions between them (Remotion’s <TransitionSeries>). A transition overlaps its two sequences by its duration, so the series is shorter than its sequences by the sum of the transitions. A transition longer than one of its sequences is shortened to it; a transition that is first, last, or next to another transition is skipped. inspect warns about both.

Field Type Default Description
key Option<&str> — Makes the ids of the transitions’ clip paths unique when two series share a frame.
items Vec<TransitionItem> — Sequences and the transitions between them, in play order.

Methods:

  • fn sequence(seconds: f32, children: Children) -> TransitionItem: A sequence item.
  • fn lengths(&self) -> Vec<SeriesLength>: The lengths of the items, for audio_fades.
  • fn audio_fades(&self, fps: usize) -> Vec<AudioFade>: Where each sequence plays and how long its audio fades at fps, see audio_fades.
  • fn transition(timing: Timing, presentation: impl Presentation) -> TransitionItem: A transition item.

Source

Fades its children in at the start and out at the end of the enclosing Sequence or scene (ui.duration). Without a known end only the fade in runs. A 0 length skips that fade.

Field Type Default Description
fade_in f32 theme.motion.duration.fast Seconds of the fade in.
fade_out f32 theme.motion.duration.fast Seconds of the fade out, ending with ui.duration.
rate_func RateFunc theme.motion.easing.standard Easing of the fades over their lengths.
children Children no_children() What it fades; a closure taking the Ui.

Methods:

  • fn new(ui: &Ui, children: Children) -> Self: Fades of the theme’s motion.duration.fast with its standard easing.
  • fn opacity(&self, t: f32, length: Option<f32>) -> f32: Opacity at t seconds of a container that lasts length seconds.

Source