Skip to content

Custom Animations

Lasse Nielsen edited this page Feb 24, 2026 · 2 revisions

Custom Animations

LibAnimate's RegisterAnimation API lets you define and register your own animations using the same keyframe system as the built-in animations.

Basic Structure

Every animation definition has this structure:

LibAnimate:RegisterAnimation("myAnimation", {
    type = "entrance",          -- "entrance", "exit", or "attention"
    defaultDuration = 0.6,      -- default duration in seconds
    defaultDistance = 300,       -- default distance in pixels (optional)
    keyframes = {
        { progress = 0.0, ... },
        { progress = 1.0, ... },
    },
})

Keyframe Properties

Each keyframe is a table with a required progress field and optional property fields:

Property Type Default Description
progress number - Required. Time point from 0.0 (start) to 1.0 (end)
translateX number 0 Horizontal offset as a fraction of distance
translateY number 0 Vertical offset as a fraction of distance
scale number 1.0 Uniform scale factor (minimum 0.001)
alpha number 1.0 Opacity from 0.0 (invisible) to 1.0 (opaque)
easing EasingSpec nil Easing for the segment starting at this keyframe

Understanding progress

The progress value represents a point in the animation timeline:

  • 0.0 = the very start of the animation
  • 0.5 = halfway through
  • 1.0 = the very end

Between keyframes, property values are linearly interpolated (after applying any easing function).

Understanding translateX and translateY

Translation values are fractions of the distance parameter, not raw pixel values. This makes animations resolution-independent:

-- If distance = 300:
-- translateY = 1.0 means 300 pixels
-- translateY = 0.5 means 150 pixels
-- translateY = -1.0 means -300 pixels

Coordinate system: WoW uses positive Y = up (opposite of CSS). In LibAnimate's built-in animations, translateY = 1.0 means "above the anchor point" and translateY = -1.0 means "below."

Understanding easing

The easing field on a keyframe controls the interpolation for the segment starting at that keyframe. The last keyframe's easing is ignored (there's no segment after it).

keyframes = {
    { progress = 0.0, alpha = 0, easing = "easeOutCubic" },  -- segment 0 to 0.5 uses easeOutCubic
    { progress = 0.5, alpha = 0.8, easing = "easeInQuad" },  -- segment 0.5 to 1.0 uses easeInQuad
    { progress = 1.0, alpha = 1.0 },                          -- no segment after this
}

Examples

Simple Fade-Scale Entrance

LibAnimate:RegisterAnimation("gentleAppear", {
    type = "entrance",
    defaultDuration = 0.5,
    keyframes = {
        { progress = 0.0, alpha = 0, scale = 0.8 },
        { progress = 1.0, alpha = 1.0, scale = 1.0 },
    },
})

Multi-Stage Entrance with Overshoot

LibAnimate:RegisterAnimation("popIn", {
    type = "entrance",
    defaultDuration = 0.6,
    keyframes = {
        { progress = 0.0, alpha = 0, scale = 0.3, easing = "easeOutCubic" },
        { progress = 0.6, alpha = 1.0, scale = 1.1 },
        { progress = 1.0, scale = 1.0 },
    },
})

Attention Seeker (Blink)

Attention seekers must return to the original state at progress = 1.0:

LibAnimate:RegisterAnimation("blink", {
    type = "attention",
    defaultDuration = 1.0,
    keyframes = {
        { progress = 0.0, alpha = 1.0 },
        { progress = 0.25, alpha = 0 },
        { progress = 0.5, alpha = 1.0 },
        { progress = 0.75, alpha = 0 },
        { progress = 1.0, alpha = 1.0 },
    },
})

Exit with Directional Movement

LibAnimate:RegisterAnimation("shrinkOut", {
    type = "exit",
    defaultDuration = 0.4,
    defaultDistance = 200,
    keyframes = {
        { progress = 0.0, alpha = 1.0, scale = 1.0, translateY = 0, easing = "easeInBack" },
        { progress = 1.0, alpha = 0, scale = 0.3, translateY = -0.5 },
    },
})

-- Usage: hide the frame in onFinished
LibAnimate:Animate(frame, "shrinkOut", {
    onFinished = function(f) f:Hide() end,
})

Custom Cubic-Bezier Easing

Use a {p1x, p1y, p2x, p2y} table for custom easing curves:

LibAnimate:RegisterAnimation("elasticPop", {
    type = "entrance",
    defaultDuration = 0.8,
    keyframes = {
        { progress = 0.0, scale = 0, alpha = 0, easing = {0.34, 1.56, 0.64, 1.0} },
        { progress = 0.7, scale = 1.05, alpha = 1.0 },
        { progress = 1.0, scale = 1.0 },
    },
})

Complex Multi-Keyframe Animation

LibAnimate:RegisterAnimation("dramaticEntry", {
    type = "entrance",
    defaultDuration = 1.2,
    defaultDistance = 400,
    keyframes = {
        { progress = 0.0, translateY = 1.0, alpha = 0, scale = 0.5, easing = "easeOutCubic" },
        { progress = 0.4, translateY = 0, alpha = 0.8, scale = 1.15 },
        { progress = 0.6, scale = 0.95, alpha = 0.9, easing = "easeInOutQuad" },
        { progress = 0.8, scale = 1.05, alpha = 1.0 },
        { progress = 1.0, scale = 1.0, alpha = 1.0 },
    },
})

Validation Rules

RegisterAnimation validates your definition and throws descriptive errors:

Rule Error Message
Name must be a non-empty string "name must be a non-empty string"
Definition must be a table "definition must be a table"
Type must be valid "type must be 'entrance', 'exit', or 'attention'"
At least 2 keyframes "keyframes must be a table with at least 2 entries"
Keyframes sorted by progress "keyframes must be sorted by ascending progress"

Tips

  1. Start simple - Begin with 2 keyframes, then add intermediate ones for complexity
  2. Use easing - Per-segment easing makes animations feel natural
  3. Test with different distances - Your translateX/translateY values scale with distance
  4. Attention seekers must loop back - Ensure progress = 1.0 returns all properties to their defaults (scale=1, alpha=1, translate=0)
  5. Scale minimum - Scale is clamped to 0.001 to prevent WoW engine errors
  6. Overwriting - Registering with an existing name silently overwrites the old animation

See Also