Animation¶
Rezi supports declarative motion in two layers:
- Hook-driven numeric animation for custom widget behavior (
useTransition,useSpring,useSequence,useStagger,useAnimatedValue,useParallel,useChain). - Container transitions for layout/surface animation on
ui.box(...),ui.row(...),ui.column(...), andui.grid(...)viatransition/exitTransitionprops.
Hook animations share a frame driver inside the widget runtime, so concurrent animations advance together instead of each hook spinning its own timer loop.
Choose the right API¶
- Use
useTransition(...)for direct time-based interpolation between numeric targets. - Use
useSpring(...)for natural motion driven by spring physics. - Use
useSequence(...)for keyframe timelines. - Use
useStagger(...)for list/rail entry timing offsets. - Use
useAnimatedValue(...)when you needvalue + velocity + isAnimatingmetadata. - Use
useParallel(...)for multiple transitions that run together. - Use
useChain(...)for step-by-step transitions. - Use container
transitionprops when you want runtime-managed position/size/opacity transitions without writing hook logic.
Easing Families and Usage¶
- Linear: constant-rate movement for utility transitions.
- Quad: subtle, low-energy transitions for small UI nudges.
- Cubic: standard default-feeling motion for most interactions.
Easing Presets Reference¶
| Preset | Family | Typical use |
|---|---|---|
linear |
linear | Utility animation, deterministic rate |
easeInQuad |
quad | Gentle acceleration in |
easeOutQuad |
quad | Gentle deceleration out |
easeInOutQuad |
quad | Soft in/out transitions |
easeInCubic |
cubic | Stronger acceleration |
easeOutCubic |
cubic | Standard UI deceleration |
easeInOutCubic |
cubic | Balanced default for panels/opacity |
easeInExpo |
expo | Fast late acceleration |
easeOutExpo |
expo | Fast early arrival |
easeInOutExpo |
expo | Dramatic in/out motion |
easeInBack |
back | Anticipation before moving forward |
easeOutBack |
back | Overshoot on settle |
easeInOutBack |
back | Anticipation + overshoot |
easeOutBounce |
bounce | Playful settling |
easeInBounce |
bounce | Reverse bounce-in |
Hook Example¶
import {
defineWidget,
ui,
useAnimatedValue,
useSequence,
useStagger,
} from "@rezi-ui/core";
type ReactorProps = {
target: number;
modules: readonly string[];
key?: string;
};
export const ReactorRow = defineWidget<ReactorProps>((props, ctx) => {
const energy = useAnimatedValue(ctx, props.target, {
mode: "spring",
spring: { stiffness: 190, damping: 22, onComplete: () => {} },
});
const pulse = useSequence(ctx, [0.25, 1, 0.4, 0.9], {
duration: 120,
loop: true,
playback: { rate: 1 },
onComplete: () => {},
});
const stagger = useStagger(ctx, props.modules, {
delay: 40,
duration: 180,
easing: "easeOutCubic",
onComplete: () => {},
});
return ui.column({ gap: 1 }, [
ui.text(
`value=${energy.value.toFixed(2)} vel=${energy.velocity.toFixed(2)} anim=${energy.isAnimating}`,
),
ui.row(
{ gap: 1 },
props.modules.map((label, i) =>
ui.box(
{
key: label,
border: "rounded",
p: 1,
opacity: (0.3 + 0.7 * (stagger[i] ?? 0)) * pulse,
},
[ui.text(label)],
),
),
),
]);
});
Retargeting and Completion Semantics¶
- Retargeting mid-flight starts from the current interpolated value (no jump).
- Looping timelines (
useSequence(..., { loop: true })) continue indefinitely. onCompleteis supported onuseTransition,useSpring,useSequence,useStagger,useAnimatedValue,useParallel, anduseChain.useAnimatedValue(..., { mode: "transition" })honors transition playback controls (paused,reversed,rate) and resumes from the retained transition progress.useStaggertracks item identity, not just item count, so same-length replacements restart the stagger.
Container Transitions¶
Container widgets support declarative render-time transitions:
ui.box({ transition })ui.row({ transition })ui.column({ transition })ui.grid({ transition })
ui.row(
{
key: "status-row",
gap: 1,
transition: {
duration: 220,
easing: "easeInOutCubic",
properties: ["position", "size"],
},
},
[ui.text("Animated row")],
);
Behavior:
propertiesdefaults to"all"when omitted (position,size, andopacity).properties: []disables all transition tracks.opacityis clamped to[0..1].- Opacity animation currently affects
boxsurfaces;row/column/gridopacity transitions are no-op and resolve to opacity1.
Exit Animations¶
Use exitTransition to animate a container out before unmount cleanup:
ui.box(
{
key: "toast",
border: "rounded",
p: 1,
transition: { duration: 160, easing: "easeOutCubic" },
exitTransition: { duration: 200, easing: "easeInCubic", properties: ["opacity"] },
},
[ui.text("Saved")],
);
Lifecycle:
- On removal, the runtime keeps the keyed subtree in an exit track.
- The exit node is rendered during the transition window.
- Exit nodes do not participate in focus traversal or hit-testing metadata.
- When exit duration completes, deferred local-state cleanup runs and the subtree is removed.
- If the same keyed node reappears before completion, the exit animation is canceled.
Playback Control¶
useTransition and useSequence support:
paused: truefreezes at the current sampled value.reversed: trueruns timeline time backward.ratescales elapsed time (0.5half-speed,2double-speed).
Color Interpolation¶
Use animation utilities for RGB interpolation:
import { interpolateRgb, interpolateRgbArray, rgb } from "@rezi-ui/core";
const mid = interpolateRgb(rgb(0, 0, 0), rgb(255, 255, 255), 0.5);
const ramp = interpolateRgbArray(rgb(0, 40, 80), rgb(220, 200, 40), 8);
- Channels are linearly interpolated in RGB space.
- Output channels are clamped to integer
[0..255].
Orchestration¶
Use orchestration helpers for grouped transitions:
useParallel(ctx, animations)runs multiple transitions concurrently and returns per-entry{ value, isAnimating }.useChain(ctx, steps)runs sequential transitions and returns{ value, currentStep, isComplete }.
useAnimatedValue¶
useAnimatedValue composes transition/spring strategies behind one API:
const animated = useAnimatedValue(ctx, target, {
mode: "transition",
transition: { duration: 180, easing: "easeOutCubic" },
});
animated.value;
animated.velocity;
animated.isAnimating;
Defaults and Safety¶
- Hook durations are normalized to safe non-negative integer milliseconds.
- Hook delay defaults:
useTransition:0msuseSpring:0msuseTransitiondefault duration:160ms.useSequencedefault per-segment duration:160ms.useStaggerdefaults:delay=40ms,duration=180ms.- Container transition default duration:
180ms. - Non-finite numeric targets snap safely instead of producing unstable interpolation.
Performance Guidance¶
- Drive animation from state targets; avoid ad-hoc timer loops in app/view code.
- Keep animated values numeric and local; derive display strings lazily in render.
- For large animated collections, combine
useStagger(...)with windowing (ui.virtualList(...)) to cap visible work.