Animations¶
Timeline control¶
Three functions advance the animation clock:
| Function | Description |
|---|---|
wait(seconds) |
Advance time by the given number of seconds |
wait(Frames(n)) |
Advance by an exact number of frames |
next_frame() |
Shortcut for wait(Frames(1)) |
The default frame rate is 24 fps. This can be changed in fairyflow.toml.
with Scene():
r = Rect().size(80, 80).color("steelblue").align_x(0.5).align_y(0.5)
wait(1) # pause one second
r.color("tomato") # instant change at t=1s
wait(0.5)
r.color("gold")
Output video
The tr parameter¶
Every attribute method accepts an optional tr keyword argument that specifies the
transition duration. Without it, the change is instant. With it, the attribute
animates linearly and the clock advances by the transition duration.
r.xy(20, 30, tr=0.5) # moves to (20, 30) over 0.5 s; clock advances 0.5 s
r.color("green", tr=1) # changes colour over 1 s; clock advances 1 s
r.alpha(0, tr=Frames(6)) # fades out over 6 frames; clock advances 6 frames
r.color("red") # instant colour change; clock does not advance
with Scene():
r = Rect().size(80, 80).color("steelblue").align_x(0.5).align_y(0.5)
wait(1)
r.color("tomato") # instant — no tr
wait(1)
r.color("steelblue", tr=1) # animated — tr=1
Output video
The first change snaps instantly at t = 1 s; the second transitions smoothly over 1 s.
Sequential and parallel composition¶
Code at the top level is already sequential — each line follows the previous one.
Seq and Par are context managers that control how their contents are composed in time.
with Seq(): — children run one after the other:
with Scene(width=300, height=140):
with Seq():
Rect().size(100, 80).xy(15, 30).color("steelblue").fade_out()
Rect().size(100, 80).xy(165, 30).color("tomato").fade_in()
Output video
with Par(): — all children start at the same time; the clock advances to the
longest child's end time:
with Scene(width=300, height=140):
with Par():
Rect().size(100, 80).xy(15, 30).color("steelblue").fade_out()
Rect().size(100, 80).xy(165, 30).color("tomato").fade_out()
Output video
Creating objects before the composition block makes them all visible from the start:
with Scene(width=300, height=140):
a = Rect().size(100, 80).xy(15, 30).color("steelblue")
b = Rect().size(100, 80).xy(165, 30).color("tomato")
with Seq():
a.fade_out() # a fades first …
b.fade_out() # … then b follows
Output video
Par and Seq compose freely. A typical pattern is a sequence of parallel steps —
top-level sequential code with Par blocks inside, each firing multiple animations at once:
with Scene():
a = Rect().size(80, 60).color("steelblue").xy(20, 60)
b = Rect().size(80, 60).color("coral").xy(120, 60)
with Par(): # step 1: both fade in together
a.fade_in(0.5)
b.fade_in(0.5)
wait(0.3)
with Par(): # step 2: both animate simultaneously
a.xy(220, 60, tr=0.8)
b.color("gold", tr=0.8)
Output video
Going the other way, nesting Seq inside Par creates staggered parallel animations —
a common pattern for cascading effects:
with Scene():
with Par():
for i in range(5):
with Seq():
wait(0.15 * i) # offset each rect
Rect().size(40, 40).xy(20 + 60 * i, 60).color("orchid").fade_in(0.4)
Output video
Moving and transforming¶
Combine tr with Par to animate position, size, and colour simultaneously:
with Scene():
r = Rect().size(60, 60).color("tomato").xy(20, 70)
with Par():
r.xy(220, 70, tr=1.5)
r.color("steelblue", tr=1.5)
r.size(80, 80, tr=1.5)
Output video
Use wait() to insert a pause before starting a transition:
with Scene():
r = Rect().size(60, 60).color("gold").align_x(0.5).align_y(0.5)
wait(1)
with Par():
r.color("tomato", tr=1)
r.size(120, 120, tr=1)
wait(0.5)
with Par():
r.color("steelblue", tr=1)
r.size(60, 60, tr=1)
Output video
Rotation and scale¶
Group nodes support .rotate() and .scale() animations. The pivot defaults to the
group's centre (pivot_x=0.5, pivot_y=0.5).
with Scene():
with Group().size(80, 80).align_x(0.5).align_y(0.5) as g:
Rect().size(80, 80).color("steelblue")
Rect().size(20, 20).color("white").xy(30, 30)
g.rotate(360, tr=2)
Output video
with Scene():
with Group().size(80, 80).align_x(0.5).align_y(0.5) as g:
Ellipse().size(80, 80).color("coral")
g.scale(0.2, tr=1)
g.scale(1, tr=1)
Output video
Fade in and fade out¶
.fade_in(time) animates alpha from 0 to 1. .fade_out(time) animates alpha from its
current value down to 0. Both advance the clock automatically.
with Scene():
r = Rect().size(120, 80).color("orchid").align_x(0.5).align_y(0.5)
r.fade_in(0.6)
wait(0.6) # hold at full opacity
r.fade_out(0.6)
Output video
You can also set alpha directly with tr:
with Scene():
r = Rect().size(120, 80).color("steelblue").align_x(0.5).align_y(0.5)
r.alpha(0)
r.alpha(1, tr=1) # fade in over 1 s
wait(0.5)
r.alpha(0, tr=1) # fade out over 1 s
Output video
Clipping animations¶
Group nodes have an animatable clipping window. clip_x/clip_w control the horizontal
extent (as a fraction of the group width), and clip_y/clip_h control the vertical extent.
with Scene():
with Group().size(200, 60) as g:
Rect().size(200, 60).color("cornflowerblue")
t = Text()
t.span("Revealed!").font_size(22).bold().color("white")
t.xy(40, 18)
g.clip_w(0) # start fully hidden
g.clip_w(1, tr=1.2) # reveal left-to-right over 1.2 s
Output video
.hide_right(time), .hide_left(time), .reveal_right(time), and .reveal_left(time) are
convenience helpers that animate the clip to conceal or reveal the group content. The direction
refers to the sweep direction: reveal_right expands the clip window rightward, reveal_left
sweeps it leftward. Each call also advances the clock automatically.
with Scene():
with Group().size(200, 60).align_x(0.5).align_y(0.5) as g:
Rect().size(200, 60).color("cornflowerblue")
t = Text()
t.span("reveal_right / hide_left").font_size(14).bold().color("white")
t.xy(18, 22)
g.reveal_right(1) # expand clip from left to right
wait(0.4)
g.hide_left(1) # shrink clip from right to left
Output video