Intro to Remotion Animations
This article is designed to help you get started with animations in the Remotion React library. Unlike traditional web development, where you might rely on CSS animations to bring elements to life, Remotion operates in a different environment. Here, each frame of your video is rendered individually, rather than applying real-time animations as you would in a web browser.
As a result, creating animations in Remotion requires a different approach and logic. Instead of relying on CSS, you'll be working with JavaScript and React to define how each frame of your animation is rendered, allowing for precise control over every aspect of your video. This guide will walk you through the basics, helping you understand how to approach animations in this unique context and get the most out of Remotion.
Setup
This article is focused primarily on the animation aspects of Remotion. The example will include a few function calls that depend on a complete project setup, but these are intended solely to streamline the code for demonstration purposes.
If you'd like to explore the full project setup, you can visit the repository, where you'll also find the code snippets featured in this article.
First Scene without animation
In this initial example, we'll showcase a static rect
element that remains stationary throughout the composition:
import { useColorPalette } from "../../../util/useColorPalette"
/**
* First scene illustrating the project setup.
*
* Key code elements are highlighted here. For the full setup,
* you can refer to the GitHub repository.
*/
export default () => {
// Retrieve the background color from a globally configured
// color palette in this example.
const { primary } = useColorPalette()
// Rect is an SVG element. This example can render SVGs because
// a parent element has already opened an <svg /> tag.
return (
<rect
x={100}
y={100}
width={100}
height={100}
fill={primary}
/>
)
}
As of now, there isn't much to see.
First Animation
In Remotion, the key concept is that an output image is generated for each frame, independently of other frames.
This means that your logic should focus on creating a pure function that determines the output for the current frame without relying on previous frames. For example, you can't simply say "move 20 pixels to the right compared to the previous frame", because the output for the previous frame would also need to be recalculated. Instead, your implementation should provide an offset that is based solely on the current frame.
Let’s begin with a basic animation where the rect element moves 20 pixels to the right for each frame:
import { useColorPalette } from "../../../util/useColorPalette"
import { useCurrentFrame } from "remotion"
export default () => {
const { primary } = useColorPalette()
// Determine the position based on the current frame index
// Here, the value changes between frames 0 and 50
const currentFrame = useCurrentFrame()
const speed = 20
const x = speed * currentFrame + 100
return (
<rect
x={x}
y={100}
width={100}
height={100}
fill={primary}
/>
)
}
First Animation with fps
While the previous example works, it's not ideal because it relies on an external configuration, specifically the fps (frames per second) value.
If the fps were changed from 30 to 60, the movement would become twice as fast, since twice as many frames would be rendered in the same amount of time.
To avoid this issue, it's better to create animations that are independent of the fps:
import { useColorPalette } from "../../../util/useColorPalette"
import {
useCurrentFrame,
useVideoConfig,
} from "remotion"
export default () => {
const { primary } = useColorPalette()
const currentFrame = useCurrentFrame()
const { fps } = useVideoConfig()
const elapsedSeconds = currentFrame / fps
// Adjusted the earlier speed of 20 units for 30fps
const speed = 600
const x = speed * elapsedSeconds + 100
return (
<rect
x={x}
y={100}
width={100}
height={100}
fill={primary}
/>
)
}
This approach should produce the same animation, regardless of the fps setting:
Percent-based animations
When working with animations, it’s often more convenient to define a range of values rather than specifying a speed. For instance, if you want to animate a rectangle from a position of 100 to 1100, you can set these start and end values, and let the speed be calculated accordingly.
To achieve this, you can use Remotion's built-in interpolate
function:
import { useColorPalette } from "../../../util/useColorPalette"
import {
interpolate,
useCurrentFrame,
useVideoConfig,
} from "remotion"
export default () => {
const { primary } = useColorPalette()
const currentFrame = useCurrentFrame()
const { fps } = useVideoConfig()
// Move the rectangle from 100 to a 1000 (left side's position)
const [startPos, endPos] = [100, 1000]
const x = interpolate(
currentFrame,
// inputRange
[0, fps],
// outputRange
[startPos, endPos],
{
// It means it should stop at the endPos after reaching it
extrapolateRight: "clamp",
},
)
return (
<rect
x={x}
y={100}
width={100}
height={100}
fill={primary}
/>
)
}
And here is the produced result:
Easings
While the previous animation is functional, it’s quite basic and lacks the visual appeal that often makes animations engaging. The rectangle moves at a constant speed, which might not be enough to capture attention.
To create more dynamic and interesting animations, you can use easings. Easings allow the transition between start and end values to follow a more complex curve rather than a simple linear path. This enables you to easily create various effects, such as speeding up, slowing down, or even bouncing animations.
import { useColorPalette } from "../../../util/useColorPalette"
import {
Easing,
interpolate,
useCurrentFrame,
useVideoConfig,
} from "remotion"
export default () => {
const { primary } = useColorPalette()
const currentFrame = useCurrentFrame()
const { fps } = useVideoConfig()
// Animate the rectangle from 100 to a 1000
const [startPos, endPos] = [100, 1000]
const x = interpolate(
currentFrame,
// inputRange
[0, fps],
// outputRange
[startPos, endPos],
{
extrapolateRight: "clamp",
// Learn more about the easing functions at:
// https://www.remotion.dev/docs/easing
//
// This will give an animation
// that slows down at the end
easing: Easing.out(Easing.cubic),
},
)
return (
<rect
x={x}
y={100}
width={100}
height={100}
fill={primary}
/>
)
}
Here’s the improved animation:
Repeating animations
There are times when you may need to create looping animations, whether it’s for a repeating background animation with the main action in the foreground, or for looping an entire video, such as for TikTok.
Let's use a calculateAnimation
function where this logic can be implemented later:
import { useColorPalette } from "../../../util/useColorPalette"
import {
Easing,
interpolate,
useCurrentFrame,
useVideoConfig,
} from "remotion"
export default () => {
const { primary } = useColorPalette()
const currentFrame = useCurrentFrame()
const { fps } = useVideoConfig()
const x = calculateAnimation({
currentFrame,
loopFrameCount: fps,
outputRange: [100, 1000],
})
return (
<rect
x={x}
y={100}
width={100}
height={100}
fill={primary}
/>
)
}
For an initial approach, here's an example of creating a simple repeating animation using the modulo
(%
) operator.
This example does not yet include the reverse animation, so it is not looping yet.
function calculateAnimation({
currentFrame,
loopFrameCount,
outputRange,
}: {
currentFrame: number
loopFrameCount: number
outputRange: readonly [number, number]
}): number {
const remainder =
currentFrame % loopFrameCount
return interpolate(
remainder,
// inputRange
[0, loopFrameCount],
outputRange,
{
// Setting it to "wrap" would yield the same result,
// making the remainder calculation unnecessary.
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
},
)
}
Here is the resulting animation:
Looping animation
To create a looping animation, you'll need to handle both a forward and a reverse animation. The key challenge is ensuring that at the end of the reverse animation, the scene returns to the exact state it was in at the beginning of the forward animation. Additionally, these animations need to run sequentially, alternating between the forward and reverse phases.
function calculateAnimation({
currentFrame,
loopFrameCount,
outputRange,
}: {
currentFrame: number
loopFrameCount: number
outputRange: readonly [number, number]
}): number {
const remainder =
currentFrame % loopFrameCount
// A value of either 0 (indicating forward)
// or 1 (indicating backward)
const direction =
Math.floor(
currentFrame / loopFrameCount,
) % 2
const isAnimatingBackward = direction === 1
const interpolatedFrame = isAnimatingBackward
? loopFrameCount - remainder
: remainder
return interpolate(
interpolatedFrame,
// inputRange
[0, loopFrameCount],
outputRange,
{
extrapolateRight: "clamp",
// Invert the easing function for the reverse animation
easing: isAnimatingBackward
? Easing.cubic
: Easing.out(Easing.cubic),
},
)
}
With that said, the first looping animation is ready:
Summary
In this article, we took a first look at using the Remotion React library to create animations. We covered essential concepts like frame-by-frame rendering, making animations independent of FPS, and using easing functions to add more dynamic movement. We also walked through how to create looping animations by managing both forward and reverse sequences.
For the complete project setup and more resources, visit the GitHub repository.
To jumpstart your next project, check out our typography template.