Styles & Building Bindings
How animation values become CSS, and the helpers to write your own framework binding.
The framework bindings are thin layers over a few core pieces. They are public, so you can use them to integrate Ani with any other UI layer.
From values to CSS: createStyleSheet
createStyleSheet(values, resolver?) converts an animation group ({ translateX: 100, opacity: 0.5 }) into a style object.
| Property | Output |
|---|---|
translate, translateX/Y/Z | translateX(100px) (px) |
rotate, rotateX/Y/Z, skew, skewX/Y | rotate(45deg) (deg) |
scale, scaleX/Y/Z | scale(1.2) (unitless) |
width, height, top, left, margin*, padding*, borderRadius, ... | 100px |
opacity, zIndex, lineHeight, fontWeight | unitless number |
| anything else | the number as is |
All transform functions are joined in key order into one transform string.
A property such as x is not a transform shorthand: it is passed through as a style key named x. Use translateX, or map it with a resolver.
import { createStyleSheet } from "@freestylejs/ani-core";
const style = createStyleSheet({ translateX: 100, opacity: 0.5 });
// { transform: 'translateX(100px)', opacity: 0.5 }Custom resolver
Pass a resolver to bind() to control the style key and value of a property. The returned key may be a CSS custom property (--progress) or a hyphenated name.
import * as a from "@freestylejs/ani-core";
const timeline = a.rafTimeline(a.ani({ to: { progress: 100 }, duration: 1 }));
const element = document.createElement("div");
timeline.bind(element, {
resolver: {
progress: (value) => ({ key: "--progress", value: `${value}%` }),
},
});
timeline.play({ from: { progress: 0 } });Writing styles: bind
timeline.bind(element, { resolver }) writes the value to the element on every notification, in the commit phase and only for the declarations that changed:
- camelCase keys (
opacity,transform) are assigned as properties. --customand hyphenated keys (--progress,background-color) go throughstyle.setProperty. Plain property assignment silently ignores them.
The same method exists on every animated object: rafGroup has bind(targets) and bindAt(index, target), createStates and follow have bind(target). Each returns an unbind function.
import * as a from "@freestylejs/ani-core";
const timeline = a.rafTimeline(a.ani({ to: { opacity: 1 }, duration: 1 }));
const element = document.createElement("div");
const unbind = timeline.bind(element);
// when the owner goes away
unbind();createStyleSheet(values, resolver?) is the pure part: use it where nothing can be bound yet, for example to render the first paint on the server.
A stable controller: createTimelineController
State machines replace their timeline on every transition. createTimelineController(getTimeline, hooks?) returns a facade that forwards every public member of the timeline to whichever timeline getTimeline() returns at call time, so references handed to your users never go stale.
import * as a from "@freestylejs/ani-core";
const states = a.createStates({
initial: "idle",
initialValue: { opacity: 0 },
states: { idle: a.ani({ to: { opacity: 1 }, duration: 0.2 }) },
});
const controller = a.createTimelineController(() => states.timeline(), {
onPlay: (config) => console.log(config.from), // runs before each forwarded play()
onReset: () => console.log("reset"), // runs after each forwarded reset()
});
controller.play({ from: { opacity: 0 } }); // plays on the current timelineisRafTimeline(controller)istruefor the facade.dispose()is forwarded as is. A binding must only call it on timelines it created itself.
Binding checklist
- Write styles with
bind(bindAtfor list items,machine.bind,follower.bind) instead of re-rendering per frame. See large-scale usage. - Follow timeline swaps with the machine:
machine.bindandmachine.onUpdatealready follow every transition, andmachine.onTransitiontells you when the timeline changes. - Dispose what you create (state machines, timelines, followers), and only detach from what the user passes in.
- Render the initial style on the server with
createStyleSheet, and never write to the DOM on the server. Subscribe after mount. See SSR. - Leave events to the framework. Core has no event layer: the user attaches handlers with the framework's own syntax and calls the timeline.
Related Components
rafTimeline- Providesconfig,onUpdate,bindandvalue.createStates- The state machine whose timeline the controller follows.