docs(skills): fix anime.js v3 syntax in v4 adapter guidance (#3064)

* docs(skills): fix anime.js v3 syntax in v4 adapter guidance

The animejs adapter docs teach v3 syntax against a v4 build, so the examples
cannot run as written: every v4 bundle assigns a *namespace object* to the
global `anime`, making v3's `anime({ targets })` call form a TypeError. `easing:`
is now `ease:`, ease names lost their `ease` prefix, and `timeline.add()` takes
`(targets, parameters, position)`.

- Rewrite `skills/hyperframes-animation/adapters/animejs.md` for v4:
  `anime.animate()` / `anime.createTimeline()`, `ease:` names, targets-first
  `add()`, position shorthands, and a note that the producer fixtures pin
  4.0.2 (`lib/`) while 4.1+ moved bundles to `dist/bundles/`.
- Fix the same v3 `anime.timeline({ targets })` snippet in
  `skills/hyperframes-keyframes/references/keyframe-patterns.md`.
- Stop advertising `anime.running` auto-discovery as a safety net. No v4 build
  exports `running` (checked 4.0.2 and 4.5.0), so `discover()` returns
  immediately and any instance a composition forgets to push onto
  `window.__hfAnime` is silently never seeked. Marked v3-only/inert in the
  skill page and the adapter docstring; explicit registration is now stated
  as mandatory.
- Add render-safety notes the page lacked: `createSeededRandom()` as the
  deterministic replacement for `Math.random()`, and why
  `autoplay: onScroll(...)`, `createDraggable`, and pointer-driven
  `createAnimatable` cannot work under headless seek rendering.
- Regenerate skills-manifest.json.

Runtime behaviour is unchanged: the `packages/core` edit is comment-only, and
the seek path already works on v4 (registered instances expose
seek/pause/play). The now-dead `anime.running` branch in `discover()` is left
in place — it is guarded and try/caught, and removing it is a behaviour change
that belongs in its own PR.

* fix(core): align Anime.js globals with v4

---------

Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com>
This commit is contained in:
focsuer
2026-08-16 14:01:38 -04:00
committed by GitHub
co-authored by Miguel Ángel
parent fecaf72d1f
commit 67edb01bf4
7 changed files with 147 additions and 58 deletions
@@ -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,
});
@@ -39,55 +50,56 @@ 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.
@@ -95,8 +107,11 @@ Use GSAP for complex scene sequencing unless the user specifically asks for Anim
## 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
@@ -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