halo DocsGitHub

Animation

Changes are instant unless you ask. Core Animation does the work, off the main thread.

Transitions

Animate style changes:

<Box
  style={{
    scale: () => (hovered() ? 1.05 : 1),
    background: () => (active() ? "#89b4fa" : "#313244"),
    transition: { scale: { curve: "spring" }, background: 150, layout: 200 },
  }}
/>

Keys: any animatable property, all, or layout (position and size). On a <Shape> also fill, stroke, track, strokeWidth, strokeStart, strokeEnd and path (morphs between paths with the same commands). Values: milliseconds or { duration, curve, delay }.

Curves

"linear", "ease-in", "ease-out" (default), "ease-in-out", cubic-bezier [x1, y1, x2, y2], "spring", or { stiffness, damping, mass }. Springs ignore duration.

Enter and exit

<Box
  enter={{ from: { opacity: 0, translateY: 8 }, duration: 200 }}
  exit={{ to: { opacity: 0, scale: 0.9 }, duration: 150 }}
/>

On elements, when they appear or are removed. On a <Surface>, every time it's shown or hidden; hiding waits for the exit.

In a list, use <For> so only the items that come and go animate; a plain {() => items().map(…)} rebuilds, and so re-enters, every row.

<For each={notifications} key={(n) => n.id}>
  {(n) => <Row title={n().title} enter={…} exit={…} />}
</For>

Keyframes

const spin = animate(
  icon,
  { rotate: [0, 360] },
  { duration: 1000, repeat: Infinity, curve: "linear" },
);
spin.cancel();

Options: duration, curve, delay, repeat, alternate, offsets, fill.

Per frame

For anything else, JS runs on the display's refresh:

const shown = tween(() => total(), { duration: 400 }); // a number that follows `total` smoothly
onFrame((time, dt) => {
  /* return false to stop */
});

Reference: animate, Transitions.