Learn Remotion: Create Animated Video with HTML, CSS & React
Creating a video with text, animations, colors, and shapes traditionally requires specialist software and potentially years of motion graphic training. What if you could use your existing toolset, training, and background in web development to do the same?
Remotion allows JavaScript developers to reuse the skills and knowledge they’ve built up to create complex animated videos using HTML, CSS, and React JS. If you can render text with React, animate a CSS style, or organize HTML content, you can now create and edit your own videos using solely code, with no video editing applications or software required.
In this article, I’ll go through the process of using Remotion and talk you through my discoveries as we go along.
You can find the complete code for this article on GitHub.
Remotion: What, and Why?
Remotion is a video creation toolkit for React created by Jonny Burger. This toolkit allows anyone with a basic understanding of React, HTML or CSS to create animated videos using code.
In the video creation space there’s currently a high barrier to entry due to the required software and training needed to use and master these tools. By utilizing JavaScript developers’ existing toolkits, this opens the video creation space to a wider user base. As videos become code, we can leverage existing patterns to allow for more effective video creation — such as automated generation based on parameters or build pipelines.
Getting Started
Thankfully, Remotion has a quick and easy setup process with a Yarn and npm starter kit. For this example, we’ll be sticking with npm as the build and run tool. Before we get started, you’ll need to have Node and npm installed. (For assistance, you can follow this guide to installing Node and npm.) Also check the Remotion installation guide if you’re on Linux, as you may need to install additional tools. After getting Node and npm set up, let’s create a new project by running this code:
npm init video
This will prompt you for a project name, which is also used as the directory name. In our case, it will be my-video
. Once entered, we can move into the my-video
directory and start the default video project by running the start script as follows:
cd my-video
npm start
After running the start command, the browser should automatically open. If not, open the browser and navigate to http://localhost:3000/. This feature allows you to watch and debug the video you’re creating. The player has controls that include a play button, which allows you to preview the video content. It may also be useful to start by looking at the code for the demo example, which Remotion provides as a guide for how to build your own video.
Hello, World!
We’re going to create our own video animating the text “Hello, World!”, to get to grips with the components and processes supplied in Remotion.
First of all, let’s delete the existing example code (everything in the src
folder), as we want to start afresh. Then, let’s create a Demo
directory under the src
directory, which will hold and manage all our video work for this project. Inside the Demo
directory, create a Demo.js
file:
import {Composition, interpolate, Sequence, useCurrentFrame, useVideoConfig} from 'remotion';
import Title from './Title';
import Hello from './Hello';
import "./demo.css";
const Demo = () => {
return (
<div className="main-container">
{/* TODO: add video content */}
</div>
);
};
export const DemoVideo = () => {
return (
<Composition
id="Demo"
component={Demo}
durationInFrames={150}
fps={30}
width={1920}
height={1080}
defaultProps={{
titleText: 'This is my first Remotion video',
titleColor: 'blue',
}}
/>
)
}
The Demo
file exports our video code. As you can see, we can create a Demo
component that will hold all the visual elements in our video. We can then export a component that renders the Composition
of our video. The Composition
component allows us to define some basic properties such as the width and height of the video clip, the FPS (frames per second), and the feature that will be rendered. We also import some utils and hooks from Remotion and some additional components that we will create soon.
Currently our Demo
component is empty, but let’s add some elements to our video:
const Demo = ({titleText, titleColor}) => {
const frame = useCurrentFrame();
const videoConfig = useVideoConfig();
const totalOpacity = interpolate(
frame,
[videoConfig.durationInFrames - 25, videoConfig.durationInFrames - 15],
[1, 0],
{
extrapolateLeft: 'clamp',
extrapolateRight: 'clamp',
}
);
return (
<div className="main-container">
<div style={{opacity: totalOpacity}}>
<Sequence from={0} durationInFrames={videoConfig.durationInFrames / 2}>
<Hello/>
</Sequence>
<Sequence from={35} durationInFrames={Infinity}>
<Title titleText={titleText} titleColor={titleColor} />
</Sequence>
</div>
</div>
);
};
We’ve added a lot to the file, so let’s break this all down.
Firstly in our render section, we can see from the file that we can now return a div with opacity styles, allowing us to fade elements in and out at the start and end of the video. For the opacity value, we use a Remotion helper. The interpolate
function allows you to better define animations and map the animation values to the current frame and the video duration. In this example, we pass in the current frame. The function will get called on each frame generated. The input range is calculated from the duration of the video and the output value ranges from 0 to 1, as this is the range for the opacity CSS value. As the Demo
component is re-rendered for each frame, the interpolate
function is called each time and will return the appropriate opacity value.
Next, we can begin rendering different visual elements on the video screen. In this example, we want the text “Hello, World!” to fade into view then disappear and the text “This is my first Remotion video” to then appear afterwards. To do so, we can render multiple Sequence
components.
A Sequence
component is another Remotion feature that allows us to define how and when a component renders in a video and for how long. This is great for building complex videos where you want to add timed or layered elements, such as this example. Each Sequence
will also show in the browser player and be titled based on the child component name. This allows you to monitor the video you’re generating and the effects you’re adding to it in real time.
Remotion also provides some useful React hooks, and in this example we make use of the useCurrentFrame
and useVideoConfig
hooks. useCurrentFrame
will return the current frame that the video is on, which is useful for animations and implementing actions based on where the current position of the video playback is. useVideoConfig
will return an object with different values, such as:
- width: the width of the video — useful for positioning elements in the video
- height: the height of the video — useful for positioning elements in the video
- FPS: frames per second — which can be used to determine the speed of animation or element movement
- durationInFrames: the total length of the video in frames — which can be used to calculate animations or times for
Sequence
show and hide.
In our case, as mentioned, firstly we want our Hello
component, the text “Hello, World!”, to appear at the start of the video and remain on screen for half of the time. We do this by using the videoConfig.duration
value, which we’ve calculated from the useVideoConfigHook
.
For the second Sequence
, we want our Title
component text, “This is my first Remotion video”, to appear after 35 frames and stay on screen for the full duration of the video. To achieve this, for From
we enter 35
, and for durationInFrames
we enter Infinity
.
To style our demo component, we can use CSS along with inline styles. When using CSS, we want to apply styles to the whole video, so let’s create a demo.css
file that will hold any styles that cover the whole video area. In our example, we want to make the background white and align out items with Flexbox:
.main-container {
flex: 1;
background-color: white;
}
Now let’s delve deeper into these elements we’re rendering.
Rendering React Components in an Animation
The Hello
component is going to be a basic React component that renders an H1 tag with some inline styles applied and the text “Hello, World!” This is the simplest form of a component we can render. For simplicity’s sake, we can use inline styles. But because this is React, you could also import styles from a CSS file and use a class name, styled-components, CSS modules, or any styling pattern you’re already familiar with as an alternative. Let’s create the Hello
component. Inside the Demo
folder, create a new file Hello.js
:
const Hello = () => {
return (
<h1
style={{
fontFamily: 'SF Pro Text, Helvetica, Arial',
fontWeight: 'bold',
fontSize: 100,
textAlign: 'center',
position: 'absolute',
bottom: 500,
width: '100%'
}}
>
Hello, World!
</h1>
);
};
export default Hello;
Now, let’s take a look at a more complex example. In the Demo
folder, create a new file called Title.js
and add in the component code below:
import {spring, useCurrentFrame, useVideoConfig} from 'remotion';
const Title = ({titleText, titleColor, bottom}) => {
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: 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>
);
};
export default Title;
We have a lot going on here, so again let’s break down what’s going on.
Remotion has first-class support for TypeScript. This is not required, but it can make the development process better, as you’ll get more detailed autocomplete suggestions in your IDE. However, to make this example more beginner friendly, we’ll just use normal JavaScript.
Our component takes in two props — titleText
and titleColor
— which will be used later in our render method. This shows that, using React, we can still pass props around the application, therefore making our video elements reusable and dynamic. You may have noticed that, in our Demo
component, we passed props in from the Composition
component. This shows the power of React in action. We can pass in props from the very top of the React application, making the video responsive, and meaning you could change one block of text to make a new video or to change the whole video context.
After we’ve accessed our props in the Title
component, we call the Remotion hooks again to get the videoConfig
and frame data. The Title
component then breaks the text prop passed and renders it one word at a time using a combination of a map and CSS transform. Here we have the opportunity to use another built-in helper function. Spring
takes in values to help generate a smooth output for the animation value. We pass the main video config’s FPS to control the speed of the animation. The frame value controls when the animation starts, and finally we pass in additional configuration options to control the smoothness of the animation.
After we have all our video components created and ready to go, we need to finally create an index.js
file in the root of the src
folder and add the following content:
import {registerRoot} from 'remotion';
import { DemoVideo } from './Demo/Demo';
registerRoot(DemoVideo);
The index file imports the registerRoot
function from Remotion, which allows us to render the video content. Think of this as the ReactDOM render function but for Remotion. Then we pass our DemoVideo
component to registerRoot
, which will visualize the rendered video in either development or build modes.
We’re now importing the Demo video that will get rendered by Remotion.
Now that we have all of these features combined, we have a fully animated video that provides one example of the different components and helper functions supplied by Remotion.
We can run the video from the root of the project with the following command:
./node_modules/.bin/remotion preview src/index.js
Or, you can update the start
script in the package.json
file:
- "start": "remotion preview src/index.tsx",
+ "start": "remotion preview src/index.js",
Then run the animation using npm start
.
Building the StarWars Animation
Now we have a basic understanding of Remotion and the different components on offer, we can challenge ourselves and have a little bit more fun. Let’s build our own version of the iconic Star Wars title intro screen. We want to be able to render a sparkly star background with bright yellow text that scrolls up the screen. We can use the knowledge we have from our “Hello, World!” example as a starting off point.
Let’s start by creating the files we need. In the src
folder, create a starWarsIndex.js
file and a StarWars
folder. In the StarWars
folder, create a further four files: starWars.js
, starWars.css
, starsBackground.js
, starsBackground.css
.
When you’re done, the src
folder should look like this:
.
├── Demo
│ └── Files from "Hello, World!" demo
├── index.js
├── StarWars
│ ├── starsBackground.css
│ ├── starsBackground.js
│ ├── starWars.css
│ └── starWars.js
└── starWarsIndex.js
Creating the Scrolling Text
First, we start with a StarWarsVideo
component, which will render a Composition
component to define the video properties. As the scrolling text is longer, we define a higher durationInFrames
number.
Add the following to src/starWarsIndex.js
:
import {registerRoot, Composition, Sequence} from 'remotion';
import {useEffect, useState} from 'react'
import { LoremIpsum } from 'lorem-ipsum';
import Stars from './StarWars/starsBackground';
import StarWars from './StarWars/starWars';
const StarWarsVideo = () => {
const [textBlocks, setTextBlocks] = useState([]);
useEffect(() => {
setTextBlocks([
lorem.generateSentences(5),
lorem.generateSentences(5),
lorem.generateSentences(5),
])
}, [])
return (
<>
<Composition
id='star-wars'
component={Video}
durationInFrames={580}
fps={30}
width={1920}
height={1080}
defaultProps={{ textBlocks }}
/>
</>
);
};
registerRoot(StarWarsVideo);
We also need to define some React state. In this Star Wars example, we’re going to make use of React state and props to generate random text each time we reload the video. Using the lorem-ipsum npm module we can make the text responsive and different every time it’s generated.
Let’s install the module:
npm i lorem-ipsum
Then, in the same file add:
// import statements
const lorem = new LoremIpsum({
sentencesPerParagraph: {
max: 8,
min: 4
},
wordsPerSentence: {
max: 16,
min: 4
}
});
const Video = ({ textBlocks }) => {
return (
<div>
<Sequence from={0} durationInFrames={Infinity}>
<Stars/>
</Sequence>
<Sequence from={0} durationInFrames={Infinity}>
<StarWars
textBlocks={textBlocks}
/>
</Sequence>
</div>
)
}
const StarWarsVideo = () => { ... };
registerRoot(StarWarsVideo);
For the Sequence
components, we can layer up two main components for the video. The Stars
component will render the starry background, and the StarWars
component will render the scrolling yellow text. The star background is using standard CSS animation and transforms to display stars. The StarWars
component is where we start getting back into Remotion-based animations. We can use the Spring
helper function to control the top position, rotate, and translate CSS transform properties to animate the scrolling of the text based on the current time in the video.
Add the following to src/starWars.js
:
import React from 'react';
import './starWars.css';
import {spring, useCurrentFrame} from 'remotion';
const StarWars = ({ textBlocks }) => {
const frame = useCurrentFrame()
const fps = 6000;
const top = spring({
frame,
from: 0,
to: -6000,
fps,
})
const rotate = spring({
frame,
from: 20,
to: 25,
fps,
})
const translateZ = spring({
frame,
from: 0,
to: -2500,
fps,
})
return (
<>
<div className="fade"/>
<section className="star-wars">
<div
className="crawl"
style={{
top: `${top}px`,
transform: `rotateX(${rotate}deg) translateZ(${translateZ}px)`
}}
>
<div className="title">
<p>Episode IV</p>
<h1>A New Hope</h1>
</div>
{
textBlocks.map((block, index) => {
return (
<p key={index}>{block}</p>
)
})
}
</div>
</section>
</>
)
}
export default StarWars;
Note that we’re rendering out the textBlocks
prop, which will be our random text each time we generate the video.
All that remains now is to create the Stars
component. Add the following to src/starsBackground.js
:
import React from 'react';
import './starsBackground.css';
const Stars = () => {
return (
<>
<div id='stars'/>
<div id='stars2'/>
<div id='stars3'/>
</>
);
}
export default Stars;
Also add the following styles to src/starsWars.css
:
.fade {
position: relative;
width: 100%;
min-height: 60vh;
top: -25px;
z-index: 1;
}
.star-wars {
display: flex;
justify-content: center;
position: relative;
height: 800px;
color: #feda4a;
font-family: 'Pathway Gothic One', sans-serif;
font-size: 500%;
font-weight: 600;
letter-spacing: 6px;
line-height: 150%;
perspective: 400px;
text-align: justify;
}
.crawl {
position: relative;
top: 9999px;
transform-origin: 50% 100%;
}
.crawl > .title {
font-size: 90%;
text-align: center;
}
.crawl > .title h1 {
margin: 0 0 100px;
text-transform: uppercase;
}
The src/starsBackground.css
is too large to list here. Please grab its contents from the GitHub repo and add it to your own project.
This will result in a fully functional Stars Wars intro video, created using only code, and no video editing software.
The last step to get the StarWars example running is to add the following script to the package.json
file:
"start:starwars": "remotion preview src/starWarsIndex.js",
And there we have it — a StarWars intro, fully coded in React.
If you want to insert the actual StarWars text, grab it from here and alter the useEffect
method call in src/starWarsIndex.js
:
useEffect(() => {
setTextBlocks([
- lorem.generateSentences(5),
- lorem.generateSentences(5),
- lorem.generateSentences(5),
+ "It is a period of civil war. Rebel spaceships...",
+ "Pursued by the Empire’s sinister agents..."
])
}, [])
Awesome!
Conclusion
We’ve made two examples showcasing the power of Remotion, each of varying complexity. However, this is just scratching the surface of how capable Remotion is. Below are some of the other features that Remotion provides that we didn’t cover.
Don’t forget, all of the code is available on GitHub.
Data Fetching
To add a reactive element to your videos, you can fetch data to help populate the content at build time. Remotion provides hooks to handle the fetching of data, such as continueRender
. delayRender
can also be used in circumstances to pause the rendering of the video content until the data has been fetched. These features could be used to generate videos based on data imputed into a database. Or they can pull data from the server — for example, creating an intro video for a blog post and pulling the blog’s title and hero image from a server.
Parameterized Rendering
From the examples we used earlier, we could control the flow of props getting passed into video elements. This allows the videos to be responsive. However, this requires code changes each time. With parameterized rendering, you can pass the data in as part of the build command. This means that you could, as part of a CI/CD flow, generate videos depending on passed-in data — for example, auto-generating onboarding videos with the person’s name and title passed in as props.
Asset Imports
You don’t need to create elements only using CSS. You can also import other assets such as images, existing videos, and audio files into your project.
There are many more additional features, and new features are being released regularly in Remotion. Remotion is a fully developed project and is making big steps in the JavaScript-based video space. This is a very powerful tool with a realm of possibilities yet to uncover.
If you’ve used Remotion to build something cool, let me know on Twitter.