Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 16 additions & 11 deletions packages/core/src/runtime/adapters/animejs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,44 @@ import { swallow } from "../diagnostics";
*
* ## Usage in a composition
*
* The v4 global `anime` is a namespace object, not a callable — `anime.animate()`,
* `anime.createTimeline()`. v3's `anime({ targets })` form does not exist in v4.
* (4.1+ also moved the bundles from `lib/` to `dist/bundles/`.)
*
* ```html
* <script src="https://cdn.jsdelivr.net/npm/animejs@4.0.2/lib/anime.iife.min.js"></script>
* <script src="https://cdn.jsdelivr.net/npm/animejs@4.5.0/dist/bundles/anime.umd.min.js"></script>
* <script>
* const anim = anime({
* targets: '.box',
* translateX: 250,
* const anim = anime.animate('.box', {
* x: 250,
* rotate: '1turn',
* duration: 2000,
* ease: 'outExpo',
* autoplay: false,
* });
* window.__hfAnime = window.__hfAnime || [];
* window.__hfAnime.push(anim);
* </script>
* ```
*
* Timelines work the same way:
* Timelines work the same way — note `add(targets, params, position)`:
*
* ```html
* <script>
* const tl = anime.timeline({ autoplay: false });
* tl.add({ targets: '.a', opacity: [0, 1], duration: 500 })
* .add({ targets: '.b', translateY: [-40, 0], duration: 400 });
* const tl = anime.createTimeline({ autoplay: false });
* tl.add('.a', { opacity: [0, 1], duration: 500 })
* .add('.b', { y: [-40, 0], duration: 400 });
* window.__hfAnime = window.__hfAnime || [];
* window.__hfAnime.push(tl);
* </script>
* ```
*
* Multiple instances are supported — all are seeked in sync.
*
* ## Auto-discovery
* ## Auto-discovery (v3 only — inert on v4)
*
* The adapter also checks `anime.running` for active instances
* (useful for compositions that forget to register manually).
* `discover()` checks `anime.running`, which v4 no longer exports, so it always
* returns empty against a v4 build. Compositions MUST push every instance onto
* `window.__hfAnime` themselves; an unregistered instance is never seeked.
*/
export function createAnimeJsAdapter(): RuntimeDeterministicAdapter {
return {
Expand Down
4 changes: 2 additions & 2 deletions skills-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"files": 17
},
"hyperframes-animation": {
"hash": "5bc2ce098387a547",
"hash": "2ce5ca7dbf361e27",
"files": 121
},
"hyperframes-cli": {
Expand All @@ -38,7 +38,7 @@
"files": 78
},
"hyperframes-keyframes": {
"hash": "a568c05c01b27461",
"hash": "d00744ff0e669624",
"files": 3
},
"hyperframes-registry": {
Expand Down
84 changes: 50 additions & 34 deletions skills/hyperframes-animation/adapters/animejs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,39 @@ description: Anime.js adapter patterns for HyperFrames. Use when writing Anime.j

HyperFrames can seek Anime.js instances through its `animejs` runtime adapter. The composition owns the animation objects; HyperFrames owns the clock.

**This page targets v4 (examples pinned to 4.5.0, MIT).** v4 is a hard break from v3 — there is no callable `anime()`, `easing:` is now `ease:`, and ease names lost their `ease` prefix. Writing v3 from memory produces a composition that throws or silently animates nothing.

The repo's own producer fixtures pin `animejs@4.0.2/lib/anime.iife.min.js`, which still resolves — but that build predates `splitText` / `scrambleText` / `createSeededRandom` / `createLayout` used below, and 4.1+ moved the bundles to `dist/bundles/`, so a version bump needs the path changed too.

## Contract

- Create animations or timelines synchronously during composition initialization.
- Set `autoplay: false` so Anime.js does not advance on its own clock.
- Register every returned animation or timeline on `window.__hfAnime`.
- Register every returned animation or timeline on `window.__hfAnime` — **explicitly. There is no working auto-discovery on v4** (see Avoid).
- Use finite durations and loop counts.
- Avoid callbacks that mutate DOM based on wall-clock time, network state, or unseeded randomness.

The adapter seeks every registered instance with `instance.seek(timeMs)`, where `timeMs` is HyperFrames time in milliseconds.
The adapter seeks every registered instance with `instance.seek(timeMs)`, where `timeMs` is HyperFrames time **in milliseconds** (`ctx.time` seconds × 1000). It also calls `pause()` and `play()` on each instance; anything exposing those three methods works, whatever created it.

## Loading v4

```html
<!-- UMD: the global `anime` is a NAMESPACE OBJECT, not a function -->
<script src="https://cdn.jsdelivr.net/npm/animejs@4.5.0/dist/bundles/anime.umd.min.js"></script>
```

`anime.animate(...)`, `anime.createTimeline(...)`, `anime.utils.*`, `anime.svg.*`, `anime.stagger(...)`. **Calling `anime(...)` is a TypeError** — every v4 build (UMD and IIFE alike) assigns a namespace object to the global, so v3's `anime({ targets })` form cannot work no matter which v4 file you load.

## Basic Pattern

```html
<script src="https://cdn.jsdelivr.net/npm/animejs@4.0.2/lib/anime.iife.min.js"></script>
<script>
const anim = anime({
targets: ".mark",
translateX: 280,
const anim = anime.animate(".mark", {
x: 280, // v4 shorthand for translateX
rotate: "1turn",
opacity: [0, 1],
duration: 1200,
easing: "easeOutExpo",
ease: "outExpo", // NOT easing: "easeOutExpo"
autoplay: false,
});

Expand All @@ -39,64 +50,68 @@ The adapter seeks every registered instance with `instance.seek(timeMs)`, where

## Timeline Pattern

`createTimeline` replaces `anime.timeline`, and `add()` takes **targets as its first argument** — `add(targets, parameters, position)`:

```html
<script>
const tl = anime.timeline({
const tl = anime.createTimeline({
autoplay: false,
easing: "easeOutCubic",
defaults: { ease: "outCubic" }, // per-timeline defaults, not a bare `easing`
});

tl.add({
targets: ".title",
translateY: [40, 0],
opacity: [0, 1],
duration: 650,
}).add(
{
targets: ".accent",
scaleX: [0, 1],
duration: 450,
},
250,
);
tl.add(".title", { y: [40, 0], opacity: [0, 1], duration: 650 });
tl.add(".accent", { scaleX: [0, 1], duration: 450 }, 250); // 250 = time position

window.__hfAnime = window.__hfAnime || [];
window.__hfAnime.push(tl);
</script>
```

Position accepts a number, a label, `"+=250"` / `"-=100"`, `"<"` (previous **end**) and `"<<"` (previous **start**).

## Module Builds

If you use an ES module build, the adapter does not care how the instance was created. It only needs the returned object to expose `seek()`, `pause()`, and preferably `play()`:
The adapter does not care how the instance was created only that it exposes `seek()`, `pause()`, and `play()`:

```html
<script type="module">
import { animate } from "https://cdn.jsdelivr.net/npm/animejs/+esm";
import { animate } from "https://cdn.jsdelivr.net/npm/animejs@4.5.0/+esm";

const anim = animate(".chip", {
x: "18rem",
duration: 900,
autoplay: false,
});
const anim = animate(".chip", { x: "18rem", duration: 900, autoplay: false });

window.__hfAnime = window.__hfAnime || [];
window.__hfAnime.push(anim);
</script>
```

## Determinism

v4 ships `createSeededRandom(seed)` — use it instead of `Math.random()` when a composition needs scatter/jitter, so the same frame renders the same on every pass:

```js
const rnd = anime.createSeededRandom(1337);
anime.animate(".dot", { y: () => -40 * rnd(), duration: 800, autoplay: false });
```

`anime.utils.random()` / `randomPick()` / `shuffle()` are **not** seeded — they break frame-to-frame reproducibility.

## Good Uses

- Small SVG and DOM flourishes where Anime.js syntax is compact.
- Imported Anime.js examples that can be made seek-driven.
- Free `splitText` / `scrambleText` (Motion puts these behind Motion+; GSAP SplitText is the other free option).
- `svg.createDrawable` / `svg.morphTo` / `svg.createMotionPath` line-draw and path work.
- Multiple independent micro-animations pushed into the same registry.

Use GSAP for complex scene sequencing unless the user specifically asks for Anime.js. GSAP is still the primary HyperFrames authoring path.

## Avoid

- Leaving `autoplay` at the Anime.js default.
- Depending on `anime.running` auto-discovery instead of explicit `window.__hfAnime.push(...)`.
- Infinite loops. Compute a finite repeat count from the composition duration.
- **Relying on the adapter's `anime.running` auto-discovery — it cannot work on v4.** `running` is not among v4.5.0's exports (verified against the published bundle), so `discover()` returns immediately and any instance you did not `push()` is never seeked. Explicit registration is mandatory, not a nicety.
- `autoplay: onScroll(...)` — there is no scroll in a headless seek render, so the animation would never advance. Drive it off composition time instead.
- `waapi.animate()` for anything the adapter must seek — the adapter seeks via `.seek()`, and whether WAAPI-backed instances honor it is **unverified**. Use the JS engine (`animate`) for rendered compositions; `waapi` is an off-main-thread optimization for live pages.
- `createDraggable`, and any pointer-driven `createAnimatable` loop — input does not exist at render time.
- Infinite loops. Compute a finite repeat count from the composition duration (v4 `loop` counts **repeats**: `loop: 1` plays twice).
- Building animations in timers, promises, event handlers, or after async asset loads.

## Validation
Expand All @@ -105,10 +120,11 @@ After editing a composition that uses Anime.js:

```bash
npx hyperframes lint
npx hyperframes check
npx hyperframes validate
```

## Credits And References

- HyperFrames adapter source: `packages/core/src/runtime/adapters/animejs.ts`.
- Anime.js documentation for `autoplay`, `pause()`, and `seek()`: https://animejs.com/documentation/
- Anime.js v4 docs: https://animejs.com/documentation/
- v3 → v4 migration (not on animejs.com): https://github.com/juliangarnier/anime/wiki/Migrating-from-v3-to-v4
6 changes: 4 additions & 2 deletions skills/hyperframes-keyframes/references/keyframe-patterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ CSS:
Anime.js:

```js
const animation = anime.timeline({ autoplay: false });
animation.add({ targets: "<selector>" /* derived channels */ });
const animation = anime.createTimeline({ autoplay: false });
animation.add("<selector>", {
/* derived channels */
});
window.__hfAnime = window.__hfAnime || [];
window.__hfAnime.push(animation);
```
Expand Down
Loading