Animation helpers for declarative runtime transitions.
Basics
Animation keyframes are written with ordinary Emerge attrs. When written
inline, keyframes are attr lists such as
[Transform.move_x(-40), Transform.alpha(0.0)]. Maps are also accepted when
building specs programmatically.
Each animation spec needs:
- at least 2 non-empty keyframes
- a positive duration in milliseconds
- a curve:
:linear,:ease_in,:ease_out, or:ease_in_out - an optional repeat:
:once,:loop, or a positive integer
A positive integer runs that many times and then clamps to the last keyframe. Enter animations don't hold their last keyframe; see "Animate Enter".
These examples assume use Emerge.UI.
Keyframes
Every keyframe must use the same attribute set throughout the animation. Width and height accept pixels, content, fill and weighted fill; other attrs must stay compatible.
Examples:
- widths/heights can animate between pixels, content, fill and weighted fill
min/2andmax/2remain static layout expressions and cannot be animation endpointspadding(8)can animate topadding(16), but not topadding_each(8, 12, 8, 12)- solid backgrounds can animate to other solid backgrounds
Background.color(gradient([...]))can animate to another gradient background with the same number of stops (colors and angle are interpolated)- image backgrounds must keep the same source and fit across keyframes
Border.shadow/1andBorder.glow/2must keep the same shadow count in each keyframe
Animate
animate/4 runs during normal retained updates. Use it for looping motion
and other ongoing transitions.
el(
[
Animation.animate(
[
[Transform.move_x(-60), Transform.alpha(0.4)],
[Transform.move_x(60), Transform.alpha(1.0)],
[Transform.move_x(-60), Transform.alpha(0.4)]
],
3000,
:linear,
:loop
),
Background.color(color(:teal, 400)),
Border.rounded(12),
padding(10)
],
text("Looping")
)Animate Enter
animate_enter/4 starts when an element first mounts. It does not start if
it is added later to an already retained node.
The enter spec is captured at mount time, so changing it later does not affect the currently running enter animation.
When an enter animation finishes, the element renders with its plain attrs again. To end on a different value, set it as a plain attr too.
If both animate_enter/4 and animate/4 are present, the regular animation
waits for the enter animation to finish and then starts from zero progress.
Fields can hand off separately: geometry may remain held for a sibling while
an unrelated paint field starts its regular clock.
el(
[
Animation.animate_enter(
[
[Transform.move_y(20), Transform.alpha(0.0)],
[Transform.move_y(0), Transform.alpha(1.0)]
],
180,
:ease_out
)
],
text("Entering")
)Animate Exit
animate_exit/4 starts when an element is removed from the tree. Emerge
keeps rendering the element until the exit animation finishes.
animate_exit/4 must use :once and is not allowed on the viewport root.
el(
[
Animation.animate_exit(
[
[Transform.alpha(1.0)],
[Transform.alpha(0.0)]
],
120,
:linear
)
],
text("Leaving")
)Animatable Attrs
Animation keyframes support these public attr families:
- layout:
width/1,height/1,padding/1,padding_each/4,spacing/1,spacing_xy/2 - background:
Background.* - border:
Border.rounded/1,Border.rounded_each/4,Border.width/1,Border.width_each/4,Border.color/1,Border.shadow/1,Border.glow/2 - font and svg color:
Font.size/1,Font.color/1,Font.letter_spacing/1,Font.word_spacing/1,Svg.color/1 - layout-aware transforms:
Emerge.UI.scale/1,Emerge.UI.rotate/1 - paint-only transforms:
Transform.move_x/1,Transform.move_y/1,Transform.rotate/1,Transform.scale/1,Transform.alpha/1
Other attrs, such as events, alignment, and font family/style/weight helpers, are not animatable.
Layout
Animation overlays are applied before measurement and layout each frame.
That means animated layout attrs such as width/1, height/1, padding/1,
spacing/1, scale/1, and rotate/1 participate in relayout. The first
keyframe also establishes the initial layout state before any animation time
has elapsed.
Dimension transitions interpolate native resolved allocation, not fill weights. Related finite geometry owners keep their own clocks; an early finisher holds its allocation until their group can release together. Loops are not finite waiters. Text, images, scroll clipping and hit regions follow the real layout.
If native frame preparation fails, the last published layout and hit regions remain available. Repeated animation-only retries back off to a maximum 250ms delay; external updates and explicit retries are not throttled.
Summary
Functions
Animate animatable attrs across keyframes during normal updates.
Animate animatable attrs when the element first mounts.
Animate animatable attrs once when the element is removed from the tree.
Animate retained attributes when their desired values change.
Types
@type animate_attr() :: {:animate, spec_map()}
@type animate_enter_attr() :: {:animate_enter, spec_map()}
@type animate_exit_attr() :: {:animate_exit, spec_map()}
@type change_attr() :: {:animate_change, {Emerge.UI.attrs(), number(), curve()}}
@type curve() :: :linear | :ease_in | :ease_out | :ease_in_out
@type keyframe() :: Emerge.UI.attrs() | %{optional(atom()) => term()}
@type keyframes() :: [keyframe()]
@type repeat() :: :once | :loop | pos_integer()
@type t() :: animate_attr() | animate_enter_attr() | animate_exit_attr() | change_attr()
Functions
@spec animate(keyframes(), number(), curve(), repeat()) :: animate_attr()
Animate animatable attrs across keyframes during normal updates.
@spec animate_enter(keyframes(), number(), curve(), repeat()) :: animate_enter_attr()
Animate animatable attrs when the element first mounts.
Unlike animate/4, this does not start if it is added later to an existing retained node,
and it does not hold its last keyframe.
If both animate_enter/4 and animate/4 are present, animate/4 starts after the
enter animation completes.
@spec animate_exit(keyframes(), number(), curve(), :once) :: animate_exit_attr()
Animate animatable attrs once when the element is removed from the tree.
The element keeps rendering until the exit animation completes.
animate_exit/4 is not allowed on the viewport root.
@spec change(Emerge.UI.attrs(), number(), curve()) :: change_attr()
Animate retained attributes when their desired values change.
Takes a list of non-nil animatable attributes sharing the duration and curve. An empty list is a no-op. Repeated attributes use the last value; separate calls can assign different timings to different properties.
First mount is immediate. Identical resolved targets and timing-only updates do not restart. Interruption starts from the current native presentation. A later plain attribute removes only its change policy.
Content width and height also transition when text, children or intrinsic font/image
measurements change, even if the declared length stays content(). Animation's own
layout samples do not restart the transition.
Animation.change([width(fill()), height(px(60))], 300, :ease_out)
# Updating the text animates the retained element's resolved width.
el([Animation.change([width(content())], 1000, :linear)], text(label))