Skip to main content

Effects

Effects let you observe changes to signals and react to them. Unlike signals,effects are not lazy:

They are useful for side effects, such as modifying the node hierarchy orspawning background animations in response to a change in some signals.

When it comes to modifying the properties of nodes, prefer to use signals .

Overview

Effects are created using the createEffect() and createDeferredEffect() functions. The first argument specifies the callback to invoke:

                            
                              
                                import
                                
                                {
                                createSignal
                                ,
                                 createEffect
                                }
                                
                                from
                                
                                '@motion-canvas/core'
                                ;
                                
                                

const signal = createSignal ( 0 ) ;
createEffect ( ( ) => {
console . log ( 'Signal changed: ' , signal ( ) ) ;
} ) ;

After creation, effects can be disposed of using the returned function:

                            
                              
                                const
                                 unsubscribe 
                                =
                                
                                createEffect
                                (
                                (
                                )
                                
                                =>
                                
                                {
                                
                                
console . log ( 'Signal changed: ' , signal ( ) ) ;
} ) ;

// do something

unsubscribe ( ) ;

Explanation

To understand the difference between signals and effects, consider this modifiedexample from the signals section:

                            
                              
                                const
                                 radius 
                                =
                                
                                createSignal
                                (
                                1
                                )
                                ;
                                
                                
const area = createSignal ( ( ) => {
console . log ( 'area recalculated!' ) ;
return Math . PI * radius ( ) * radius ( ) ;
} ) ;

createEffect ( ( ) => {
area ( ) ;
} ) ;
// area recalculated!

radius ( 2 ) ;
// area recalculated!
radius ( 3 ) ;
// area recalculated!
radius ( 4 ) ;
// area recalculated!

This time, the area signal is invoked inside an effect, instead of beingcalled directly. Now, whenever the radius signal changes, the effect istriggered which recalculates the area signal instantly.

This is a compromise. On one hand, you can now react to changes immediatelywithout having to invoke the signal each frame. On the other hand, alldependencies of the effect are no longer lazy. Normally this shouldn't be aproblem, but if the dependencies of the effect change often and are expensive tocalculate, this may affect seeking performance.

Deferred effects

If your effect references multiple dependencies that change often, you can use createDeferredEffect() to defer themuntil the end of the current frame. This way, the effect is only executed once,even if multiple dependencies change. The downside is that the side effectscaused by the deferred effect are not immediately visible. They will becomevisible after the current generator step is finished, but before the renderingstarts.

                            
                              
                                const
                                 a 
                                =
                                
                                createSignal
                                (
                                1
                                )
                                ;
                                
                                
const b = createSignal ( 2 ) ;

let value = 0 ;
createEffect ( ( ) => {
console . log ( 'effect invoked' ) ;
value = a ( ) + b ( ) ;
} ) ;
// effect invoked

let deferredValue = 0 ;
createDeferredEffect ( ( ) => {
console . log ( 'deferred effect invoked' ) ;
deferredValue = a ( ) + b ( ) ;
} ) ;
// deferred effect invoked

a ( 2 ) ;
// effect invoked
b ( 3 ) ;
// effect invoked

console . log ( value ) ; // 5 - effect's value is updated immediately.
console . log ( deferredValue ) ; // 3 - deferred effect's value is not yet ready.

yield ; // deferred effect invoked
console . log ( deferredValue ) ; // 5

Complex example

The following example demonstrates how to use effects to automatically updatethe node hierarchy, so the amount of circles displayed matches the value of the count signal:

Press play to preview the animation
                                
                                  
                                    import
                                    
                                    ...
                                    
                                    

export default makeScene2D ( function * ( view ) {
const count = createSignal ( 0 ) ;
const container = createRef < Layout > ( ) ;

view . add ( < Layout alignItems = { 'center' } ref = { container } layout /> ) ;

const circles : Circle [ ] = [ ] ;
createEffect ( ( ) => {
const targetCount = Math . round ( count ( ) ) ;
let i = circles . length ;
// add any missing circles
for ( ; i < targetCount ; i ++ ) {
const circle = ( < Circle fill = { 'white' } /> ) as Circle ;
circles . push ( circle ) ;
container ( ) . add ( circle ) ;
spawn ( circle . size ( 80 , 0.3 ) ) ;
}
// remove any extra circles
for ( ; i > targetCount ; i -- ) {
const circle = circles . pop ( ) ! ;
spawn ( circle . size ( 0 , 0.3 ) . do ( ( ) => circle . remove ( ) ) ) ;
}
} ) ;

count ( 1 ) ;
yield * waitFor ( 1 ) ;
count ( 6 ) ;
yield * waitFor ( 1 ) ;
count ( 4 ) ;
yield * count ( 0 , 2 ) ;
yield * waitFor ( 1 ) ;
} ) ;