# Kompositions-API

Reference til film, tid, grundelementer til tidsstyring, animationsmatematik, scene og kamera, markør, tempo og videomateriale.

Canonical: https://zxn.studio/da/motion/composition-api

Alt her eksporteres fra `@zxn/motion-core`, medmindre andet er angivet.

## En film

En film er en React-komponent plus de oplysninger, en vært skal bruge for at vise den: størrelse, billedfrekvens og antal billeder. `defineFilm` validerer oplysningerne og returnerer filmen; et indgangsmodul eksporterer et array af dem.

```tsx
import { defineFilm, FullFrame, useTimeline } from "@zxn/motion-core";

type Props = { readonly title: string; readonly color: string };

function Intro({ title, color }: Props) {
  const { frame } = useTimeline();
  return <FullFrame style={{ background: color }}><h1>{title} — {frame}</h1></FullFrame>;
}

export const films = [defineFilm({
  id: "intro",
  title: "Intro",
  width: 1920,
  height: 1080,
  frameRate: 30,
  frames: 150,
  defaultProps: { title: "Hello", color: "#111827" },
  controls: {
    title: { type: "text", label: "Title" },
    color: { type: "color", label: "Background" },
  },
  component: Intro,
})];
```

Props skal være JSON: De gemmes i projektfiler, sendes på kommandolinjen og overføres mellem processer. `controls` beskriver dem for en vært, der vil vise et egenskabspanel (`text`, `number`, `color`, `boolean`, `choice`).

`describeFilm(film)` returnerer den `FilmDescriptor`, afspilleren og rendereren tager imod. `FilmSurface` monterer en film ved ét præcist billede; værter bruger den, film gør ikke.

## Tid

| Hook | Returnerer |
| --- | --- |
| `useTimeline()` | `{ frame, absoluteFrame, film }`. `frame` er lokalt for den omsluttende `<Cue>`; `absoluteFrame` er filmens. `film` indeholder `width`, `height`, `frameRate`, `frames`. |
| `useFilmConfig()` | Kun `film`-delen. |
| `useVirtualTime()` | `{ frame, seconds, milliseconds }` for det billede, der renderes. Brug det til at flytte afspilningspositionen i tidsbaserede biblioteker: `timeline.seek(seconds)`, `lottie.goToAndStop(...)`. |
| `useTempo()` | Filmens musikalske ur; se Tempo. |

En film må ikke aflæse et rigtigt ur. Lad billednummeret styre alt, så renderes filmen ens i afspilleren, når du trækker afspilningsmarkøren, og ved eksport.

## Grundelementer til tidsstyring

- `<FullFrame>` fylder filmen. Den er en flex-container, så `alignItems` og `justifyContent` centrerer indholdet.
- `<Cue start length>` viser sine børn i `length` billeder fra `start` og genstarter `frame` ved nul indeni, så en komponent animerer ens, uanset hvor den placeres. Når et tempo er angivet, tager den også `startBar`/`startBeat` og `lengthBars`/`lengthBeats`.
- `<Hold frame>` fryser sine børn ved ét billede.
- `<Repeat every>` gentager sine børns lokale tid.

## Animationsmatematik

```ts
mapRange(frame, [0, 30], [0, 1], { clamp: true, ease: easing.easeOut });
springValue({ frame, frameRate: film.frameRate, stiffness: 170, damping: 26, clamp: true });
seededRandom("stream-7");          // the same number every time, for this seed
```

`mapRange` tager tilsvarende lister med to eller flere støttepunkter, så ét kald kan beskrive indgang, pause og udgang. `springValue` bruger en lukket formel: Den beregner fjederens position ved et billede i stedet for at føre en simulering frem trinvis. Derfor svarer det at trække afspilningsmarkøren ind midt i en fjederbevægelse til en sekventiel rendering. `easing` indeholder `linear`, `easeIn`, `easeOut` og `easeInOut`.

## Scene, kamera og markør

En film, der viser en brugergrænseflade, har brug for et sted at placere den og noget at se på den med. `<Stage>` er en flade med fast størrelse og sit eget koordinatsystem; alt på den placeres af én `layout`-tabel, og det er den samme tabel, kameraet indrammer efter, og markøren bevæger sig mellem punkterne i.

```tsx
const layout = { panel: { x: 400, y: 300, width: 600, height: 400 }, "export-button": { x: 1716, y: 112, width: 92, height: 32 } };

<Stage width={1920} height={1080} layout={layout} shots={shots} contain vignette>
  <StageItem id="panel">…</StageItem>
  <StageItem id="sky" depth={0.6} />
  <Pointer path={path} />
</Stage>
```

**Intet måler DOM'en.** Layout er ikke tilgængeligt før filmens første optegning, så et kamera, der målte, ville indramme det forkerte på billede nul i hver eksport. Ved at deklarere et rektangel én gang bliver »kameraet ser på det, markøren lander på det, og det reagerer« én samlet sandhed i stedet for tre, der skal holdes i takt manuelt.

`<Camera shots>` tager en liste over indstillinger: `{ at, look, zoom, tilt?, settle?, hold?, padding? }`. `look` er et punkt på fladen eller id'et på noget, scenen har placeret; et navngivet element indrammes ud fra sine grænser plus `padding`, så ingen skal indtaste pixelkoordinater. `settle` er `"spring"` (standard), `"ease"` eller `"cut"`; zoom interpoleres multiplikativt, så en bevægelse fra 1×→4× bruger lige lang tid på 1–2 og 2–4. `contain` forhindrer en indstilling i at vise noget uden for fladens kant. `idle` tilføjer en drift med fast startværdi, så en fastholdt indstilling ikke er helt livløs.

**Én bevidst bevægelse pr. indstilling, og hold så stille.** Et kamera, der ankommer og allerede er på vej videre, virker nervøst. `reviewCameraShots()` kontrollerer dette. Det er en advarsel, ikke en fejl: En bevidst hurtig panorering mellem to slag kan sagtens være tilsigtet.

Parallakse er `depth`-proppen på `<StageItem>`: 0 bevæger sig med fladen, mens 1 er så langt væk, at kameraets bevægelse slet ikke flytter det. `useCamera()` returnerer den aktuelle visning til alt, der skal reagere på den.

`<Pointer path>` tegner en markør i fladens koordinatsystem, så kameraet tager den med. Nøglepunkter er `{ at, to, press?, drag? }`, hvor `to` er et punkt eller et element-id. Bevægelsen accelererer og bremser blødt og buer lidt væk fra den rette linje, fordi en markør, der glider med konstant fart, virker som et diagram. `reviewPointerPath()` advarer om bevægelser, der er for hurtige til at virke håndstyrede.

`usePointer(path).pressedOn("export-button")` er sand på præcis de billeder, hvor markøren trykker på den, så en komponent tegner sin egen trykkede tilstand ud fra samme sti, der bevæger markøren. Intet skal tilpasses manuelt.

## Tempo

En film kan deklarere `tempo: { bpm, beatsPerBar = 4, offsetSeconds = 0 }`. `useTempo()` giver derefter `bar(n)`, `beat(n)`, `barAt(frame)`, `beatAt(frame)` og `onBeat(frame, division?)`.

Alle konverteringer går gennem én funktion, så et sceneskift og et blink på samme nedslag lander på samme billede i stedet for med ét billedes mellemrum. Hvert slag beregnes ud fra sit eget indeks i stedet for ved at lægge et slags antal billeder til det foregående: Ved akkumulering afrundes én gang pr. slag, og fejlen vokser, så afvigelsen ved takt 200 i en film på 29,97 fps er hele billeder. `onBeat` er sand på præcis ét billede pr. underdeling, så et blink varer ét billede i stedet for at flimre over to.

## Videomateriale

`<Footage asset="ASSET_ID" from? speed? duration? fit? muted />` viser en video inde i filmen, så den kan ligge i et skråtstillet kort, maskeres eller få tekst lagt ovenpå.

Det er et canvas, ikke et `<video>`: Et videoelement søger efter sin egen tidsplan, og »tæt nok på« er ikke et billede. `<Footage>` deklarerer, hvilken mediefil og hvilket tidspunkt den ønsker, og værten udfylder hvert canvas via en **billedleverandør**, der er installeret som `globalThis.zxnFootage`, og venter på dem alle før optagelse. En manglende udbyder eller et billede, udbyderen ikke kan levere, giver en synlig fejl, der navngiver mediefilen.

Kommandolinjerendereren installerer en filbaseret udbyder: `zxn-motion render … --footage <id>=<file>`. Andre værter leverer deres egen. `from` og `speed` omsætter filmens billede til kildens, og sidste billede fastholdes i stedet for at fortsætte forbi slutningen. Lyd ligger uden for rammen: En film er et billede.

## Hold filmen hurtig

Rendereren rasteriserer DOM'en én gang pr. billede, så en film koster det, dens DOM koster at lægge ud og tegne.

- Foretræk få store elementer frem for tusindvis af små. Et partikelfelt eller en tegnregn med ét element pr. partikel er langsomt ved enhver opløsning; tegn det i stedet på ét `<canvas>` ud fra billednummeret.
- Sløring er dyrt at tegne: `filter: blur()`, `backdrop-filter` og store radier for `text-shadow`/`box-shadow`, ganget med hvert element, der har dem.
- Arbejd i 1920×1080, medmindre du behøver mere. En vært kan skalere en film; fire gange så mange pixels giver fire gange så meget tegnearbejde.
- Beregn ud fra billednummeret; akkumuler ikke tilstand på tværs af billeder. Billeder kan blive efterspurgt i vilkårlig rækkefølge.

