mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
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:
+1
-1
@@ -48,7 +48,7 @@
|
|||||||
"player:perf": "bun run --filter @hyperframes/player perf",
|
"player:perf": "bun run --filter @hyperframes/player perf",
|
||||||
"format:check": "oxfmt --check .",
|
"format:check": "oxfmt --check .",
|
||||||
"knip": "knip",
|
"knip": "knip",
|
||||||
"test:scripts": "node --import tsx --test scripts/check-tracked-artifacts.test.mjs scripts/check-no-main-deletions.test.mjs scripts/check-docs-snippet-motion.test.mjs scripts/registry-target-paths.test.mjs scripts/check-workspace-contracts.test.mjs scripts/check-package-cycles.test.mjs scripts/check-cli-process-ownership.test.mjs scripts/package-subpaths.test.mjs scripts/validate-release-channel.test.mjs scripts/publish-workflow.test.mjs scripts/install-workspace-dependencies.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/catalog-payload-assets.test.ts scripts/catalog-preview-temp.test.ts scripts/studio-runtime-smoke.test.mjs scripts/verify-packed-manifests.test.mjs scripts/lint-skills.test.mjs packages/gcp-cloud-run/check-dockerfile-workspaces.test.mjs && vitest run scripts/catalog/",
|
"test:scripts": "node --import tsx --test scripts/animejs-v4-guidance.test.mjs scripts/check-tracked-artifacts.test.mjs scripts/check-no-main-deletions.test.mjs scripts/check-docs-snippet-motion.test.mjs scripts/registry-target-paths.test.mjs scripts/check-workspace-contracts.test.mjs scripts/check-package-cycles.test.mjs scripts/check-cli-process-ownership.test.mjs scripts/package-subpaths.test.mjs scripts/validate-release-channel.test.mjs scripts/publish-workflow.test.mjs scripts/install-workspace-dependencies.test.mjs scripts/draft-changelog.test.ts scripts/set-version.test.ts scripts/release-prepare.test.ts scripts/cli-options.test.ts scripts/changelog-weekly.test.ts scripts/claude-plugin-compression.test.ts scripts/catalog-payload-assets.test.ts scripts/catalog-preview-temp.test.ts scripts/studio-runtime-smoke.test.mjs scripts/verify-packed-manifests.test.mjs scripts/lint-skills.test.mjs packages/gcp-cloud-run/check-dockerfile-workspaces.test.mjs && vitest run scripts/catalog/",
|
||||||
"typecheck:scripts": "tsc --noEmit -p scripts/tsconfig.json",
|
"typecheck:scripts": "tsc --noEmit -p scripts/tsconfig.json",
|
||||||
"test:skills": "node --test 'skills/**/*.test.mjs'",
|
"test:skills": "node --test 'skills/**/*.test.mjs'",
|
||||||
"generate:previews": "tsx scripts/generate-template-previews.ts",
|
"generate:previews": "tsx scripts/generate-template-previews.ts",
|
||||||
|
|||||||
@@ -8,14 +8,18 @@ import { swallow } from "../diagnostics";
|
|||||||
*
|
*
|
||||||
* ## Usage in a composition
|
* ## 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
|
* ```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>
|
* <script>
|
||||||
* const anim = anime({
|
* const anim = anime.animate('.box', {
|
||||||
* targets: '.box',
|
* x: 250,
|
||||||
* translateX: 250,
|
|
||||||
* rotate: '1turn',
|
* rotate: '1turn',
|
||||||
* duration: 2000,
|
* duration: 2000,
|
||||||
|
* ease: 'outExpo',
|
||||||
* autoplay: false,
|
* autoplay: false,
|
||||||
* });
|
* });
|
||||||
* window.__hfAnime = window.__hfAnime || [];
|
* window.__hfAnime = window.__hfAnime || [];
|
||||||
@@ -23,13 +27,13 @@ import { swallow } from "../diagnostics";
|
|||||||
* </script>
|
* </script>
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* Timelines work the same way:
|
* Timelines work the same way — note `add(targets, params, position)`:
|
||||||
*
|
*
|
||||||
* ```html
|
* ```html
|
||||||
* <script>
|
* <script>
|
||||||
* const tl = anime.timeline({ autoplay: false });
|
* const tl = anime.createTimeline({ autoplay: false });
|
||||||
* tl.add({ targets: '.a', opacity: [0, 1], duration: 500 })
|
* tl.add('.a', { opacity: [0, 1], duration: 500 })
|
||||||
* .add({ targets: '.b', translateY: [-40, 0], duration: 400 });
|
* .add('.b', { y: [-40, 0], duration: 400 });
|
||||||
* window.__hfAnime = window.__hfAnime || [];
|
* window.__hfAnime = window.__hfAnime || [];
|
||||||
* window.__hfAnime.push(tl);
|
* window.__hfAnime.push(tl);
|
||||||
* </script>
|
* </script>
|
||||||
@@ -37,10 +41,11 @@ import { swallow } from "../diagnostics";
|
|||||||
*
|
*
|
||||||
* Multiple instances are supported — all are seeked in sync.
|
* 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
|
* `discover()` checks `anime.running`, which v4 no longer exports, so it always
|
||||||
* (useful for compositions that forget to register manually).
|
* 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 {
|
export function createAnimeJsAdapter(): RuntimeDeterministicAdapter {
|
||||||
return {
|
return {
|
||||||
@@ -133,9 +138,10 @@ interface AnimeInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface AnimeGlobal {
|
interface AnimeGlobal {
|
||||||
(params: unknown): AnimeInstance;
|
animate?: (targets: unknown, params?: unknown) => AnimeInstance;
|
||||||
timeline?: (params?: unknown) => AnimeInstance;
|
createTimeline?: (params?: unknown) => AnimeInstance;
|
||||||
running: AnimeInstance[];
|
/** Legacy v3 registry retained for backward-compatible discovery. */
|
||||||
|
running?: AnimeInstance[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AnimeWindow extends Window {
|
interface AnimeWindow extends Window {
|
||||||
|
|||||||
+7
-5
@@ -97,13 +97,15 @@ declare global {
|
|||||||
};
|
};
|
||||||
THREE?: ThreeLike;
|
THREE?: ThreeLike;
|
||||||
/**
|
/**
|
||||||
* Global anime.js instance (set by including the anime.iife.min.js script).
|
* Global Anime.js v4 namespace (set by the UMD or IIFE bundle).
|
||||||
* The adapter uses `anime.running` for auto-discovery.
|
* Register returned instances on `window.__hfAnime`; v4 has no
|
||||||
|
* `anime.running` auto-discovery registry.
|
||||||
*/
|
*/
|
||||||
anime?: {
|
anime?: {
|
||||||
(params: unknown): unknown;
|
animate?: (targets: unknown, params?: unknown) => unknown;
|
||||||
timeline?: (params?: unknown) => unknown;
|
createTimeline?: (params?: unknown) => unknown;
|
||||||
running: unknown[];
|
/** Legacy v3 registry retained for backward-compatible discovery. */
|
||||||
|
running?: unknown[];
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* anime.js instances registered by compositions.
|
* anime.js instances registered by compositions.
|
||||||
|
|||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import test from "node:test";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const repoRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
|
||||||
|
|
||||||
|
const guidance = [
|
||||||
|
{
|
||||||
|
path: "packages/core/src/runtime/adapters/animejs.ts",
|
||||||
|
required: ["anime.animate", "anime.createTimeline"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "skills/hyperframes-animation/adapters/animejs.md",
|
||||||
|
required: ["anime.animate", "anime.createTimeline"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "skills/hyperframes-keyframes/references/keyframe-patterns.md",
|
||||||
|
required: ["anime.createTimeline"],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function read(path) {
|
||||||
|
return readFileSync(join(repoRoot, path), "utf8");
|
||||||
|
}
|
||||||
|
|
||||||
|
function fencedCode(source) {
|
||||||
|
const normalized = source.replace(/^\s*\* ?/gm, "");
|
||||||
|
return [...normalized.matchAll(/```[^\n]*\n([\s\S]*?)```/g)].map((match) => match[1]).join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { path, required } of guidance) {
|
||||||
|
test(`${path} uses Anime.js v4 call sites in executable examples`, () => {
|
||||||
|
const snippets = fencedCode(read(path));
|
||||||
|
for (const call of required) {
|
||||||
|
assert.match(snippets, new RegExp(`\\b${call.replace(".", "\\.")}\\s*\\(`));
|
||||||
|
}
|
||||||
|
assert.doesNotMatch(snippets, /\banime\s*\(\s*\{/);
|
||||||
|
assert.doesNotMatch(snippets, /\banime\.timeline\s*\(/);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test("the window anime global advertises the v4 namespace API", () => {
|
||||||
|
const source = read("packages/core/src/runtime/window.d.ts");
|
||||||
|
const global = source.match(/\n\s*anime\?: \{([\s\S]*?)\n\s*\};/);
|
||||||
|
assert.ok(global, "found Window.anime declaration");
|
||||||
|
assert.match(global[1], /\banimate\??:\s*\(/);
|
||||||
|
assert.match(global[1], /\bcreateTimeline\??:\s*\(/);
|
||||||
|
assert.doesNotMatch(global[1], /^\s*\(params:/m);
|
||||||
|
assert.doesNotMatch(global[1], /\btimeline\??:/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("the adapter's local anime global shape matches v4 while retaining legacy discovery", () => {
|
||||||
|
const source = read("packages/core/src/runtime/adapters/animejs.ts");
|
||||||
|
const global = source.match(/interface AnimeGlobal \{([\s\S]*?)\n\}/);
|
||||||
|
assert.ok(global, "found AnimeGlobal declaration");
|
||||||
|
assert.match(global[1], /\banimate\??:\s*\(/);
|
||||||
|
assert.match(global[1], /\bcreateTimeline\??:\s*\(/);
|
||||||
|
assert.match(global[1], /\brunning\??:/);
|
||||||
|
assert.doesNotMatch(global[1], /^\s*\(params:/m);
|
||||||
|
assert.doesNotMatch(global[1], /\btimeline\??:/);
|
||||||
|
});
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
"files": 17
|
"files": 17
|
||||||
},
|
},
|
||||||
"hyperframes-animation": {
|
"hyperframes-animation": {
|
||||||
"hash": "5bc2ce098387a547",
|
"hash": "2ce5ca7dbf361e27",
|
||||||
"files": 121
|
"files": 121
|
||||||
},
|
},
|
||||||
"hyperframes-audio": {
|
"hyperframes-audio": {
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
"files": 78
|
"files": 78
|
||||||
},
|
},
|
||||||
"hyperframes-keyframes": {
|
"hyperframes-keyframes": {
|
||||||
"hash": "a568c05c01b27461",
|
"hash": "d00744ff0e669624",
|
||||||
"files": 3
|
"files": 3
|
||||||
},
|
},
|
||||||
"hyperframes-registry": {
|
"hyperframes-registry": {
|
||||||
|
|||||||
@@ -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.
|
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
|
## Contract
|
||||||
|
|
||||||
- Create animations or timelines synchronously during composition initialization.
|
- Create animations or timelines synchronously during composition initialization.
|
||||||
- Set `autoplay: false` so Anime.js does not advance on its own clock.
|
- 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.
|
- Use finite durations and loop counts.
|
||||||
- Avoid callbacks that mutate DOM based on wall-clock time, network state, or unseeded randomness.
|
- 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
|
## Basic Pattern
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://cdn.jsdelivr.net/npm/animejs@4.0.2/lib/anime.iife.min.js"></script>
|
|
||||||
<script>
|
<script>
|
||||||
const anim = anime({
|
const anim = anime.animate(".mark", {
|
||||||
targets: ".mark",
|
x: 280, // v4 shorthand for translateX
|
||||||
translateX: 280,
|
|
||||||
rotate: "1turn",
|
rotate: "1turn",
|
||||||
opacity: [0, 1],
|
opacity: [0, 1],
|
||||||
duration: 1200,
|
duration: 1200,
|
||||||
easing: "easeOutExpo",
|
ease: "outExpo", // NOT easing: "easeOutExpo"
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,55 +50,56 @@ The adapter seeks every registered instance with `instance.seek(timeMs)`, where
|
|||||||
|
|
||||||
## Timeline Pattern
|
## Timeline Pattern
|
||||||
|
|
||||||
|
`createTimeline` replaces `anime.timeline`, and `add()` takes **targets as its first argument** — `add(targets, parameters, position)`:
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script>
|
<script>
|
||||||
const tl = anime.timeline({
|
const tl = anime.createTimeline({
|
||||||
autoplay: false,
|
autoplay: false,
|
||||||
easing: "easeOutCubic",
|
defaults: { ease: "outCubic" }, // per-timeline defaults, not a bare `easing`
|
||||||
});
|
});
|
||||||
|
|
||||||
tl.add({
|
tl.add(".title", { y: [40, 0], opacity: [0, 1], duration: 650 });
|
||||||
targets: ".title",
|
tl.add(".accent", { scaleX: [0, 1], duration: 450 }, 250); // 250 = time position
|
||||||
translateY: [40, 0],
|
|
||||||
opacity: [0, 1],
|
|
||||||
duration: 650,
|
|
||||||
}).add(
|
|
||||||
{
|
|
||||||
targets: ".accent",
|
|
||||||
scaleX: [0, 1],
|
|
||||||
duration: 450,
|
|
||||||
},
|
|
||||||
250,
|
|
||||||
);
|
|
||||||
|
|
||||||
window.__hfAnime = window.__hfAnime || [];
|
window.__hfAnime = window.__hfAnime || [];
|
||||||
window.__hfAnime.push(tl);
|
window.__hfAnime.push(tl);
|
||||||
</script>
|
</script>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Position accepts a number, a label, `"+=250"` / `"-=100"`, `"<"` (previous **end**) and `"<<"` (previous **start**).
|
||||||
|
|
||||||
## Module Builds
|
## 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
|
```html
|
||||||
<script type="module">
|
<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", {
|
const anim = animate(".chip", { x: "18rem", duration: 900, autoplay: false });
|
||||||
x: "18rem",
|
|
||||||
duration: 900,
|
|
||||||
autoplay: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
window.__hfAnime = window.__hfAnime || [];
|
window.__hfAnime = window.__hfAnime || [];
|
||||||
window.__hfAnime.push(anim);
|
window.__hfAnime.push(anim);
|
||||||
</script>
|
</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
|
## Good Uses
|
||||||
|
|
||||||
- Small SVG and DOM flourishes where Anime.js syntax is compact.
|
- 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.
|
- 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.
|
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
|
## Avoid
|
||||||
|
|
||||||
- Leaving `autoplay` at the Anime.js default.
|
- Leaving `autoplay` at the Anime.js default.
|
||||||
- Depending on `anime.running` auto-discovery instead of explicit `window.__hfAnime.push(...)`.
|
- **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.
|
||||||
- Infinite loops. Compute a finite repeat count from the composition duration.
|
- `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.
|
- Building animations in timers, promises, event handlers, or after async asset loads.
|
||||||
|
|
||||||
## Validation
|
## Validation
|
||||||
@@ -105,10 +120,11 @@ After editing a composition that uses Anime.js:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx hyperframes lint
|
npx hyperframes lint
|
||||||
npx hyperframes check
|
npx hyperframes validate
|
||||||
```
|
```
|
||||||
|
|
||||||
## Credits And References
|
## Credits And References
|
||||||
|
|
||||||
- HyperFrames adapter source: `packages/core/src/runtime/adapters/animejs.ts`.
|
- 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
|
||||||
|
|||||||
@@ -42,8 +42,10 @@ CSS:
|
|||||||
Anime.js:
|
Anime.js:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
const animation = anime.timeline({ autoplay: false });
|
const animation = anime.createTimeline({ autoplay: false });
|
||||||
animation.add({ targets: "<selector>" /* derived channels */ });
|
animation.add("<selector>", {
|
||||||
|
/* derived channels */
|
||||||
|
});
|
||||||
window.__hfAnime = window.__hfAnime || [];
|
window.__hfAnime = window.__hfAnime || [];
|
||||||
window.__hfAnime.push(animation);
|
window.__hfAnime.push(animation);
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user