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:
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 bykinHooke'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 to0to create a spring thatoscillates indefinitely. -
initialVelocity- The initial velocity of the spring. You can set thefromandtopositions 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
)
;
}
)
;