Quickstart
In this guide, we'll create a simple animation using Motion Canvas.
Prerequisites
Make sure that Node.js version 16 or greater is installed on your machine.
You can run the following command to check if Node.js is already installed:
node -v
Creating a new project
Run the following command in order to scaffold a new Motion Canvas project (If the command fails check out the troubleshooting section below):
npm init @motion-canvas@latest
Answer the prompts to name your project and select which language you would like to use; either TypeScript or plain JavaScript. We recommend using TypeScript in your first project, since that's the language we're using throughout this documentation.
You'll also be asked to choose how you'd like to export your animations. Motion Canvas comes with a built-in Image sequence exporter - perfect if you want to import your animations into a video editor. However, if you want to directly produce a finished video, you can choose to install the Video (FFmpeg) exporter. Don't worry, you can select multiple exporters, and you can always add more later.
To complete the scaffolding process, you'll need to run the following commands:
- Change Directory (
cd
) to the project root (You should replace<project-path>
with the path you picked during scaffolding):cd <project-path>
- Install necessary dependencies:
npm install
- Start the editor:The editor can be accessed by visiting http://localhost:9000/. We'll use it to preview our animation, although for now there's not much to see.
npm start
Programming an animation
The scaffolding command will create several files for you, but for now we're
going to focus on src/scenes/example.tsx
, which is where we can add our
animations. Open example.tsx
in a text editor, and replace all code in the
file with the following snippet.
import ...
export default makeScene2D(function* (view) {
const myCircle = createRef<Circle>();
view.add(
<Circle
ref={myCircle}
// try changing these properties:
x={-300}
width={140}
height={140}
fill="#e13238"
/>,
);
yield* all(
myCircle().position.x(300, 1).to(-300, 1),
myCircle().fill('#e6a700', 1).to('#e13238', 1),
);
});
Now save the file. Any changes you make are automatically picked up and reflected in the preview.
You should see a red circle in the preview pane at the top right of the web application. Press the play button to see the circle animate across the screen.
Explanation
Each video in Motion Canvas is represented by a project configuration object. In
our example, this configuration is declared in src/project.ts
:
import {makeProject} from '@motion-canvas/core';
import example from './scenes/example?scene';
export default makeProject({
scenes: [example],
});
When creating a project, we need to provide it with an array of scenes to
display. In this case, we use only one scene imported from
src/scenes/example.tsx?scene
.
Note the ?scene
at the end - it's required to transform the imported module
into a proper scene. Among other things, it makes it possible to dynamically
refresh the preview whenever you modify the scene. The editor will not work
without it.
A scene is a set of elements displayed on the screen and an animation that governs them. The most basic scene looks as follows:
import {makeScene2D} from '@motion-canvas/2d';
export default makeScene2D(function* (view) {
// animation
});
makeScene2D()
takes a function generator and turns it into a scene which we
then import in our project file. The function generator describes the flow of
the animation, while the provided view
argument is used to add elements to the
scene.
You can learn more about scenes, nodes, and this XML-like syntax in the
scene hierarchy section. For now, what's important is that,
in our example, we add an individual <Circle/>
node to our scene. We make it red, set its width and height as 140
pixels and
position it 300
pixels left from the center:
view.add(
<Circle
ref={myCircle}
x={-300}
width={140}
height={140}
fill="#e13238"
/>,
);
To animate our circle we first need to
grab a reference to it. That's the purpose of the
createRef
function. We use it to create a
reference and pass it to our circle using the
ref
attribute:
const myCircle = createRef<Circle>();
view.add(
<Circle
ref={myCircle}
x={-300}
width={140}
height={140}
fill="#e13238"
/>,
);
We then access the circle through myCircle()
and animate its properties:
yield *
all(
myCircle().fill('#e6a700', 1).to('#e13238', 1),
myCircle().position.x(300, 1).to(-300, 1),
);
This snippet may seem a bit confusing so let's break it down.
Each property of a node can be read and updated throughout the animation. For
example, in the circle above we defined its fill
property as '#e13238'
:
<Circle
ref={myCircle}
x={-300}
width={140}
height={140}
fill="#e13238"
/>
Using our reference we can now retrieve this property's value:
const fill = myCircle().fill(); // '#e13238'
We can also update it by passing the new value as the first argument:
myCircle().fill('#e6a700');
This will immediately update the color of our circle. If we want to transition to a new value over some time, we can pass the transition duration (in seconds) as the second argument:
myCircle().fill('#e6a700', 1);
This creates a tween animation that smoothly changes the fill color over one second.
Animations in Motion Canvas don't play on their own, we need to explicitly tell them to. This is why scenes are declared using generator functions - they serve as a description of how the animation should play out. By yielding different instructions we can tell the scene animation to do different things.
For example, to play the tween we created, we can do:
yield * myCircle().fill('#e6a700', 1);
This will pause the generator, play out the animation we yielded, and then continue.
To play another animation, right after the first one, we can simply write
another yield*
statement:
yield * myCircle().fill('#e6a700', 1);
yield * myCircle().fill('#e13238', 1);
But since we're animating the same property, we can write it in a more compact way:
yield * myCircle().fill('#e6a700', 1).to('#e13238', 1);
In our example, aside from changing the color, we also move our circle around. We can try doing it the same way we animated the color:
yield * myCircle().fill('#e6a700', 1).to('#e13238', 1);
yield * myCircle().position.x(300, 1).to(-300, 1);
This works, but the position will start animating after the fill color. To
make them happen at the same time, we use the all
function:
yield *
all(
myCircle().fill('#e6a700', 1).to('#e13238', 1),
myCircle().position.x(300, 1).to(-300, 1),
);
all
takes one or more animations and merges them together. Now they'll
happen at the same time. The animation flow section goes into more
depth about generators and flow functions such as all
.
This brings us back to our initial example:
import {Circle, makeScene2D} from '@motion-canvas/2d';
import {all, createRef} from '@motion-canvas/core';
export default makeScene2D(function* (view) {
const myCircle = createRef<Circle>();
view.add(
<Circle
ref={myCircle}
x={-300}
width={140}
height={140}
fill="#e13238"
/>,
);
yield* all(
myCircle().position.x(300, 1).to(-300, 1),
myCircle().fill('#e6a700', 1).to('#e13238', 1),
);
});
Troubleshooting
npm init @motion-canvas@latest
fails to execute.
There was a bug in npm causing the
above command to fail. It got fixed in version 8.15.1
. You can follow
this guide to
update your npm. Alternatively, you can use the following command instead:
npm exec @motion-canvas/create@latest
npm install
fails with code ENOENT
If npm install
fails with the following error:
npm ERR! code ENOENT
npm ERR! syscall open
npm ERR! path [path]\package.json
npm ERR! errno -4058
npm ERR! enoent ENOENT: no such file or directory, open '[path]\package.json'
npm ERR! enoent This is related to npm not being able to find a file.
npm ERR! enoent
Make sure that you're executing the command in the correct directory. When you
finish bootstrapping the project with npm init
, it will display three
commands:
cd [path]
npm install
npm start
Did you run the cd
command to switch to the directory containing your project?
I moved the camera too far away and can't find the preview (The preview is in black)
You can press 0
to refocus the camera on the preview.
The animation ends abruptly or does not start at the beginning.
Make sure the playback range selector in the timeline starts and ends where you expect it to, e.g., at the beginning and end of your animation. The range selector is a gray bar in the time axis of your timeline. When you move your mouse over it, six dots will appear allowing you to manipulate it.
File watching does not work on Windows Subsystem for Linux (WSL) 2
When running Vite on WSL2, file system watching does not work if a file is edited by Windows applications.
To fix this, move the project folder into the WSL2 file system and use WSL2 applications to edit files. Accessing the Windows file system from WSL2 is slow, so this will improve performance.
For more information view the Vite Docs.