Skip to main content

Scene hierarchy

Scenes are collections of nodes displayed in your animation. They're organized in a tree hierarchy, with the scene view at its root. This concept is similar to the Document Object Model used to represent HTML and XML documents.

Here's an example of a simple scene hierarchy together with its object representation:

view.add(
<>
<Circle />
<Layout>
<Rect />
<Txt>Hi</Txt>
</Layout>
</>,
);

Each node is an instance of a class extending the base Node class. To make the code more readable, Motion Canvas uses a custom JSX runtime. This way, instead of instantiating the nodes ourselves, we can write an XML-like markup. Note that Motion Canvas does not use React itself, only JSX. There's no virtual DOM or reconciliation and the JSX tags are mapped directly to Node instances. These two code snippets are equivalent:

// JSX
view.add(
<>
<Circle />
<Layout>
<Rect />
<Txt>Hi</Txt>
</Layout>
</>,
);
// No JSX
view.add([
new Circle({}),
new Layout({
children: [
new Rect({}),
new Txt({text: 'Hi'}),
],
}),
]);

Modifying the hierarchy

After the hierarchy has been created, it's still possible to add, remove, and rearrange nodes at any time. The Node class contains the children and parent properties that can be used to traverse the tree. But in order to modify it, it's recommended to use the following helper methods:

Node.add

API 文档

Node.insert

API 文档

Node.remove

API 文档

Node.reparent

API 文档

Node.moveUp

API 文档

Node.moveDown

API 文档

Node.moveToTop

API 文档

Node.moveToBottom

API 文档

Node.moveTo

API 文档

Node.moveAbove

API 文档

Node.moveBelow

API 文档

Node.removeChildren

API 文档

Querying the hierarchy

Sometimes it can be useful to traverse the hierarchy and find some specific nodes. In this documentation, we'll be referring to this process as querying. Consider the following animation:

Click to preview animation

import {makeScene2D, Layout, Txt, Circle, Rect, is} from '@motion-canvas/2d';
import {all} from '@motion-canvas/core';

export default makeScene2D(function* (view) {
view.add(
<Layout layout gap={20} alignItems={'center'}>
<Txt fill={'white'}>Example</Txt>
<Rect fill={'#f3303f'} padding={20} gap={20}>
<Txt fill={'white'}>42</Txt>
<Circle size={60} fill={'#FFC66D'} />
<Txt fill={'white'}>!!!</Txt>
</Rect>
</Layout>,
);

const texts = view.findAll(is(Txt));

yield* all(...texts.map(text => text.fill('#FFC66D', 1).back(1)));
});

It contains multiple text nodes whose color oscillates between white and yellow. To achieve that, we used view.findAll(is(Txt)) to search through all descendants of the view node and select only those of type Txt. The first argument passed to the findAll method is a so-called predicate. It's a function that takes a node and returns true if it's a node we're looking for.

For instance, if we wanted to find all nodes whose scale x is greater than 1, we could write:

const wideNodes = view.findAll(node => node.scale.x() > 1);

Knowing this, we could try to find all nodes of type Txt as follows:

const texts = view.findAll(node => node instanceof Txt);

But Motion Canvas comes with a helpful utility function called is that can create this predicate for us:

import {is} from '@motion-canvas/2d';
// ...
const texts = view.findAll(is(Txt));

These can be used with any JavaScript function that accepts a predicate. The findAll method has been implemented to traverse all descendants of a node, but if we wanted to query only the direct children, we could retrieve the children array and call the built-in filter method with our predicate:

const textChildren = someParent.children().filter(is(Txt));

There are a few other methods that can be used to query the hierarchy depending on your needs:

Node.findAll

API 文档

Node.findFirst

API 文档

Node.findLast

API 文档

Node.findAncestor

API 文档