remotion icon indicating copy to clipboard operation
remotion copied to clipboard

Chained sequences with transitions

Open BrunoQuaresma opened this issue 2 years ago • 3 comments

Would be interesting to have a built-in wat to chain sequences + transitions like the video below:

https://user-images.githubusercontent.com/3165839/158035225-ff1eb6bd-45fe-4180-a324-d5b8331dfd71.mov

Here is the code that I used for it:

Scene.tsx

import React, {useMemo} from 'react';
import {interpolate, Sequence, AbsoluteFill, useCurrentFrame} from 'remotion';

type SceneProps = {
  from: number;
  durationInFrames: number;
  transitionDurationInFrames: number;
  interpolateRange?: Array<number>;
  styles?:
    | React.CSSProperties
    | ((interpolateValue: number) => React.CSSProperties);
};

export const Scene: React.FC<SceneProps> = ({
  from,
  durationInFrames,
  transitionDurationInFrames,
  interpolateRange = [0, 1],
  styles,
  children,
}) => {
  const frame = useCurrentFrame();
  const interpolateValue = interpolate(
    frame,
    [from, from + transitionDurationInFrames],
    interpolateRange,
    {extrapolateRight: 'clamp'}
  );
  const stylesValue: React.CSSProperties | undefined = useMemo(() => {
    if (typeof styles === 'function') {
      return styles(interpolateValue);
    }

    return styles;
  }, [interpolateValue]);

  return (
    <Sequence from={from} durationInFrames={durationInFrames}>
      <AbsoluteFill style={stylesValue}>{children}</AbsoluteFill>
    </Sequence>
  );
};

export const useTransitionSequence = ({
  sceneDuration,
  transitionDuration,
  scenes,
}: {
  sceneDuration: number;
  transitionDuration: number;
  scenes: Array<
    Pick<SceneProps, 'interpolateRange' | 'styles'> & {children: JSX.Element}
  >;
}) => {
  const render = () =>
    scenes.map((scene, index) => {
      const isFirst = index === 0;
      const isLast = index === scenes.length - 1;
      const overlaps = isFirst || isLast ? 1 : 2;
      const durationInFrames = sceneDuration + overlaps * transitionDuration;

      return (
        <Scene
          key={index}
          {...scene}
          from={
            index * sceneDuration +
            (!isFirst ? (index - 1) * transitionDuration : 0)
          }
          durationInFrames={durationInFrames}
          transitionDurationInFrames={transitionDuration}
        />
      );
    });

  return {
    render,
  };
};

export const measureTransitionSequence = ({
  numberOfScenes,
  sceneDuration,
  transitionDuration,
}: {
  numberOfScenes: number;
  sceneDuration: number;
  transitionDuration: number;
}) => {
  const overlaps = numberOfScenes - 1;
  return numberOfScenes * sceneDuration + overlaps * transitionDuration;
};

Usage

import React from 'react';
import {useVideoConfig, AbsoluteFill} from 'remotion';
import {useTransitionSequence} from '../Scene';

const secondsTo = (value: number) => (seconds: number) => {
  return Math.round(seconds * value);
};

export const MyVideo: React.FC = () => {
  const videoConfig = useVideoConfig();
  const secondsToFrame = secondsTo(videoConfig.fps);
  const sequence = useTransitionSequence({
    sceneDuration: secondsToFrame(1),
    transitionDuration: secondsToFrame(0.5),
    scenes: [
      {styles: {backgroundColor: 'red'}, children: <>Hi world!</>},
      {
        interpolateRange: [1, 0],
        styles: (interpolateValue) => ({
          backgroundColor: 'blue',
          transform: `translateX(${interpolateValue * 100}%)`,
        }),
        children: <>Hi text!</>,
      },
      {
        interpolateRange: [1, 0],
        styles: (interpolateValue) => ({
          backgroundColor: 'yellow',
          transform: `translateY(${interpolateValue * 100}%)`,
        }),
        children: <>Hi text!</>,
      },
      {
        interpolateRange: [1, 0],
        styles: (interpolateValue) => ({
          backgroundColor: 'green',
          transform: `translateX(${interpolateValue * 100}%)`,
        }),
        children: <>Hi text!</>,
      },
    ],
  });

  return <AbsoluteFill>{sequence.render()}</AbsoluteFill>;
};

BrunoQuaresma avatar Mar 12 '22 21:03 BrunoQuaresma

Good idea! We still want to provide first-party transition support. Do you think it can be treater together with #35 or is it a different idea?

JonnyBurger avatar Mar 13 '22 09:03 JonnyBurger

Started working on it: https://github.com/remotion-dev/remotion/pull/1057

JonnyBurger avatar Jun 19 '22 09:06 JonnyBurger

@BrunoQuaresma Have you had a look at https://github.com/marcusstenbeck/remotion-transition-series?

fyi, I'm the author

marcusstenbeck avatar Apr 21 '23 14:04 marcusstenbeck