Effects
Effects let you observe changes to signals and react to them. Unlike signals,effects are not lazy:
-
createEffect()runs the callbackimmediately after any of its dependencies changes. -
createDeferredEffect()runs thecallback at the end of each frame during which any of its dependencieschanged.
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:
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
)
;
}
)
;