Emerge.UI.Animation (Emerge v0.5.1)

Copy Markdown View Source

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/2 and max/2 remain static layout expressions and cannot be animation endpoints
  • padding(8) can animate to padding(16), but not to padding_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/1 and Border.glow/2 must 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

animate_attr()

@type animate_attr() :: {:animate, spec_map()}

animate_enter_attr()

@type animate_enter_attr() :: {:animate_enter, spec_map()}

animate_exit_attr()

@type animate_exit_attr() :: {:animate_exit, spec_map()}

change_attr()

@type change_attr() :: {:animate_change, {Emerge.UI.attrs(), number(), curve()}}

curve()

@type curve() :: :linear | :ease_in | :ease_out | :ease_in_out

keyframe()

@type keyframe() :: Emerge.UI.attrs() | %{optional(atom()) => term()}

keyframes()

@type keyframes() :: [keyframe()]

repeat()

@type repeat() :: :once | :loop | pos_integer()

spec_map()

@type spec_map() :: %{
  :keyframes => keyframes(),
  :duration => number(),
  :curve => curve(),
  optional(:repeat) => repeat()
}

t()

Functions

animate(keyframes, duration, curve, repeat \\ :once)

@spec animate(keyframes(), number(), curve(), repeat()) :: animate_attr()

Animate animatable attrs across keyframes during normal updates.

animate_enter(keyframes, duration, curve, repeat \\ :once)

@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.

animate_exit(keyframes, duration, curve, repeat \\ :once)

@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.

change(attrs, duration, curve)

@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))