Skip to main content

References

Usually, when creating a node, we want to store a reference to it, so we cananimate it later. One way to do that is by assigning it to a variable first, andthen adding it to the scene:

                            
                              
                                const
                                 circle 
                                =
                                
                                <
                                Circle
                                
                                />
                                ;
                                
                                
view . add ( circle ) ;

// we can now animate our circle:
yield * circle . scale ( 2 , 0.3 ) ;
info

If you're used to libraries such as React, the above example may seem strange.In Motion Canvas, the JSX components immediately create and return an instanceof the given class. It's completely valid to store it as a reference and use itthroughout the animation.

But this approach doesn't scale well. The more nodes we add, the harder it getsto see the overall structure of our scene. Consider the following example:

                            
                              
                                const
                                 rectA 
                                =
                                
                                <
                                Rect
                                
                                />
                                ;
                                
                                
const rectB = < Rect /> ;
const circle = < Circle > { rectA } </ Circle > ;
view . add (
< Layout >
{ circle }
{ rectB }
</ Layout > ,
) ;

And now compare it to a version that doesn't store any references:

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

If you find the latter example more readable, this guide is for you.

ref property

Each node in Motion Canvas has a property called ref that allows you to createa reference to said node. It accepts a callback that will be invoked right afterthe node has been created, with the first argument being the newly createdinstance.

With this in mind, we can rewrite the initial example as:

                            
                              
                                let
                                 circle
                                :
                                
                                Circle
                                ;
                                
                                
view . add (
< Circle
ref = { instance => {
circle = instance ;
} }
/> ,
) ;

yield * circle . scale ( 2 , 0.3 ) ;

Using the ref property in this way is not really practical, and we wouldn'trecommend it. But it's crucial to understand how it works because all theupcoming methods use this property as a base.

createRef() function

The preferred way of using the ref property is in conjunction with the createRef() function. Continuing with ourexample, we can rewrite it as:

                            
                              
                                import
                                
                                {
                                createRef
                                }
                                
                                from
                                
                                '@motion-canvas/core/lib/utils'
                                ;
                                
                                

// ...

const circle = createRef < Circle > ( ) ;
view . add ( < Circle ref = { circle } /> ) ;

yield * circle ( ) . scale ( 2 , 0.3 ) ;

Notice that circle is no longer just a variable that points to our circle.Instead, it's a signal-like function that can be used to accessit. Invoking it without any arguments ( circle() ) returns our instance.

Going back to the example with the more complex scene, we can now rewrite it as:

                            
                              
                                const
                                 rectA 
                                =
                                
                                createRef
                                <
                                Rect
                                >
                                (
                                )
                                ;
                                
                                
const rectB = createRef < Rect > ( ) ;
const circle = createRef < Circle > ( ) ;
view . add (
< Layout >
< Circle ref = { circle } >
< Rect ref = { rectA } />
</ Circle >
< Rect ref = { rectB } />
</ Layout > ,
) ;

makeRef() function

Another common use case of the ref property is to assign the newly createdinstance to a property of some object. In the following example, we assign ourcircle to circle.instance (We'll talk about why this may be useful in a bit):

                            
                              
                                const
                                 circle 
                                =
                                
                                {
                                instance
                                :
                                
                                null
                                
                                as
                                
                                Circle
                                }
                                ;
                                
                                
view . add (
< Circle
ref = { instance => {
circle . instance = instance ;
} }
/> ,
) ;

We can use the makeRef() function to simplify thisprocess:

                            
                              
                                import
                                
                                {
                                makeRef
                                }
                                
                                from
                                
                                '@motion-canvas/core/lib/utils'
                                ;
                                
                                

// ...

const circle = { instance : null as Circle } ;
view . add (
< Circle ref = { makeRef ( circle , 'instance' ) } /> ,
) ;

Array of references

makeRef() can be particularly useful when we create an array of nodes and wantto grab references to all of them:

                            
                              
                                const
                                 circles
                                :
                                
                                Circle
                                [
                                ]
                                
                                =
                                
                                [
                                ]
                                ;
                                
                                
view . add (
< Layout >
{ range ( 10 ) . map ( index => (
< Circle ref = { makeRef ( circles , index ) } />
) ) }
</ Layout > ,
) ;

In JavaScript, arrays are objects whose properties are their indices. So makeRef(circles, index) will set the nth element of our array to the createdcircle. As a result, we end up with an array of size 10 filled with circlesthat we can use to animate all of them.

Custom functions

makeRef() can also be used to return more than one reference from a customfunction component:

                            
                              
                                function
                                
                                Label
                                (
                                {
                                
                                
refs ,
children ,
} : {
refs : { rect : Rect ; text : Txt } ;
children : string ;
} ) {
return (
< Rect ref = { makeRef ( refs , 'rect' ) } >
< Txt ref = { makeRef ( refs , 'text' ) } > { children } </ Txt >
</ Rect >
) ;
}

const label = { rect : null as Rect , text : null as Text } ;
view . add ( < Label refs = { label } > HELLO </ Label > ) ;

// we can now animate both the Rect and the Text of our label:
yield * label . rect . opacity ( 2 , 0.3 ) ;
yield * label . text . fontSize ( 24 , 0.3 ) ;

In this example, we define a function component called Label consisting of arectangle with some text inside. When using the component, we use the refs property to pass the label object created by us. makeRef() is then used tofill this object with all the necessary references.

makeRefs() function

Looking at the previous example, you may notice that we had to define the refs type twice. First in the Label declaration and then again when creating the label object:

                            
                              
                                function
                                
                                Label
                                (
                                {
                                
                                
refs ,
children ,
} : {
refs : { rect : Rect ; text : Txt } ;
children : string ;
} ) {
return (
< Rect ref = { makeRef ( refs , 'rect' ) } >
< Txt ref = { makeRef ( refs , 'text' ) } > { children } </ Txt >
</ Rect >
) ;
}

const label = { rect : null as Rect , text : null as Text } ;
view . add ( < Label refs = { label } > HELLO </ Label > ) ;

We can use makeRefs() to eliminate thisredundancy. It can extract the type from the Label declaration and create anempty object matching it:

                            
                              
                                import
                                
                                {
                                makeRef
                                ,
                                 makeRefs
                                }
                                
                                from
                                
                                '@motion-canvas/core/lib/utils'
                                ;
                                
                                

// ...

function Label ( {
refs ,
children ,
} : {
refs : { rect : Rect ; text : Txt } ;
children : string ;
} ) {
return (
< Rect ref = { makeRef ( refs , 'rect' ) } >
< Txt ref = { makeRef ( refs , 'text' ) } > { children } </ Txt >
</ Rect >
) ;
}

const label = makeRefs < typeof Label > ( ) ;
view . add ( < Label refs = { label } > HELLO </ Label > ) ;