# Kom godt i gang

Byg biblioteket fra kildekoden, skriv din første film, se den i browserafspilleren, og render den til MP4 eller WebM fra kode eller kommandolinjen.

Canonical: https://zxn.studio/da/motion/getting-started

En film er en React-komponent, der tegner ét billede: Den får et heltalsnummer på et billede og nogle JSON-props og returnerer billedet for præcis det billede. Fordi en film aldrig aflæser et ur eller gemmer tilstand mellem billeder, ser billede 47 ens ud, uanset om du har trukket afspilningsmarkøren hen til det, afspillet frem til det eller eksporteret det.

Det er hele idéen. Alt andet her — afspilleren, rendereren og CLI'et — er mekanik til at bede en film om billeder og gøre noget med svarene.

@video(lower-third)

## Installation

Biblioteket er endnu ikke på npm, så du bygger det fra kildekoden. Du skal bruge Node 22 eller nyere.

```bash
git clone https://github.com/zinxan/motion-graphics.git
cd motion-graphics
npm install
npm run check      # typecheck, tests, build
```

`npm install` forbinder pakkerne i arbejdsområdet, så `@zxn/motion-core`, `@zxn/motion-player`, `@zxn/motion-renderer` og `@zxn/motion-cli` kan findes ved navn overalt i kodearkivet. Arbejd i `examples/`, eller peg din egen apps bundler på de byggede pakker.

## Din første film

En film er `defineFilm({...})` omkring en komponent. Definitionen indeholder de oplysninger, en vært skal bruge, før den renderer noget: størrelsen, billedfrekvensen, antallet af billeder og de props, komponenten forventer. Et indgangsmodul eksporterer dem som et array med navnet `films`.

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

type Props = {
  readonly headline: string;
  readonly subtitle: string;
  readonly accent: string;
};

function Opener({ headline, subtitle, accent }: Props) {
  const { frame, film } = useTimeline();
  const last = film.frames - 1;
  const rise = springValue({ frame, frameRate: film.frameRate, stiffness: 150, damping: 18, clamp: true });
  const sub = mapRange(frame, [10, 26], [0, 1], { clamp: true, ease: easing.easeOut });
  const out = mapRange(frame, [last - 18, last], [1, 0], { clamp: true, ease: easing.easeIn });

  return (
    <FullFrame style={{ background: "#0b0c0e", flexDirection: "column", alignItems: "center", justifyContent: "center", gap: 24, opacity: out }}>
      <h1 style={{ margin: 0, fontFamily: "Inter, system-ui, sans-serif", fontSize: 150, fontWeight: 900, letterSpacing: "-0.04em", color: "#f3efe7", transform: `translateY(${(1 - rise) * 90}px)` }}>
        {headline}
      </h1>
      <p style={{ margin: 0, fontFamily: "Inter, system-ui, sans-serif", fontSize: 52, fontWeight: 500, color: accent, opacity: sub, transform: `translateY(${(1 - sub) * 20}px)` }}>
        {subtitle}
      </p>
    </FullFrame>
  );
}

export const films = [defineFilm({
  id: "opener",
  title: "Opener",
  width: 1920,
  height: 1080,
  frameRate: 30,
  frames: 120,
  component: Opener,
  defaultProps: { headline: "Frame by frame", subtitle: "A film is a function of time", accent: "#7a6cff" },
  controls: {
    headline: { type: "text", label: "Headline" },
    subtitle: { type: "text", label: "Subtitle" },
    accent: { type: "color", label: "Accent" },
  },
})];
```

@video(opener)

Fire ting i filen er værd at fremhæve.

`useTimeline()` returnerer `{ frame, absoluteFrame, film }`. `frame` er det billede, der tegnes, og `film` indeholder `width`, `height`, `frameRate` og `frames`. Intet andet i komponenten ved, hvad klokken er.

`<FullFrame>` fylder filmen og er en flex-container, og derfor centrerer `alignItems` og `justifyContent` teksten.

`springValue` og `mapRange` omsætter billednummeret til de tal, stylingen har brug for. `springValue` bruger en lukket formel: Den beregner fjederens position ved et billede i stedet for at føre en simulering frem trin for trin. Derfor giver det samme billede at trække afspilningsmarkøren ind midt i bevægelsen som at rendere sekventielt. `mapRange` tager tilsvarende lister med to eller flere støttepunkter, så ét kald kan beskrive en indgang, en pause og en udgang.

`controls` beskriver hver prop for en vært, der vil vise et egenskabspanel — `text`, `number`, `color`, `boolean` eller `choice`. Giv hver standardprop en kontrol, så kan en redaktør ændre filmen uden at åbne koden. Props skal være JSON, fordi de gemmes i projektfiler, sendes på kommandolinjen og overføres mellem processer.

Ved 30 fps er `frames: 120` en film på fire sekunder.

## Se en forhåndsvisning

`@zxn/motion-player` giver dig `<FilmPlayer>`: et visningsområde, der skalerer filmen, så den passer, en afspil/pause-knap, genstart og en tidslinjeskyder. Den tager en `FilmDescriptor`, som er det, `describeFilm()` returnerer.

```tsx
import { describeFilm } from "@zxn/motion-core";
import { FilmPlayer } from "@zxn/motion-player";
import "@zxn/motion-player/player.css";
import { films } from "./films";

export const Preview = () => <FilmPlayer film={describeFilm(films[0]!)} loop />;
```

`controls={false}` skjuler afspilningskontrollerne, `inputProps` tilsidesætter filmens standardværdier, og `onFrameChange` fortæller, hvor afspilningsmarkøren er. Afspilleren leveres med sit eget lille stilark; der er ingen Tailwind at konfigurere.

Træk i tidslinjeskyderen. En film, der ser rigtig ud under afspilning og forkert ud, når du trækker i skyderen, aflæser noget andet end billednummeret. Det er netop den fejl, hele designet skal gøre synlig tidligt.

## Render den i browseren

`@zxn/motion-renderer` monterer det samme træ som afspilleren, færdiggør hvert præcist billede, rasteriserer DOM'en og koder det via WebCodecs. Spørg først, om enheden kan kode det, du ønsker:

```ts
import { canRenderFilm, renderFilm } from "@zxn/motion-renderer";
import { describeFilm } from "@zxn/motion-core";
import { films } from "./films";

const film = describeFilm(films[0]!);
if (await canRenderFilm(film, "mp4-h264")) {
  const bytes = await renderFilm({
    film,
    format: "mp4-h264",                      // also "mov-h264", "webm-vp9"
    onProgress: ({ ratio }) => console.log(ratio),
  });
}
```

`renderFilm` returnerer asynkront et `Uint8Array` med den færdige fil. Den tager også `inputProps` og et `AbortSignal` som `signal`. Hvis enheden ikke kan kode formatet, hvis et billede kaster en fejl, eller hvis encoderen intet returnerer, afvises kaldet med en fejl, der angiver problemet. Den skifter ikke til et andet codec og giver dig ikke et sort billede — et erstatningsformat, ingen har bedt om, er værre end en fejl.

## Render den fra kommandolinjen

`@zxn/motion-cli` bundler filmens indgangsmodul og renderer den i et skjult Electron-vindue i en sandkasse, hvilket passer til CI og scripts.

```bash
zxn-motion render ./films.tsx opener ./out/opener.mp4
zxn-motion render ./films.tsx opener ./out/opener.webm --props ./props.json
zxn-motion render ./films.tsx opener ./out/opener.mp4 --force
```

De tre positionsargumenter er indgangsmodulet, filmens `id` og outputstien. Filendelsen vælger forudindstillingen: `.mp4` og `.mov` er H.264, og `.webm` er VP9. Alt andet giver en fejl i stedet for et gæt.

`--props <file.json>` fletter et JSON-objekt ind i filmens `defaultProps`. `--force` kræves for at erstatte en eksisterende fil, og den gamle fil bevares, indtil erstatningen er renderet. `--footage <id>=<file>` leverer en video til hver `<Footage asset="id" />` i filmen; flaget gentages én gang pr. mediefil. `--help` viser resten.

Tilgængelige codecs afhænger af værtens operativsystem og Electron-build. Som i browseren fejler CLI'et med en forklaring i stedet for at erstatte et codec.

## Sådan hænger pakkerne sammen

| Pakke | Det får du |
| --- | --- |
| `@zxn/motion-core` | `defineFilm`, hooks til billedstyring, grundelementer til tidsstyring, animationsmatematik, `Canvas2D` og værktøjerne til film om brugergrænseflader: `Stage`, `Camera`, `Pointer`, `Footage`. |
| `@zxn/motion-player` | `<FilmPlayer>`: afspilning, pause, søgning og gentagelse i browseren. |
| `@zxn/motion-renderer` | `renderFilm()` og `canRenderFilm()`. |
| `@zxn/motion-cli` | `zxn-motion render`. |
| `@zxn/motion-graphics` | Deklarative diagrammer og fremhævelser, der er deterministiske for hvert billede. |
| `@zxn/motion-text` | Serialiserbare 2D-tekststile med én renderer til canvas og eksport. |

Afhængighederne peger indad: CLI → renderer og afspiller → core. Core har ingen afhængighed af afspiller, renderer, Electron eller filsystem, så en vært, der kun skal definere og montere film, behøver kun core.

Afspilleren og rendereren monterer begge `FilmSurface`, den samme komponent, ved ét præcist billede. Derfor stemmer forhåndsvisningen og eksporten overens: Der er ingen anden implementering, der kan komme ud af trit.

## Næste skridt

- [Tænk i billeder](/da/motion/thinking-in-frames) — den mentale model, grundelementerne til tidsstyring og vejen til let aflæselige bevægelser.
- [Kompositions-API](/da/motion/composition-api) — referencen: film, tid, scene og kamera, markør, tempo og videomateriale.
- [Kontroller og egenskaber](/da/motion/controls-and-props) — design filmens input, så et menneske eller en maskine kan styre den.
- [Tegn på canvas](/da/motion/canvas) — når DOM er det forkerte værktøj, og `<Canvas2D>` er det rigtige.
- [Brug af pakker](/da/motion/using-packages) — d3, GSAP, Lottie, simplex-noise og culori inde i en film.
- [Tempo og musik](/da/motion/tempo-and-music) — klipning efter takter og slag.
- [Film om brugergrænseflader](/da/motion/interface-films) — scene, kamera og markør til at vise software.
- [Ydeevne](/da/motion/performance) — hvad et billede koster, og hvordan du bruger færre ressourcer.
- [Opskrifter](/da/motion/recipes) — otte verificerede eksempelfilm, du kan læse og låne fra.
- [AI-agenter](/da/motion/ai-agents) — generering af film og kontrollen, der bedømmer dem.

