Skip to main content

Spline

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    {
                                    makeScene2D
                                    }
                                    
                                    from
                                    
                                    '@motion-canvas/2d/lib/scenes'
                                    ;
                                    
                                    
import { Spline , Knot } from '@motion-canvas/2d/lib/components' ;
import { createRef } from '@motion-canvas/core/lib/utils' ;
import { waitFor } from '@motion-canvas/core/lib/flow' ;

export default makeScene2D ( function * ( view ) {
const spline = createRef < Spline > ( ) ;

view . add (
< Spline ref = { spline } lineWidth = { 4 } fill = { '#e13238' } closed >
< Knot position = { [ - 120 , - 30 ] } startHandle = { [ 0 , 70 ] } />
< Knot
position = { [ 0 , - 50 ] }
startHandle = { [ - 40 , - 60 ] }
endHandle = { [ 40 , - 60 ] }
/>
< Knot position = { [ 120 , - 30 ] } startHandle = { [ 0 , - 70 ] } />
< Knot position = { [ 0 , 100 ] } startHandle = { [ 5 , 0 ] } />
Spline > ,
) ;

yield * spline ( ) . scale ( 0.9 , 0.6 ) . to ( 1 , 0.4 ) ;
} ) ;

The Spline component allows us to draw and animate smooth curvesthrough a series of control points.

Defining control points

In order to draw a spline, we need to specify what its knots are. The Spline component provides multiple ways of specifying these control points which wewill go through in this section.

Using the points property

The easiest way to define a spline's knots is by passing an array of positionsvia the spline's points property. Each point will be treated as the positionof one of the spline's knots.

Press play to preview the animation
                                
                                  
                                    export
                                    
                                    default
                                    
                                    makeScene2D
                                    (
                                    function
                                    *
                                    
                                    (
                                    view
                                    )
                                    
                                    {
                                    
                                    
view . add (
< Spline
lineWidth = { 6 }
stroke = { 'lightseagreen' }
points = { [
[ - 300 , 0 ] ,
[ - 150 , - 100 ] ,
[ 150 , 100 ] ,
[ 300 , 0 ] ,
] }
/> ,
) ;
} ) ;

The result is a curve that smoothly passes through each of the provided points.

info

Remember to provide a lineWidth and stroke to the spline as it won't bevisible otherwise. Alternatively, you may also specify a fill color.

We can alter the shape of the curve by passing a value between 0 and 1 tothe smoothness property.

Press play to preview the animation
                                
                                  
                                    export
                                    
                                    default
                                    
                                    makeScene2D
                                    (
                                    function
                                    *
                                    
                                    (
                                    view
                                    )
                                    
                                    {
                                    
                                    
const spline = createRef < Spline > ( ) ;

view . add (
< Spline
ref = { spline }
lineWidth = { 6 }
stroke = { 'lightseagreen' }
smoothness = { 0.4 }
points = { [
[ - 300 , 0 ] ,
[ - 150 , - 100 ] ,
[ 150 , 100 ] ,
[ 300 , 0 ] ,
] }
/> ,
) ;

yield * spline ( ) . smoothness ( 0 , 1 ) . to ( 1 , 1 ) . to ( 0.4 , 1 ) ;
} ) ;

While defining the knots in this way is very simple and can be enough for simplecurves, there is an important limitation to this method: we cannot alter theposition of the knot's handles. Instead, the handles get calculatedautomatically so that the curve passes smoothly through each point without anysharp or sudden turns.

Fun fact

The auto handles are calculated based on the positions of a knot's twoneighboring knots. A spline that calculates the handle positions of its knots inthis way is called a Cardinal Spline .

Let's look at the second way of defining knots to learn how we can more finelycontrol the shape of our spline.

Using Knot nodes

The second way of defining knots is by—fittingly—using the Knot node.The same spline from above can also be written like this.

Press play to preview the animation
                                
                                  
                                    export
                                    
                                    default
                                    
                                    makeScene2D
                                    (
                                    function
                                    *
                                    
                                    (
                                    view
                                    )
                                    
                                    {
                                    
                                    
view . add (
< Spline lineWidth = { 6 } stroke = { 'lightseagreen' } >
< Knot position = { [ - 300 , 0 ] } />
< Knot position = { [ - 150 , - 100 ] } />
< Knot position = { [ 150 , 100 ] } />
< Knot position = { [ 300 , 0 ] } />
Spline > ,
) ;
} ) ;

As you can see, we get the exact same shape we did when using the points property. The advantage of defining the knots with Knot nodes is that it alsoallows us to control the positions of each knot's handles via the startHandle and endHandle properties.

Press play to preview the animation
                                
                                  
                                    export
                                    
                                    default
                                    
                                    makeScene2D
                                    (
                                    function
                                    *
                                    
                                    (
                                    view
                                    )
                                    
                                    {
                                    
                                    
view . add (
< Spline lineWidth = { 6 } stroke = { 'lightseagreen' } >
< Knot position = { [ - 300 , 0 ] } />
< Knot position = { [ - 150 , - 100 ] } endHandle = { [ - 100 , 0 ] } />
< Knot position = { [ 150 , 100 ] } startHandle = { [ 100 , 0 ] } />
< Knot position = { [ 300 , 0 ] } />
Spline > ,
) ;
} ) ;

Note that handle positions are relative to the knot's position.

note

Similar to using the points property, if no explicit handles are provided fora knot, the handles get calculated automatically so that the curve smoothlypasses through the knot.

Mirrored handles

Handles are mirrored by default. This means that when we provide only one of thehandles of a knot, the other one will implicitly be set to a flipped version ofthe provided handle.

                            
                              
                                <
                                Knot
                                
                                startHandle
                                =
                                {
                                [
                                100
                                ,
                                
                                50
                                ]
                                }
                                
                                />
                                
                                
// Is the same as
< Knot startHandle = { [ 100 , 50 ] } endHandle = { [ - 100 , - 50 ] } />

Broken knots

Providing both startHandle and endHandle results in a so-called brokenknot . Broken knots are very useful because they allow us to add sharp cornersto our spline.

Press play to preview the animation
                                
                                  
                                    export
                                    
                                    default
                                    
                                    makeScene2D
                                    (
                                    function
                                    *
                                    
                                    (
                                    view
                                    )
                                    
                                    {
                                    
                                    
view . add (
< Spline lineWidth = { 16 } stroke = { 'lightseagreen' } closed >
< Knot position = { [ - 50 , - 80 ] } startHandle = { [ 0 , 20 ] } endHandle = { [ 90 , 0 ] } />
< Knot position = { [ 50 , 0 ] } />
< Knot position = { [ - 50 , 80 ] } startHandle = { [ 90 , 0 ] } endHandle = { [ 0 , - 20 ] } />
Spline > ,
) ;
} ) ;

Blending between user handles and calculated handles

By default, the auto-calculated handles get ignored when at least one of the startHandle or endHandle properties is provided. However, it is possible toblend between user-provided and auto-calculated handles by using the auto property.

                            
                              
                                <
                                Spline
                                
                                lineWidth
                                =
                                {
                                16
                                }
                                
                                stroke
                                =
                                {
                                'lightseagreen'
                                }
                                
                                closed
                                >
                                
                                
< Knot
position = { [ - 50 , - 80 ] }
startHandle = { [ 0 , 20 ] }
endHandle = { [ 90 , 0 ] }
auto = { 0.5 }
/>
< Knot position = { [ 50 , 0 ] } />
< Knot
position = { [ - 50 , 80 ] }
startHandle = { [ 90 , 0 ] }
endHandle = { [ 0 , - 20 ] }
auto = { 0.5 }
/>
Spline >

auto should be a value between 0 and 1 and represents the percentage ofhow much to blend between the user-provided handles ( 0 ) and auto-calculatedhandles ( 1 ).

tip

auto is a compound signal, which means you can specify startHandleAuto and endHandleAuto to individually control the blend of each handle.

                                
                                  
                                    <
                                    Knot
                                    
                                    
position = { [ 0 , 0 ] }
startHandle = { [ - 50 , - 50 ] }
endHandle = { [ 30 , 0 ] }
startHandleAuto = { 0.3 }
endHandleAuto = { 0.8 }
/>

Since auto is a signal, it can also be animated.

Press play to preview the animation
                                
                                  
                                    export
                                    
                                    default
                                    
                                    makeScene2D
                                    (
                                    function
                                    *
                                    
                                    (
                                    view
                                    )
                                    
                                    {
                                    
                                    
const knots : Knot [ ] = [ ] ;

view . add (
< Spline lineWidth = { 16 } stroke = { 'lightseagreen' } lineJoin = { 'round' } closed >
< Knot
ref = { makeRef ( knots , 0 ) }
position = { [ - 50 , - 80 ] }
startHandle = { [ 0 , 20 ] }
endHandle = { [ 90 , 0 ] }
/>
< Knot position = { [ 50 , 0 ] } />
< Knot
ref = { makeRef ( knots , 1 ) }
position = { [ - 50 , 80 ] }
startHandle = { [ 90 , 0 ] }
endHandle = { [ 0 , - 20 ] }
/>
Spline > ,
) ;

yield * all ( ... knots . map ( knot => knot . auto ( 1 , 1 ) . to ( 0 , 1 ) ) ) ;
} ) ;

Animating splines

While animating splines isn't too different from animating any other node, thissection aims to illustrate a few of the most common use cases.

Drawing splines

Similar to the Line component, the Spline node provides start and end signals which allow us to control the segment of the curve that should bevisible. Both start and end are values between 0 and 1 and represent thepercentage of the spline's arclength from which to start drawing from.

                            
                              
                                <
                                Spline
                                
                                
points = { [
[ - 300 , 0 ] ,
[ - 150 , - 100 ] ,
[ 150 , 100 ] ,
] }
start = { 0.4 }
end = { 0.8 }
/>

The example above would draw the spline starting at 40% of the spline'sarclength ( start={0.4} ) and draw it up until 80% of the spline's arclength( end={0.8} ).

note

When using start and end in conjunction with startOffset and endOffset , start and end will be relative to the remaining length of the spline aftertaking the offset into account.

We can then animate drawing a spline by tweening these properties:

Press play to preview the animation
                                
                                  
                                    export
                                    
                                    default
                                    
                                    makeScene2D
                                    (
                                    function
                                    *
                                    
                                    (
                                    view
                                    )
                                    
                                    {
                                    
                                    
const spline = createRef < Spline > ( ) ;

view . add (
< Spline
ref = { spline }
lineWidth = { 6 }
stroke = { 'lightseagreen' }
points = { [
[ - 300 , 0 ] ,
[ - 150 , - 100 ] ,
[ 150 , 100 ] ,
[ 300 , 0 ] ,
] }
end = { 0 }
/> ,
) ;

yield * spline ( ) . end ( 1 , 1.5 ) ;
yield * spline ( ) . start ( 1 , 1.5 ) . to ( 0.5 , 1 ) ;
yield * spline ( ) . end ( 0.5 , 1 ) ;
yield * all ( spline ( ) . start ( 0 , 1.5 ) , spline ( ) . end ( 1 , 1.5 ) ) ;
} ) ;

Animating the knots of a spline

Knot s can be animated in much the same way as other components.

note

Animating a spline's knots is only possible when using the Knot component, not when using the points property.

Below are a few examples of interesting effects that can be achieved byanimating different properties of knots.

tip

You can think of startHandle and endHandle as being the children of theKnot—changing the knot's position, rotation and scale will also transform thehandles. The only exceptions are auto handles which are unaffected by thesetransformations.

Press play to preview the animation
                                      
                                        
                                          export
                                          
                                          default
                                          
                                          makeScene2D
                                          (
                                          function
                                          *
                                          
                                          (
                                          view
                                          )
                                          
                                          {
                                          
                                          
const knotPositions : PossibleVector2 [ ] = [
[ - 200 , 0 ] ,
[ - 100 , - 80 ] ,
[ 0 , 80 ] ,
[ 100 , - 80 ] ,
[ 200 , 0 ] ,
] ;
const knots : Knot [ ] = [ ] ;

view . add (
< Spline lineWidth = { 6 } stroke = { 'lightseagreen' } >
{ knotPositions . map ( ( pos , i ) => (
< Knot ref = { makeRef ( knots , i ) } position = { pos } />
) ) }
Spline > ,
) ;

yield * all (
knots [ 1 ] . position . y ( 80 , 1 ) . to ( - 80 , 1 ) ,
knots [ 2 ] . position . y ( - 80 , 1 ) . to ( 80 , 1 ) ,
knots [ 3 ] . position . y ( 80 , 1 ) . to ( - 80 , 1 ) ,
) ;
} ) ;

Animating objects along a spline

Splines can be useful to model the path that an object should follow. You canuse the getPointAtPercentage method to achieve this.

Press play to preview the animation
                                
                                  
                                    export
                                    
                                    default
                                    
                                    makeScene2D
                                    (
                                    function
                                    *
                                    
                                    (
                                    view
                                    )
                                    
                                    {
                                    
                                    
const spline = createRef < Spline > ( ) ;
const progress = createSignal ( 0 ) ;

view . add (
< >
< Spline
ref = { spline }
lineWidth = { 6 }
stroke = { 'lightgray' }
points = { [
[ - 300 , 0 ] ,
[ - 150 , - 100 ] ,
[ 150 , 100 ] ,
[ 300 , 0 ] ,
] }
/>
< Rect
size = { 26 }
fill = { 'lightseagreen' }
position = { ( ) => spline ( ) . getPointAtPercentage ( progress ( ) ) . position }
rotation = { ( ) =>
spline ( ) . getPointAtPercentage ( progress ( ) ) . tangent . degrees
}
/> ,
> ,
) ;

yield * progress ( 1 , 2 ) . to ( 0 , 2 ) ;
} ) ;

The getPointAtPercentage method returns a CurvePoint objectwhich contains the position of the point that sits at the given percentage alongthe spline's arclength as well as the point's tangent vector.