Skip to main content

Tweening

Tweens are one of the fundamental building blocks of animation. They are aspecial type of generators that animate between two values over given time.

tween function

The simplest way to create a tween is via the tween function:

                            
                              
                                import
                                
                                {
                                makeScene2D
                                }
                                
                                from
                                
                                '@motion-canvas/2d/lib/scenes'
                                ;
                                
                                
import { Circle } from '@motion-canvas/2d/lib/components' ;
import { createRef } from '@motion-canvas/core/lib/utils' ;
import { tween , map } from '@motion-canvas/core/lib/tweening' ;

export default makeScene2D ( function * ( view ) {
const circle = createRef < Circle > ( ) ;

view . add (
< Circle
ref = { circle }
x = { - 300 }
width = { 240 }
height = { 240 }
fill = " #e13238 "
/> ,
) ;
yield * tween ( 2 , value => {
circle ( ) . position . x ( map ( - 300 , 300 , value ) ) ;
} ) ;
} ) ;

In the example above, we animate the x coordinate of our circle from -300 to 300 over a span of 2 second.

The tween function takes two parameters. The firstone specifies the tween duration in seconds. The second is a callback functionthat will be called each frame the tween takes place. The value parameter itreceives is a number ranging from 0 to 1 , informing us about the progress ofthe tween. We can use it to calculate the values that our tween animates. In thecase of our circle, we use the map function to map the value range from [0, 1] to [-300, 300] and set it as the x coordinate:

Timing functions

At the moment, our animation feels a bit unnatural. The speed with which the value parameter changes is constant, which in turn makes the circle move withconstant speed. In real life, however, objects have inertia - they take time tospeed up and slow down. We can simulate this behavior with timing functions .

A timing function takes a number in the range [0, 1] and returns anothernumber in the same range but with a modified rate of change. Motion Canvasprovides all the most popular timing functions (sometimes called easing functions) but since it's a normal JavaScript functionyou can create your own.

Let's use the easeInOutCubic function tofix our animation:

                            
                              
                                yield
                                
                                *
                                
                                
tween ( 2 , value => {
circle ( ) . position . x ( map ( - 300 , 300 , easeInOutCubic ( value ) ) ) ;
} ) ;

easeInOut means that the object will speed up at the start ( in ) and slowdown at the end ( Out ). Cubic denotes the mathematical function used - inthis case it's a cubic equation. Knowing that, a function called easeOutQuad would make the object start with full speed and then slow down at the end usinga quadratic equation.

The effects of a particular easing function can be visualised by animating the y coordinate of an object as time changes with a constant rate, such asbouncing it in and out.

Because using timing functions to map a range of values is a really commonpattern, it's possible to skip map entirely and pass the range to the timingfunction itself:

                            
                              
                                // This:
                                
                                
map ( - 300 , 300 , easeInOutCubic ( value ) ) ;
// Can be simplified to:
easeInOutCubic ( value , - 300 , 300 ) ;

Interpolation functions

So far, we've only animated a single, numeric value. The map function can be used to interpolate between twonumbers but to animate more complex types we'll need to use interpolationfunctions. Consider the following example:

                            
                              
                                // import { Color } from "@motion-canvas/core/lib/types";
                                
                                
yield *
tween ( 2 , value => {
circle ( ) . fill (
Color . lerp (
new Color ( '#e6a700' ) ,
new Color ( '#e13238' ) ,
easeInOutCubic ( value ) ,
) ,
) ;
} ) ;

Color.lerp is a static function thatinterpolates between two colors:

tip

All complex types in Motion Canvas provide a static methodcalled lerp that interpolates between two instances of said type.

Aside from the default linear interpolation, some types offer more advancedfunctions such as the Vector2.arcLerp . Itmakes the object follow a curved path from point a to b:

                            
                              
                                yield
                                
                                *
                                
                                
tween ( 2 , value => {
circle ( ) . position (
Vector2 . arcLerp (
new Vector2 ( - 300 , 200 ) ,
new Vector2 ( 300 , - 200 ) ,
easeInOutCubic ( value ) ,
) ,
) ;
} ) ;

Tweening properties

The tween function is useful when we need toorchestrate complex animations. However, there's a better way of tweeningindividual properties. You may recall from the quickstart section that the following tween:

                            
                              
                                yield
                                
                                *
                                
                                
tween ( 2 , value => {
circle ( ) . color (
Color . lerp (
new Color ( '#e6a700' ) ,
new Color ( '#e13238' ) ,
easeInOutCubic ( value ) ,
) ,
) ;
} ) ;

Can be written as:

                            
                              
                                yield
                                
                                *
                                
                                circle
                                (
                                )
                                .
                                color
                                (
                                '#e13238'
                                ,
                                
                                2
                                )
                                ;
                                

Here, we use a SignalTween signature thatlooks similar to a setter, except it accepts the transition duration as itssecond argument. Under the hood, this will also create a tween - one that startswith the current value and ends with the newly provided one.

We can chain multiple tweens together by calling the to() method on thereturned object:

                            
                              
                                yield
                                
                                *
                                
                                circle
                                (
                                )
                                .
                                color
                                (
                                '#e13238'
                                ,
                                
                                2
                                )
                                .
                                to
                                (
                                '#e6a700'
                                ,
                                
                                2
                                )
                                ;
                                

By default, property tweens use easeInOutCubic as the timing function. We canoverride that by providing a third argument:

                            
                              
                                yield
                                
                                *
                                
                                
circle ( ) . color (
'#e13238' ,
2 ,
easeOutQuad ,
) ;

Similarly, we can pass a custom interpolation function as the fourth argument:

                            
                              
                                yield
                                
                                *
                                
                                
circle ( ) . position (
new Vector2 ( 300 , - 200 ) ,
2 ,
easeInOutCubic ,
Vector2 . arcLerp ,
) ;

spring function

The spring function allows us to interpolatebetween two values using Hooke's law. We need to provide it with the descriptionof our spring and the from and to values. You can think of it as having aspring in resting position (the to value), stretching it all the way to thestarting position (the from value), and then letting it go. The movement ofthe spring as it tries to reach the equilibrium is what we can use to dive ouranimations.

In the example below, we use springs to animate a position of a circle, but thismethod can be used for more things than just position.

                            
                              
                                import
                                
                                {
                                makeScene2D
                                }
                                
                                from
                                
                                '@motion-canvas/2d'
                                ;
                                
                                
import { Circle } from '@motion-canvas/2d/lib/components' ;
import { createRef } from '@motion-canvas/core/lib/utils' ;
import {
PlopSpring ,
SmoothSpring ,
spring ,
} from '@motion-canvas/core/lib/tweening' ;

export default makeScene2D ( function * ( view ) {
const circle = createRef < Circle > ( ) ;

view . add (
< Circle
ref = { circle }
x = { - 400 }
size = { 240 }
fill = { '#e13238' }
/> ,
) ;

yield * spring ( PlopSpring , - 400 , 400 , 1 , value => {
circle ( ) . position . x ( value ) ;
} ) ;
yield * spring ( SmoothSpring , 400 , - 400 , value => {
circle ( ) . position . x ( value ) ;
} ) ;
} ) ;

Spring description

The first argument of the spring function expectsan object that describes the physical properties of our spring. Motion Canvasships with a few useful presets that you can use, such as PlopSpring and SmoothSpring . But it's possible to define your own spring:

                            
                              
                                const
                                 MySpring 
                                =
                                
                                {
                                
                                
mass : 0.04 ,
stiffness : 10.0 ,
damping : 0.7 ,
initialVelocity : 8.0 ,
} ;
  • mass - Describes the inertia of the spring. How much force is required toaccelerate and decelerate it.
  • stiffness - The coefficient of the spring. Usually represented by k inHooke's equation. It describes how stiff the spring is.
  • damping - Over time, damping causes the spring to lose energy and eventuallysettle in equilibrium. You can set it to 0 to create a spring thatoscillates indefinitely.
  • initialVelocity - The initial velocity of the spring. You can set the from and to positions to the same value and give the spring some initial velocityto make it oscillate in place.

Settle tolerance

Notice how in our spring example, we provided the first spring with anadditional value:

                            
                              
                                yield
                                
                                *
                                
                                spring
                                (
                                PlopSpring
                                ,
                                
                                -
                                400
                                ,
                                
                                400
                                ,
                                
                                1
                                
                                /*...*/
                                )
                                ;
                                
                                
// here ^

This optional argument is called settleTolerance and is used to define theminimal distance from the to value the spring should reach to be consideredsettled. The generator created by the spring function finishes only when the spring settles. By adjusting the tolerancewe can make the animation finish faster, depending on our needs. In our examplewe animate the position so a tolerance of 1 means that the spring needs to beat most 1 pixel away from the to value.

Saving and restoring states

All nodes provide a save method which allowsus to save a snapshot of the node's current state. We can then use the restore method at a later point in ouranimation to restore the node to the previously saved state.

                            
                              
                                circle
                                (
                                )
                                .
                                save
                                (
                                )
                                ;
                                
                                
yield * circle ( ) . position ( new Vector2 ( 300 , - 200 ) , 2 ) ;
yield * circle ( ) . restore ( 1 ) ;

It is also possible to provide a custom timing function tothe restore method.

                            
                              
                                yield
                                
                                *
                                
                                circle
                                (
                                )
                                .
                                restore
                                (
                                1
                                ,
                                 linear
                                )
                                ;
                                

Node states get stored on a stack. This makes it possible to save more than onestate by invoking the save method multipletimes. When calling restore , the node willbe restored to the most recently saved state by popping the top entry in thestate stack. If there is no saved state, this method does nothing.

The example below shows a more complete example of how we can store and restoremultiple states across an animation.

                            
                              
                                import
                                
                                {
                                makeScene2D
                                }
                                
                                from
                                
                                '@motion-canvas/2d'
                                ;
                                
                                
import { Circle } from '@motion-canvas/2d/lib/components' ;
import { createRef } from '@motion-canvas/core/lib/utils' ;
import { all } from '@motion-canvas/core/lib/flow' ;

export default makeScene2D ( function * ( view ) {
const circle = createRef < Circle > ( ) ;

view . add (
< Circle
ref = { circle }
size = { 150 }
position = { [ - 300 , - 300 ] }
fill = { '#e13238' }
/> ,
) ;

circle ( ) . save ( ) ;
yield * all ( circle ( ) . position . x ( 0 , 1 ) , circle ( ) . scale ( 1.5 , 1 ) ) ;

circle ( ) . save ( ) ;
yield * all ( circle ( ) . position . y ( 0 , 1 ) , circle ( ) . scale ( 0.5 , 1 ) ) ;

circle ( ) . save ( ) ;
yield * all ( circle ( ) . position . x ( 300 , 1 ) , circle ( ) . scale ( 1 , 1 ) ) ;

yield * circle ( ) . restore ( 1 ) ;
yield * circle ( ) . restore ( 1 ) ;
yield * circle ( ) . restore ( 1 ) ;
} ) ;