Motion as Material
Fire komponenter · 2D canvas · nul afhængigheder

Bevægelse er et materiale, ikke en effekt.

Fire færdige generative komponenter, hver fra en anden gren af matematikken, hver bygget til at kunne sendes i produktion. Ingen af dem er en demo: de håndterer resize, pixelforhold, synlighed, reduceret bevægelse og pointer, og de fortæller dig selv hvad de koster.

A
Strømfeltet
støjfelt · advektion
B
Grafen
kraftsimulering · routing
C
Diffusionen
reaktion · per pixel
D
Tilstandsfeltet
data · anvendt
Plade A

Strømfeltet

støjfelt · advektion

Tusinder af partikler der driver gennem et usynligt vektorfelt. Feltet er 3D-værdistøj: to rumlige akser og tiden som den tredje, så mønsteret ikke bare bevæger sig, det forvandler sig. Ingen partikel ved noget om nogen anden. Al struktur du ser, er feltets.

FlowField, – partikler ms/frame –fps –kører

Parametre

Støjen er tredimensionel

Vinklen slås op i noise(x·s, y·s, t·ts). Den tredje akse er tid. Bruger man i stedet 2D-støj og flytter feltet, glider mønsteret hen over skærmen som en tapet, her ændrer det karakter på stedet.

Her er overtegning det rigtige

Sporene laves ved at male et halvtransparent rektangel hen over billedet hver frame. Modsat plade B, hvor hvert segment får sin egen alpha. Forskellen: her er der 4000 spor og ingen der skal se ens ud, så den billige metode er også den pæneste.

Partikler dør med vilje

Hver partikel har en levetid og genfødes tilfældigt. Uden det samler alting sig i feltets attraktorer efter få sekunder, og billedet dør. Levetiden er den vigtigste parameter, du ikke ville have gættet.

Farven kommer fra farten

Ikke fra positionen. Hurtige partikler er lysere. Det gør feltets struktur læsbar uden at man behøver tegne feltet selv, hastighed er allerede information.

Plade B

Grafen

kraftsimulering · routing

Et netværk der finder sin egen form gennem frastødning, fjedre og dæmpning, og agenter der ikke vandrer tilfældigt, men beregner den korteste vej fra A til B og løber den. Det er forskellen mellem en pauseskærm og noget der ser ud som om det vil noget. Træk i en node.

ForceGraph, – noder · – kanter energi –ruter – ms/frame –kører

Parametre

Topologien er ikke tilfældig

Netværket bygges med præferentiel tilknytning: en ny node forbinder sig helst til dem, der allerede har mange forbindelser. Det giver knudepunkter. Et uniformt tilfældigt netværk ser dødt ud, fordi virkelige netværk aldrig er uniforme.

Tre kræfter, ikke én

Frastødning mellem alle par (O(n²), fint under ~300 noder), fjedre langs kanterne mod en hvilelængde, og en svag centrering så grafen ikke driver væk. Dæmpningen afgør om den svinger eller sætter sig.

Agenterne beregner en rute

Dijkstra med kantlængde som vægt, kørt én gang når agenten fødes. Derefter følger den bare sin liste af noder. Ruteberegningen er gratis; det er tegningen der koster.

Energien fortæller dig hvornår

Målet i baren er den samlede kinetiske energi. Falder den under en tærskel, er layoutet sat, og man kan holde op med at simulere. I produktion er dét dit signal til at fryse, ikke en timer.

Den vigtigste beslutning her er ikke fysikken. Det er at agenterne har et mål. Et tilfældigt gennemløb og en beregnet rute koster næsten det samme at implementere, men den ene ser ud som støj og den anden som hensigt.
Plade C

Diffusionen

reaktion · per pixel

Gray–Scott: to stoffer der diffunderer med hver sin hastighed, reagerer med hinanden, og som ud af en jævn suppe frembringer pletter, striber og celledeling. To ligninger. Ingen regler om form. Tegn på den med musen, den vokser videre af sig selv.

GrayScott, – celler · – trin/frame ms/frame –fps –kører

Parametre

To tal styrer alt

feed og kill. Hele RAMTT af mønstre, pletter, orme, labyrinter, evig celledeling, ligger i et lille område af det todimensionelle rum de udspænder. Flyt dem en tusindedel og du er i et andet regime.

Den regner i småt og vises i stort

Simuleringen kører på et gitter på nogle få tusinde celler, skrives med putImageData til et lille canvas, og skaleres op med drawImage. Browserens skalering er gratis; at regne i fuld opløsning er det ikke.

To buffere, aldrig én

Hvert trin læser fra den ene og skriver til den anden, hvorefter de byttes. Skriver man tilbage i den man læser fra, forplanter fejlen sig gennem gitteret inden for én frame og mønsteret bliver retningsbestemt.

Det er her 2D canvas knækker

Alt andet på siden er lineært i antal elementer. Denne er kvadratisk i opløsning og ganges med antal trin. Se den målte kurve nedenfor, det er den eneste af de fire, der har et reelt loft.

Plade D

Tilstandsfeltet

data · anvendt

De tre første er smukke. Denne er brugbar. Samme komponent, to forskellige datastrømme: bevægelsen er ikke dekoration, den er aflæsningen. Amplitude følger niveauet, uroen følger volatiliteten, farven følger zonen. Send et spike ind og se hvordan den reagerer, og hvor længe den er om det.

StateField, to strømme, samme komponent venstre –højre – ms/frame –kører

Parametre

Glat signalet, ikke billedet

Rådata hopper. En EWMA med en tidskonstant du kan skrue på ligger mellem data og visual. Uden den flimrer komponenten ved hver måling, og brugeren lærer at ignorere den.

Hold en rolig grundtilstand

Hvis komponenten altid er i bevægelse, betyder bevægelse ingenting. Reservér intensiteten. Det meste af tiden skal den være næsten stille, så er den værd at kigge på når den ikke er.

Farve er en zone, ikke en gradient

Tre diskrete tilstande med bløde overgange slår en kontinuerlig farveskala. Folk kan aflæse "gul" fra øjenkrogen; de kan ikke aflæse 63 % hen ad en rampe.

Reduceret bevægelse må ikke fjerne information

Slår man bevægelsen fra, skal aflæsningen stadig være der, her som en statisk profil. En komponent hvor prefers-reduced-motion gør skærmen tom, bar information i bevægelsen alene. Det er en fejl, ikke en indstilling.

Specimenet på om ambient visuals hører hjemme i dit produkt: dæk tallet til. Kan man stadig se, om noget er galt? Kan man ikke, er visualen dekoration, og så skal den holde op med at bevæge sig.
Måling

Stigen

målt, ikke påstået

Hvornår skal man forlade 2D canvas? Ikke når nogen siger det, men når man har målt det. Knappen kører Gray–Scott ved stigende gitterstørrelser på din maskine, lige nu, og tegner ms per frame. Budgettet er 16,7 ms for 60 fps.

ikke kørt
GitterCellerms/frameLoft
Kør målingen.
2D canvashovedtråden

Op til nogle få tusind tegneoperationer per frame. Plade A, B og D bor her og kommer aldrig i nærheden af loftet. Fordelen er ikke ydeevne, det er at du kan læse din egen kode om et år.

OffscreenCanvasi en worker

Samme API, men tegningen flyttes af hovedtråden. Løser ikke at noget er langsomt; løser at det langsomme blokerer scroll og input. Det rigtige næste trin når rammerne er tunge men ikke ekstreme.

WebGLfragment shader

Gray–Scott hører til her. Hver celle er uafhængig af alle andre, præcis dét en GPU gør bedst. To teksturer der bytter plads, samme ligninger, hundrede gange gitteret. Prisen er at fejlsøgning bliver væsentligt sværere.

WebGPUcompute

Når regnestykket ikke naturligt er "en farve per pixel", partikelfysik med kollisioner, sortering, n-legeme. Overkill til alt på denne side. Værd at kende grænsen, ikke at krydse den for tidligt.

Den fejl folk laver, er at springe et trin over. Man skriver en shader til noget, der aldrig ville have kostet mere end 3 ms i canvas, og bruger så to dage på at få den til at se rigtig ud på Safari. Mål først. Loftet ligger næsten altid højere, end det føles.
Fælles for alle fire, de 30 linjer enhver af dem har brug for:

  ResizeObserver        gitter og buffere følger elementets faktiske størrelse
  IntersectionObserver  stop løkken når komponenten ikke er på skærmen
  visibilitychange      stop når fanen går i baggrunden
  devicePixelRatio      klippet til 2, 3× koster 2,25× fyldrate for intet
  prefers-reduced-motion ét stillbillede, med samme information
  performance.now()     delta-tid, aldrig frame-tælling
  pointercancel         ellers hænger interaktionen når systemet overtager gesten