85453da49f
regression / regression-shards (style-16-prod style-9-prod style-17-prod iframe-render-compat variables-prod mp4-h265-sdr, shard-4) (push) Has been cancelled
regression / regression-shards (style-4-prod style-11-prod style-2-prod animejs-adapter typegpu-adapter parallel-capture-regression, shard-5) (push) Has been cancelled
regression / regression-shards (style-7-prod style-8-prod style-10-prod css-spinner-render-compat webm-transparency mp4-h264-sdr webm-vp9, shard-3) (push) Has been cancelled
regression / regression-shards (sub-composition-video style-18-prod raf-ball-render-compat font-variant-numeric sub-comp-t0 sub-comp-id-selector, shard-7) (push) Has been cancelled
Windows render verification / Detect changes (push) Has been cancelled
Windows render verification / Preflight (lint + format) (push) Has been cancelled
Windows render verification / Render on windows-latest (push) Has been cancelled
Windows render verification / Tests on windows-latest (push) Has been cancelled
CI / Detect changes (push) Has been cancelled
CI / Build (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Fallow audit (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Test (push) Has been cancelled
CI / Producer: integration tests (push) Has been cancelled
CI / Producer: unit tests (push) Has been cancelled
CI / File size check (push) Has been cancelled
CI / Test: skills (push) Has been cancelled
CI / Skills: manifest in sync (push) Has been cancelled
CI / CLI: npx shim (macos-latest) (push) Has been cancelled
CI / CLI: npx shim (ubuntu-latest) (push) Has been cancelled
CI / CLI: npx shim (windows-latest) (push) Has been cancelled
CI / SDK: unit + contract + smoke (push) Has been cancelled
CI / Test: runtime contract (push) Has been cancelled
CI / Studio: load smoke (push) Has been cancelled
CI / Smoke: global install (push) Has been cancelled
CI / CLI smoke (required) (push) Has been cancelled
CI / Semantic PR title (push) Has been cancelled
Player perf / Detect changes (push) Has been cancelled
Player perf / Preflight (lint + format) (push) Has been cancelled
Player perf / player-perf (push) Has been cancelled
Player perf / Perf: drift (push) Has been cancelled
Player perf / Perf: fps (push) Has been cancelled
Player perf / Perf: parity (push) Has been cancelled
Player perf / Perf: scrub (push) Has been cancelled
Player perf / Perf: load (push) Has been cancelled
preview-regression / Detect changes (push) Has been cancelled
preview-regression / Preflight (lint + format) (push) Has been cancelled
preview-regression / Preview parity (push) Has been cancelled
preview-regression / preview-regression (push) Has been cancelled
regression / regression (push) Has been cancelled
regression / Detect changes (push) Has been cancelled
regression / Preflight (lint + format) (push) Has been cancelled
regression / regression-shards (hdr-regression style-5-prod style-3-prod mov-prores, shard-1) (push) Has been cancelled
regression / regression-shards (overlay-montage-prod style-12-prod chat missing-host-comp-id png-sequence portrait-edge-bleed, shard-6) (push) Has been cancelled
regression / regression-shards (style-13-prod style-6-prod vignelli-stacking gsap-letters-render-compat audio-mux-parity, shard-8) (push) Has been cancelled
regression / regression-shards (style-15-prod hdr-hlg-regression style-1-prod many-cuts vfr-screen-recording render-symlinked-assets, shard-2) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Docs / Validate docs (push) Has been cancelled
Sync skills to ClawHub / Publish changed skills (push) Has been cancelled
290 lines
10 KiB
Plaintext
290 lines
10 KiB
Plaintext
---
|
||
title: "Timing & Animation"
|
||
description: "Set clip timing, elastic holds, GSAP tweens, and keyframed animations on composition elements."
|
||
---
|
||
|
||
Every clip in a HyperFrames composition has a position on the timeline (`data-start`, `data-duration`) and an optional animation attached to it. The SDK exposes typed helpers for both: the timing API controls *when* an element appears and how long it stays on screen; the animation API controls *how* it moves through that window.
|
||
|
||
## Clip Timing
|
||
|
||
### Reading timings
|
||
|
||
`getElementTimings()` returns a `Record<HfId, ElementTimingSnapshot>` — one entry per element that carries timing data. Values are derived from the live DOM on every call (never cached in the snapshot). Keys are `scopedId`: for top-level elements this equals the bare `id`, but for elements inside a sub-composition it is the scoped form `"hf-HOST/hf-LEAF"`.
|
||
|
||
```typescript
|
||
import { openComposition } from "@hyperframes/sdk";
|
||
|
||
const comp = await openComposition(html);
|
||
const timings = comp.getElementTimings();
|
||
|
||
for (const [id, t] of Object.entries(timings)) {
|
||
console.log(id, t.enterAt, "→", t.exitAt, "labels:", t.labels);
|
||
}
|
||
```
|
||
|
||
Each `ElementTimingSnapshot` contains:
|
||
|
||
<ResponseField name="enterAt" type="number">
|
||
Absolute timeline position (seconds) at which the element enters.
|
||
</ResponseField>
|
||
<ResponseField name="exitAt" type="number">
|
||
Absolute timeline position (seconds) at which the element exits.
|
||
</ResponseField>
|
||
<ResponseField name="labels" type="string[]">
|
||
GSAP timeline label names whose numeric position falls within `[enterAt, exitAt]`. Parsed fresh from the GSAP script on every `getElementTimings()` call — never stale.
|
||
</ResponseField>
|
||
|
||
<Note>
|
||
`getElementTimings()` only includes elements that have `data-start` and either `data-duration` or `data-end` attributes. Untimed elements are omitted. The method prefers `data-duration` over `data-end − data-start` when both exist, matching the behavior of `setTiming`.
|
||
</Note>
|
||
|
||
### Setting timing on one element
|
||
|
||
```typescript
|
||
// Move hf-title to start at 1.0 s, last 2.5 s, on track 0
|
||
comp.setTiming("hf-title", { start: 1.0, duration: 2.5, trackIndex: 0 });
|
||
```
|
||
|
||
All three fields are optional — pass only what you want to change:
|
||
|
||
<ParamField path="start" type="number">
|
||
Clip start time in seconds.
|
||
</ParamField>
|
||
<ParamField path="duration" type="number">
|
||
Clip duration in seconds.
|
||
</ParamField>
|
||
<ParamField path="trackIndex" type="number">
|
||
Zero-based track index.
|
||
</ParamField>
|
||
|
||
### Updating multiple elements in one batch
|
||
|
||
`setElementTiming(map)` dispatches one `setTiming` op per entry inside a single batch, so the history records one undo step and patch listeners see one event:
|
||
|
||
```typescript
|
||
comp.setElementTiming({
|
||
"hf-title": { start: 0.5, duration: 2.0 },
|
||
"hf-logo": { start: 0.0, duration: 5.0, trackIndex: 1 },
|
||
"hf-cta": { start: 3.0, duration: 2.5 },
|
||
});
|
||
```
|
||
|
||
Unknown ids are silently skipped.
|
||
|
||
### Elastic holds
|
||
|
||
An elastic hold freezes or loops a portion of an element's timeline window. Use it to hold a static frame between two animated segments without changing clip duration.
|
||
|
||
```typescript
|
||
comp.setHold("hf-card", {
|
||
start: 1.5, // hold begins at this time within the composition
|
||
end: 4.0, // hold ends at this time
|
||
fill: "freeze", // "freeze" | "loop"
|
||
});
|
||
```
|
||
|
||
`ElasticHold` fields:
|
||
|
||
<ParamField path="start" type="number">
|
||
Absolute composition time at which the hold begins.
|
||
</ParamField>
|
||
<ParamField path="end" type="number">
|
||
Absolute composition time at which the hold ends.
|
||
</ParamField>
|
||
<ParamField path="fill" type='"freeze" | "loop"'>
|
||
`"freeze"` holds the last frame until `end`; `"loop"` repeats the segment from `start` back to `start`.
|
||
</ParamField>
|
||
|
||
---
|
||
|
||
## GSAP Tweens
|
||
|
||
GSAP tweens are the primary animation primitive. The SDK reads tween IDs from `element.animationIds` (returned by the query API) and writes through `addGsapTween`, `setGsapTween`, and `removeGsapTween`.
|
||
|
||
### Adding a tween
|
||
|
||
```typescript
|
||
const animId = comp.addGsapTween("hf-title", {
|
||
method: "from",
|
||
position: 0.5,
|
||
duration: 0.6,
|
||
ease: "power3.out",
|
||
fromProperties: { opacity: 0, y: 40 },
|
||
});
|
||
// animId is the newly-assigned animation ID — store it for subsequent edits
|
||
```
|
||
|
||
`addGsapTween` returns the newly-assigned animation ID as a `string`.
|
||
|
||
### GsapTweenSpec fields
|
||
|
||
<ParamField path="method" type='"from" | "to" | "fromTo" | "set"'>
|
||
The GSAP timeline method to call.
|
||
</ParamField>
|
||
<ParamField path="position" type="number | string">
|
||
Timeline position: a number (seconds) or a label-relative string (e.g. `"intro+=0.3"`). Number-only is required for `addWithKeyframes` / `replaceWithKeyframes` — see [Keyframes](#keyframes) below.
|
||
</ParamField>
|
||
<ParamField path="duration" type="number">
|
||
Tween duration in seconds.
|
||
</ParamField>
|
||
<ParamField path="ease" type="string">
|
||
GSAP ease string, e.g. `"power2.inOut"`.
|
||
</ParamField>
|
||
<ParamField path="fromProperties" type="Record<string, unknown>">
|
||
Starting properties — used with `"from"` and `"fromTo"`.
|
||
</ParamField>
|
||
<ParamField path="toProperties" type="Record<string, unknown>">
|
||
Ending properties — used with `"fromTo"`.
|
||
</ParamField>
|
||
<ParamField path="properties" type="Record<string, unknown>">
|
||
Animation target properties — used with `"to"` tweens.
|
||
</ParamField>
|
||
<ParamField path="repeat" type="number">
|
||
Number of repeats (`-1` = infinite). Maps to GSAP's `repeat` option.
|
||
</ParamField>
|
||
<ParamField path="yoyo" type="boolean">
|
||
When `true`, alternates direction on each repeat.
|
||
</ParamField>
|
||
<ParamField path="stagger" type="number | Record<string, unknown>">
|
||
GSAP stagger amount (seconds) or a full stagger config object.
|
||
</ParamField>
|
||
|
||
### Modifying a tween
|
||
|
||
```typescript
|
||
// Change ease and duration — all other fields stay as-is
|
||
comp.setGsapTween(animId, { ease: "elastic.out(1, 0.5)", duration: 0.9 });
|
||
```
|
||
|
||
`setGsapTween` takes the animation ID and a `Partial<GsapTweenSpec>` — only the keys you pass are updated.
|
||
|
||
### Removing a tween
|
||
|
||
```typescript
|
||
comp.removeGsapTween(animId);
|
||
```
|
||
|
||
### Looking up animation IDs
|
||
|
||
Query the element snapshot to find animation IDs attached to a given element:
|
||
|
||
```typescript
|
||
const el = comp.getElement("hf-title");
|
||
const [firstAnimId] = el?.animationIds ?? [];
|
||
if (firstAnimId) {
|
||
comp.setGsapTween(firstAnimId, { ease: "none" });
|
||
}
|
||
```
|
||
|
||
### Feature-detecting advanced ops
|
||
|
||
Some GSAP operations (such as `setGsapKeyframe`, arc paths, and label ops) require the parser engine, which ships in a later phase. Use `can()` to gate them before dispatching:
|
||
|
||
```typescript
|
||
const check = comp.can({
|
||
type: "setGsapTween",
|
||
animationId: firstAnimId,
|
||
properties: { ease: "power3.inOut" },
|
||
});
|
||
|
||
if (!check.ok) {
|
||
if (check.code === "E_NO_GSAP_TIMELINE") {
|
||
// Parser engine not yet available — show a disabled state or skip
|
||
console.warn("GSAP timeline not available:", check.message);
|
||
}
|
||
return;
|
||
}
|
||
|
||
comp.dispatch({ type: "setGsapTween", animationId: firstAnimId, properties: { ease: "power3.inOut" } });
|
||
```
|
||
|
||
Stable error codes returned by `can()`:
|
||
|
||
| Code | Meaning |
|
||
|------|---------|
|
||
| `E_TARGET_NOT_FOUND` | The target `HfId` does not exist in the document. |
|
||
| `E_NO_ROOT` | The document has no root element. |
|
||
| `E_NO_GSAP_TIMELINE` | Op requires the GSAP parser engine, which is not yet available. |
|
||
| `E_NO_GSAP_SCRIPT` | The composition has no embedded GSAP script. |
|
||
|
||
---
|
||
|
||
## Keyframes
|
||
|
||
For keyframe-based animations, use `addWithKeyframes` and `replaceWithKeyframes`. Both operate on the GSAP CSS-keyframes layer and return the minted animation ID.
|
||
|
||
### Adding a keyframed tween
|
||
|
||
```typescript
|
||
const animId = comp.addWithKeyframes(
|
||
"#hf-badge", // CSS selector targeting the element
|
||
1.0, // timeline position in seconds (number only)
|
||
0.8, // duration in seconds
|
||
[
|
||
{ percentage: 0, properties: { opacity: 0, scale: 0.8 } },
|
||
{ percentage: 60, properties: { opacity: 1, scale: 1.05 }, ease: "power2.out" },
|
||
{ percentage: 100, properties: { scale: 1 } },
|
||
],
|
||
"power2.inOut", // optional overall ease
|
||
);
|
||
```
|
||
|
||
Returns the new animation ID string, or `""` if the op was rejected.
|
||
|
||
### Replacing an existing keyframed tween
|
||
|
||
```typescript
|
||
const newAnimId = comp.replaceWithKeyframes(
|
||
oldAnimId,
|
||
"#hf-badge",
|
||
1.0,
|
||
1.2,
|
||
[
|
||
{ percentage: 0, properties: { x: -60, opacity: 0 } },
|
||
{ percentage: 100, properties: { x: 0, opacity: 1 }, ease: "back.out(1.7)" },
|
||
],
|
||
);
|
||
// newAnimId !== oldAnimId — position-derived IDs renumber after the remove
|
||
```
|
||
|
||
<Warning>
|
||
`replaceWithKeyframes` is equivalent to `removeGsapTween` + `addWithKeyframes` in one atomic op. Because position-derived tween IDs renumber after the removal step, the returned ID is always a **new** ID and must not be assumed equal to the input `animationId`. Re-query `element.animationIds` after a replace to get the current set.
|
||
</Warning>
|
||
|
||
### KeyframeSpec fields
|
||
|
||
<ParamField path="percentage" type="number">
|
||
Position within the tween as a percentage (0–100).
|
||
</ParamField>
|
||
<ParamField path="properties" type="Record<string, number | string>">
|
||
CSS / GSAP properties at this keyframe.
|
||
</ParamField>
|
||
<ParamField path="ease" type="string">
|
||
Per-keyframe ease applied *from* this keyframe to the next.
|
||
</ParamField>
|
||
<ParamField path="auto" type="boolean">
|
||
GSAP endpoint flag — when `true`, this keyframe picks up the element's current value automatically.
|
||
</ParamField>
|
||
|
||
### Lower-level keyframe and label ops
|
||
|
||
For lower-level operations — individual keyframe mutations (`addGsapKeyframe`, `setGsapKeyframe`, `removeGsapKeyframe`), property removal, arc paths, label insertion, and animation splitting — use `dispatch()` directly with the corresponding `EditOp` types. See the [Edit Operations reference](/sdk/reference/edit-operations) for the full op union.
|
||
|
||
---
|
||
|
||
<CardGroup cols={2}>
|
||
<Card title="Edit Operations" icon="code" href="/sdk/reference/edit-operations">
|
||
Full reference for every op type in the `EditOp` union, including lower-level keyframe and arc ops.
|
||
</Card>
|
||
<Card title="Types" icon="brackets-curly" href="/sdk/reference/types">
|
||
`GsapTweenSpec`, `KeyframeSpec`, `ElasticHold`, `ElementTimingSnapshot`, and all related types.
|
||
</Card>
|
||
<Card title="GSAP Animation Guide" icon="wand-magic-sparkles" href="/guides/gsap-animation">
|
||
Authoring GSAP timelines in composition HTML.
|
||
</Card>
|
||
<Card title="Keyframes Guide" icon="film" href="/guides/keyframes">
|
||
Keyframe authoring patterns and best practices.
|
||
</Card>
|
||
</CardGroup>
|
||
|