Austin Roy Omondi Live long and prosper 👌

Remotion: A framework for making videos in React 

9 min read 2781

Remotion Making Videos In React

We use video to enrich our web experiences in various ways, but what if I told you that you can now use the web to create videos? This article introduces Remotion, a suite of libraries building a fundament for creating videos programmatically using React.

Remotion allows you to create visual effects with canvas, CSS, SVG, and WebGL and compose them into video. You can even do so programmatically using familiar concepts like variables, functions, math, and so on. With Remotion, you can also use JavaScript code to generate video and eventually render it into an MP4 file if you wish.

Remotion has two core dependencies that we need to install before we can get started: FFmpeg and Node.js . Below are links to information and installation guides for both on various platforms:

To get a better understanding of how Remotion works, we will go through the demo video that is created when you initialize Remotion.

Initializing Remotion

Once you have FFmpeg and Node, you are ready to get started with Remotion. You can initialize a React app and a demo video using some boilerplate by running:

yarn create video

or

npm init video

Linux users will need to install some additional packages to get Chrome/Puppeteer working:

apt install -y gconf-service libasound2 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 ca-certificates fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget libgbm-dev

These commands will generate a JavaScript application running on an express server that contains the following:

  • A server.tsx file that contains the logic used to render your video
  • A remotion.config.ts file where you can configure various Remotion settings. You can read more on settings you can configure here
  • An optional Dockerfile you can use if you want to dockerize the server for SSR (server-side rendering)
  • An src directory containing the React components used to create your video
  • Configuration files for Prettier, ESlint, GitHub workflows, and your code editor (if you use VSCode)

The script also installs any npm dependencies you need to get started. Once this is done, you can run the video with yarn start or npm run start. Remotion’s video player will run localhost:3000 with the components placed on a timeline, similar to what you would see in video editors like Adobe Premiere Pro.

How Remotion creates video using React

The basic idea behind Remotion is that you get a frame number and a blank canvas to render anything you want by using various web technologies like SVG and WebGL.

We made a custom demo for .
No really. Click here to check it out.

Inside the src directory, you will find one HelloWorld folder containing components used to generate our final clip and files that compile these into the final video. In this section, we will look at how all these pieces come together to make a web-generated video.

Inside the HelloWorld folder are the files that the example code uses to generate our video: a rotating React logo (the atom) and some animated text. Before we can dive into these files, let’s look at a few hooks and functions from Remotion that we will use.

useVideoConfig allows the component to retrieve information about the context of the video. It returns an object containing the video properties, allowing them to be reused across components. There are four properties:

  • width: the width in pixels
  • height: the height in pixels
  • fps: the frame rate in frames per seconds
  • durationInFrames the total number of frames to be generated

useCurrentFrame returns an integer identifying the current frame that is being viewed. You can then use this frame to animate various properties on the page, from states to styles.

spring allows the developer to leverage spring animation primitives which put things into natural motion on the web. These work similarly to React Spring.

interpolate is a helper function that allows you to map a value to another using a concise syntax, making the animations more readable. It takes four arguments:

  • The input value
  • The range values, which the input can assume
  • The range of values that you want to map the input to
  • An options object with additional settings (you can find more information in this section of the documentation)

Sequence is a higher order component that allows one to time-shift parts of the animation and make them more reusable by taking the following props:

  • from : A required integer that indicates what frame the sequence starts from. When the sequence is at frame, its children are at 0`
  • durationInFrames : The length of the sequence in frames. Any components exceding this value willl not be displayed. If you wish to have the sequence run for an unlimited duration, you can pass Infinity because this prop is required
  • name: An optional label used purely for identification
  • layout: An optional prop that can be either "absolute-fill" (default) or "none". When set to "absolute-fill", the components act as layers and are placed on top of each other. You can pass "none" if you wish to customize this behavior

Composition is a component that registers a video to make it renderable and makes it show up in the sidebar of the Remotion player. The props it takes are the same as those contained in the object returned by useVideoConfig along with two additional ones:

  • id: ID of the composition that is shown in the sidebar and also a unique identifier of the composition. You must specify this and include only letters, numbers, and - if you want to render it
  • component or lazyComponent: This is either the React component with the animation or a function that returns a dynamic import. You can only pass one of these two as passing neither or both of the props is an error

Lastly, we have the registerRoot function. This is the root component of the Remotion project that should return one or more compositions wrapped in a React fragment.

These are the fundamental building blocks that you need to create a video. Now let’s see them in action.

Adding the React components

Inside the src folder, we have Arc.tsx, Atom.tsx, and Logo.tsx that are used to generate the React logo. We also have config.ts, which contains color constants, and Title.tsx and Subtitle.tsx, which create animated text for video.

Let’s look at Arc.tsx, which uses SVG to create the arcs in the React logo. This file has the following code:

import {useVideoConfig} from 'remotion';
import {COLOR_1, COLOR_2} from './config';
export const Arc: React.FC<{
    progress: number;
    rotation: number;
    rotateProgress: number;
}> = ({progress, rotation, rotateProgress}) => {
    const config = useVideoConfig();
    const cx = config.width / 2;
    const cy = config.height / 2;
    const rx = config.height / 8;
    const ry = rx * 2.2;
    const arcLength = Math.PI * 2 * Math.sqrt((rx * rx + ry * ry) / 2);
    const strokeWidth = arcLength / 60;
    return (
        <svg
            viewBox={`0 0 ${config.width} ${config.height}`}
            style={{
                position: 'absolute',
                transform: `rotate(${rotation * rotateProgress}deg)`,
            }}
        >
            <defs>
                <linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
                    <stop offset="0%" stopColor={COLOR_1} />
                    <stop offset="100%" stopColor={COLOR_2} />
                </linearGradient>
            </defs>
            <ellipse
                cx={cx}
                cy={cy}
                rx={rx}
                ry={ry}
                fill="none"
                stroke="url(#gradient)"
                strokeDasharray={arcLength}
                strokeDashoffset={arcLength - arcLength * progress}
                strokeLinecap="round"
                strokeWidth={strokeWidth}
            />
        </svg>
    );
};

In the Arc component, we feed three numbers that rotate the arc with CSS and fetch the config object from useVideoConfig. We then use the config object to get the length and width that we will use to generate the arc’s dimensions with SVG and some math formulae.

The Atom.tsx component takes a scale number and creates the circle at the centre of the logo, using it as the scale of the transform.

import {useVideoConfig} from 'remotion';
import {COLOR_1, COLOR_2} from './config';
export const Atom: React.FC<{
    scale: number;
}> = ({scale}) => {
    const config = useVideoConfig();
    return (
        <svg
            viewBox={`0 0 ${config.width} ${config.height}`}
            style={{
                position: 'absolute',
                transform: `scale(${scale})`,
            }}
        >
            <defs>
                <linearGradient id="gradient2" x1="0%" y1="0%" x2="100%" y2="0%">
                    <stop offset="0%" stopColor={COLOR_1} />
                    <stop offset="100%" stopColor={COLOR_2} />
                </linearGradient>
            </defs>
            <circle
                r={70}
                cx={config.width / 2}
                cy={config.height / 2}
                fill="url(#gradient2)"
            />
        </svg>
    );
};

Logo.tsx joins these two components to create the final spinning logo. It takes a transitionStart number value that is used with frame, interpolate, and spring to animate the arcs and with the atom to create the animation. Logo contains the following code:

import {interpolate, spring, useCurrentFrame, useVideoConfig} from 'remotion';
import {Arc} from './Arc';
import {Atom} from './Atom';
export const Logo: React.FC<{
    transitionStart: number;
}> = ({transitionStart}) => {
    const videoConfig = useVideoConfig();
    const frame = useCurrentFrame();
    const development = spring({
        config: {
            damping: 100,
            mass: 0.5,
        },
        fps: videoConfig.fps,
        frame,
    });
    const rotationDevelopment = spring({
        config: {
            damping: 100,
            mass: 0.5,
        },
        fps: videoConfig.fps,
        frame,
    });
    const scaleIn = spring({
        frame,
        config: {
            mass: 0.5,
        },
        fps: videoConfig.fps,
    });
    const translation = interpolate(
        spring({
            frame: frame - transitionStart,
            fps: videoConfig.fps,
            config: {
                damping: 100,
                mass: 0.5,
            },
        }),
        [0, 1],
        [0, -150]
    );
    const scale = frame < 50 ? scaleIn : 1;
    const logoRotation = interpolate(
        frame,
        [0, videoConfig.durationInFrames],
        [0, 360]
    );
    return (
        <div
            style={{
                position: 'absolute',
                width: videoConfig.width,
                height: videoConfig.height,
                transform: `scale(${scale}) translateY(${translation}px) rotate(${logoRotation}deg)`,
            }}
        >
            <Arc
                rotateProgress={rotationDevelopment}
                progress={development}
                rotation={30}
            />
            <Arc
                rotateProgress={rotationDevelopment}
                rotation={90}
                progress={development}
            />
            <Arc
                rotateProgress={rotationDevelopment}
                rotation={-30}
                progress={development}
            />
            <Atom scale={rotationDevelopment} />
        </div>
    );
};

The Logo component has a number of spring functions that are utilized to create the final animation: development, rotationDevelopment, scaleIn, and translation. These utilize the spring parameters defined here along with the interpolate helper in some cases like translation and logoRotation to describe the animation properties.

To define the transform of the logo, the scale property utilizes the frame of the video that is fetched from the useCurrent hook and the translation and logoRotation properties that are defined with spring.

We have three Arc components in the Logo component that are rotated 60 degrees away from each other to create the final appearance of mimicking electrons circling the center of the atom.

Title.tsx contains the title text, “Welcome to Remotion” that appears after the logo. It takes in the titleText then splits it and renders in the words one by one:

import {spring, useCurrentFrame, useVideoConfig} from 'remotion';
export const Title: React.FC<{
    titleText: string;
    titleColor: string;
}> = ({titleText, titleColor}) => {
    const videoConfig = useVideoConfig();
    const frame = useCurrentFrame();
    const text = titleText.split(' ').map((t) => ` ${t} `);
    return (
        <h1
            style={{
                fontFamily: 'SF Pro Text, Helvetica, Arial',
                fontWeight: 'bold',
                fontSize: 100,
                textAlign: 'center',
                position: 'absolute',
                bottom: 160,
                width: '100%',
            }}
        >
            {text.map((t, i) => {
                return (
                    <span
                        key={t}
                        style={{
                            color: titleColor,
                            marginLeft: 10,
                            marginRight: 10,
                            transform: `scale(${spring({
                                fps: videoConfig.fps,
                                frame: frame - i * 5,
                                config: {
                                    damping: 100,
                                    stiffness: 200,
                                    mass: 0.5,
                                },
                            })})`,
                            display: 'inline-block',
                        }}
                    >
                        {t}
                    </span>
                );
            })}
        </h1>
    );
};

The Title component takes in two props: titleText and textColor. The text function maps the words in titleText onto an array. We then use this array to render the words in following order in a spring animation applied to the style object of the span with the text.map function.

Similar to Title, the Subtitle component also animates some text, but in this case, it is a single unit of smaller text animated together with the interpolate function.

import {interpolate, useCurrentFrame} from 'remotion';
import {COLOR_1} from './config';
export const Subtitle: React.FC = () => {
    const frame = useCurrentFrame();
    const opacity = interpolate(frame, [0, 30], [0, 1]);
    return (
        <div
            style={{
                fontFamily: 'Helvetica, Arial',
                fontSize: 40,
                textAlign: 'center',
                position: 'absolute',
                bottom: 140,
                width: '100%',
                opacity,
            }}
        >
            Edit{' '}
            <code
                style={{
                    color: COLOR_1,
                }}
            >
                src/Video.tsx
            </code>{' '}
            and save to reload.
        </div>
    );
};

Working with sequences and compositions

Now that we have our base components set up, we need to make use of them in sequences and compositions to create our final video. Sequences, as mentioned above, allow us to time-shift components and lay them out in a sort of timeline to create a final video. You can see this in action inside HelloWorld.tsx where the three components above are each placed in a sequence to create a final clip with all three animating at different times.

import {interpolate, Sequence, useCurrentFrame, useVideoConfig} from 'remotion';
import {Logo} from './HelloWorld/Logo';
import {Subtitle} from './HelloWorld/Subtitle';
import {Title} from './HelloWorld/Title';
export const HelloWorld: React.FC<{
    titleText: string;
    titleColor: string;
}> = ({titleText, titleColor}) => {
    const frame = useCurrentFrame();
    const videoConfig = useVideoConfig();
    const opacity = interpolate(
        frame,
        [videoConfig.durationInFrames - 25, videoConfig.durationInFrames - 15],
        [1, 0],
        {
            extrapolateLeft: 'clamp',
            extrapolateRight: 'clamp',
        }
    );
    const transitionStart = 25;
    return (
        <div style={{flex: 1, backgroundColor: 'white'}}>
            <div style={{opacity}}>
                <Sequence from={0} durationInFrames={videoConfig.durationInFrames}>
                    <Logo transitionStart={transitionStart} />
                </Sequence>
                <Sequence from={transitionStart + 10} durationInFrames={Infinity}>
                    <Title titleText={titleText} titleColor={titleColor} />
                </Sequence>
                <Sequence from={transitionStart + 50} durationInFrames={Infinity}>
                    <Subtitle />
                </Sequence>
            </div>
        </div>
    );
};

You can see each Sequence wraps around a component, and the sequences each have a different from value. This depicts the offset on the timeline of the combined clip, which will be comprised of all three animated components. So, while it is possible to individually create videos from each of the components, HelloWorld makes use of sequences to bring them together into a single clip.

The next step is to use the Composition to make our clips available for Remotion to view in the player and render into .mp4 files. This is done in the Video.tsx file as follows:

  • Import the components to be registered
  • Feed each component to a Composition component under the component prop
  • Specify the rest of the video properties of each composition and include any additional props
  • Wrap all the compositions in a single React fragment and export it to be registered in the root component
import {Composition} from 'remotion';
import {HelloWorld} from './HelloWorld';
import {Logo} from './HelloWorld/Logo';
import {Subtitle} from './HelloWorld/Subtitle';
export const RemotionVideo: React.FC = () => {
    return (
        <>
            <Composition
                id="HelloWorld"
                component={HelloWorld}
                durationInFrames={150}
                fps={30}
                width={1920}
                height={1080}
                defaultProps={{
                    titleText: 'Welcome to Remotion',
                    titleColor: 'black',
                }}
            />
            <Composition
                id="Logo"
                component={Logo}
                durationInFrames={200}
                fps={30}
                width={1920}
                height={1080}
            />
            <Composition
                id="Title"
                component={Subtitle}
                durationInFrames={100}
                fps={30}
                width={1920}
                height={1080}
            />
        </>
    );
};

Registering the root component

With all the compositions and sequences ready, we now have to register them in the root component to finalize our setup. This step is covered inside the index.tsx file in just three lines:

import {registerRoot} from 'remotion';
import {RemotionVideo} from './Video';

registerRoot(RemotionVideo);

We simply import the fragment component we created in Video.tsx into the file and pass it into the registerRoot component. Everything is good to go!

Viewing and exporting the video

When you run the application, you will see a video player in your browser that is just like the screenshot below where the various compositions are listed and playable individually.

Viewing Exporting Remotion Video Individual Compositions

In the Remotion player, you can play and pause your videos and view where each animation fits in the timeline. You can also adjust the fit of the video view to your liking.

If you are satisfied with your final video, render and export it into a video file for external use by running:

npm run build

or

yarn build

This runs the underlying Remotion CLI command as specified in package.json:

npx remotion render src/index.tsx HelloWorld out.mp4

This command has a number of options that can be used to add to the specified configuration of the video, like codecs and formats. You can read more about those options in the CLI reference.

The future of Remotion

While still fairly new, Remotion provides an interesting and programmatic way to create video. Developers can integrate Remotion into React applications to add inputs that dynamically influence the video output. More great things are on the horizon for Remotion, like proper audio support, which I deliberately left out for now as it is still under development. You can find more information about Remotion’s audio API under the audio section of the docs.

Remotion is open source, so the creator Jonny Burger has created an opportunity for the dev community to contribute towards improving what is already an exciting package. The licensing around Remotion makes it a great choice for many personal or small scale projects. It is free to use for individuals as well as non-profit and for-profit organizations with three or fewer employees. There are also various company plans you can read about here if you wish to make use of Remotion in an organization that is ineligible for the free license.

 

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Austin Roy Omondi Live long and prosper 👌

Leave a Reply