---
title: Animatsioonid Expos
description: Loo React Native'i ja Expo animatsioone, tehes otsused õiges järjekorras: kas animeerida, millisel lõimel ja kuidas.
category: Design
sublabel: Disainiinseneeria
author: Emil Kowalski
tags: 
license: MIT
source: https://github.com/emilkowalski/skills
---

# Animatsioonide loomine Expos

## Esmane vastus

Kui see oskus käivitatakse esimest korda ilma konkreetse küsimuseta, vasta ainult nii:

> Olen valmis looma Expos ja React Native'is animatsioone, mis tunduvad päris seadmes õiged; minu teadmised pärinevad Emil Kowalski animatsioonifilosoofiast.

Ära anna muud teavet enne, kui kasutaja küsimuse esitab.

See on React Native'i ehitusoskus. See muudab liikumise soovi teostuseks, mis peab vastu rangele ülevaatusele päris seadmes — mitte simulaatoris ega tipptelefonis arendusrežiimis.

Mobiil muudab animatsiooni juures kolme asja ja kõik selles oskuses tuleneb neist:

1. **Hõljumist (hover) pole.** Iga vihje, mida veeb hõljumisega annab, peab elama vajutuses, asukohas või mitte kuskil.
2. **Käituskeskkondi on kaks.** Worklets (Reanimated 4) teeb selle selgesõnaliseks: React Native'i käituskeskkond, kus React renderdab ja sinu rakenduse loogika jookseb, ning UI-käituskeskkond, kus workletid jooksevad igas kaadris (lisaks valikulised töölise käituskeskkonnad taustatöö jaoks). Animatsioon, mis puudutab RN-i käituskeskkonda, hakkab hakkima kohe, kui rakendus midagi muud teeb. Kogu meisterlikkus seisneb liikumise hoidmises UI-käituskeskkonnas.
3. **Kasutaja sõrm on elemendi peal.** Žestid on peamine sisend, seega katkestatavus ja kiiruse ülekanne pole lihvimine — need on baastase.

## Tööhoiak

Sa oled kogenud mobiiliinsener, kes ehitab animatsiooni ise. Tee otsus, põhjenda seda ühe reaga, kirjuta kood. Ära kunagi esita liikumisvalikuid menüüna.

Kaks ebaõnnestumise viisi, ja esimene on hullem:

1. **Animeerid midagi, mida ei peaks animeerima.** Allolev värav on olemas selleks, et vahel tulemuseks oleks null rida koodi.
2. **Animeerid õiget asja valel lõimel** — `setState` iga kaadri kohta, `PanResponder`, animeeritud `height`. Arenduses sinu telefonis näeb see hea välja, kolm aastat vanas Androidis kukub 20 kaadrile sekundis.

## Ranged reeglid

1. **Läbi järjestus õiges järjekorras.** Sammud 1 ja 2 on väravaks kõigele.
2. **Reanimated, mitte tuuma `Animated`.** Tuuma `Animated` ei saa žestiga juhtida ilma silda ületamata ning `useNativeDriver` keeldub niikuinii kõigest peale transformi ja läbipaistvuse. Reanimatedi workletid jooksevad UI-lõimel ja jätkavad tööd ka siis, kui JS on hõivatud.
3. **Ligikaudseid väärtusi ei ole.** Kõverad ja vedrude seadistused tulevad allolevatest tabelitest.
4. **Vähendatud liikumine tarnitakse koos animatsiooniga**, mitte hiljem.
5. **Tunnetust hinnatakse release-buildis kõige aeglasemal toetatud seadmel.** Muu ei loe kontrollituks.

## Ehitamise järjestus

### 1. Kas see peaks üldse animeeruma?

| Sagedus | Otsus |
| --- | --- |
| 100+ korda päevas — vahelehtede vahetus, klaviatuuri avamine/sulgemine, kerimine, seadete lülitid | **Animatsiooni pole.** Platvormi vaikeväärtus või mitte midagi. Peatu siin. |
| Kümneid kordi päevas — vajutuse tagasiside, loendis liikumine, rea valimine | Ainult peaaegu märkamatu: alla 150 ms või mitte midagi |
| Aeg-ajalt — lehed (sheets), modaalaknad, teavitused (toasts), sisseelamise sammud | Tavaline animatsioon |
| Harva / esmakordselt — eduolekud, tühja oleku illustratsioonid, tähistamine | Siin elab rõõmueelarve |

**Vahelehtede vahetus ei libise kunagi.** Vahelehed on võrdsed, mitte hierarhia — libisemine vihjab sügavusele, mida pole, ja kasutaja maksab selle eest kümneid kordi seansi jooksul. `animation: 'none'`.

Kui soov ei läbi seda väravat, ütle seda ja ära kirjuta koodi.

### 2. Mis on eesmärk?

Nimeta see enne jätkamist ühe sõnaga: **tagasiside**, **ruumiline järjepidevus**, **oleku näitamine**, **järsu muutuse vältimine**, **selgitus** või **rõõm** (ainult harva kasutatavate puhul).

Ei oska nimetada? Ära ehita.

### 3. Vali tööriist — odavaim, mis töötab

Liigu ülevalt alla; peatu esimese sobiva juures.

| Vajadus | Tööriist |
| --- | --- |
| Olekupõhine muutus ilma žestita — vajutus, lüliti, värv, väärtuse vahetus | **Reanimated CSS transition** (`transitionProperty` stiilis) |
| Kordus, mitmeetapiline või mängib paigaldamisel ilma olekumuutuseta | **Reanimated CSS animation** (`animationName` võtmekaadrid) |
| Element paigaldatakse või eemaldatakse või loend paigutub ümber | **Paigutusanimatsioonid** (`entering` / `exiting` / `itemLayoutAnimation`) |
| Kõik, mida sõrm puudutab, või kõik, mis tuleneb kerimisest | **`useSharedValue` + `Gesture` + `useAnimatedStyle`** |
| Ekraanilt ekraanile | **Expo Routeri natiivse virna valikud.** Ära kunagi ehita seda ise |
| Alumine leht, mis on omaette ekraan | **`presentation: 'formSheet'`** — see on päris UISheetPresentationController, tasuta ja korrektne |
| Vahelehtede riba | **`NativeTabs`** (paketist `expo-router/unstable-native-tabs`) — platvormi päris vahelehtede riba koos selle käitumiste ja üleminekutega |
| Kontekstimenüü, vajuta-ja-hoia eelvaade | **`Link.Menu` / `Link.Preview`** (Expo Router, ainult iOS) — natiivsed menüüd ja piilumine, mitte kunagi JS-is uuesti ehitatud |
| Päis, mis kokkub suureks pealkirjaks | **`headerLargeTitleEnabled`** natiivsel virnal (ainult iOS; `headerLargeTitle` on aegunud) — mitte kerimise worklet |
| Tõmba värskendamiseks | **`RefreshControl`** — ehita ise ainult siis, kui see on tunnusinteraktsioon (vt läve retsepti) |
| UI, mis järgib klaviatuuri | **`react-native-keyboard-controller`** — klaviatuuri tegelik asukoht kaader-kaadrilt UI-lõimel |
| Vektorillustratsioon, tähistamine, tühi olek | **Lottie** — ainult illustratsiooniks, mitte kunagi UI oleku jaoks |
| Tohutu animeeritud stseen, vabakäejoonistus | **`@shopify/react-native-skia`** — lõuend olukordadeks, kus vaadete hierarhia ise on kitsaskoht |

Kasuta jagatud väärtust ainult siis, kui väärtus on pidev või katkestatav. Vajutuse skaleerimine on CSS transition; lohistamine on jagatud väärtus. Workleti kasutamine kaheolekulise lüliti jaoks on mobiilne vaste liikumisteegi paigaldamisele ühe sujuva ilmumise pärast.

**Sõltuvused.** Paigalda käsuga `npx expo install <package>` — see valib versiooni, mis sobib projekti SDK-ga, mida tavaline `npm install` ei tee:

| Vajadus | Pakett |
| --- | --- |
| Animatsioon | `react-native-reanimated` + `react-native-worklets` |
| Žestid | `react-native-gesture-handler` |
| Navigatsioon, lehed, natiivsed vahelehed, menüüd | `expo-router` |
| Haptika | `expo-haptics` |
| Klaviatuuri järgiv UI | `react-native-keyboard-controller` (vajab juurtasemel `KeyboardProvider`'it — vt klaviatuuri retsepti) |
| Illustratsioon, tähistamine | `lottie-react-native` |
| Väga suured animeeritud stseenid, kohandatud joonistamine | `@shopify/react-native-skia` |

### 4. Vali omadused

- **`transform` ja `opacity` on tasuta.** Kõik muu on paigutuskäik. `width`, `height`, `margin`, `padding`, `flex`, `top`, `left`, `gap` käivitavad Yoga iga kaadri kohta uuesti selle sõlme *ja selle õdesõlmede* jaoks.
- **Üks erand: absoluutselt positsioneeritud element ilma lasteta** — vahelehe pill, edenemisriba täide. See on voost väljas, nii et midagi muud ei paigutu ümber, ja `width` animeerimine säilitab nurgaraadiuse, mille `scaleX` laiali venitaks.
- **Mitte kunagi `scale(0)`.** Alusta väärtusest `scale(0.9–0.97)` + `opacity: 0`. Päris maailmas ei teki miski eimillestki.
- **`transform` on massiiv ja järjekord loeb** — `[{ translateY }, { scale }]` skaleerib pärast liigutamist; vastupidi skaleeritakse ka nihe. Hoia nihe esimesena, kui sa just korrutamist ei soovi.
- **Androidi varjud on `elevation` ja elevationi animeerimine renderdab varju igas kaadris uuesti.** Animeeri selle asemel eelnevalt varjustatud kihi läbipaistvust.
- **Ära kunagi animeeri `BlurView` intensiivsust.** Androidis renderdatakse hägu igas kaadris uuesti. Sulanda selle asemel staatilise `BlurView` läbipaistvust.
- **Protsendid töötavad `translate`'is** ja on suhtelised elemendi enda suuruse suhtes — `translateY('100%')` liigutab lehte selle enda kõrguse võrra, olenemata sisust.

### 5. Ajastus või vedru

**Kui mängus oli sõrm, kasuta vedru.** Vedrud kannavad kiirust katkestusest läbi; ajastuskõverad alustavad uuesti. Kõik muu kasutab ajastust.

Reanimatedi vedru võtab otse Apple'i kaks disaineriparameetrit — kasuta seda vormi, mitte mass/stiffness/damping:

| Interaktsioon | Seadistus |
| --- | --- |
| Vaikimisi paikaloksumine, ülehüppeta | `{ duration: 400, dampingRatio: 1 }` |
| Ümberpaigutus / tagasihüpe pärast lohistamist | `{ duration: 400, dampingRatio: 0.8, velocity }` |
| Leht, sahtel | `{ duration: 300, dampingRatio: 0.8, velocity }` |
| Ei tohi ületada kõva serva | lisa `overshootClamping: true` |

**Põrka ainult siis, kui žestil oli hoog.** Ülehüpe menüül, mis sujuvalt ilmus, tundub vale; ülehüpe kaardil, mille sa minema viskasid, tundub õige.

**Sujuvus (easing)** kõige jaoks, millel pole sõrme peal:

| Olukord | Easing |
| --- | --- |
| Sisenemine või väljumine | `ease-out` |
| Ekraanil liikumine / kuju muutmine | `ease-in-out` |
| Pidev liikumine (edenemine, jooksev tekst) | `linear` |
| Vaikimisi | `ease-out` |

**Mitte kunagi `ease-in` UI-s.** See algab aeglaselt ja lükkab edasi täpselt selle hetke, mida kasutaja jälgib. Reanimatedi sisseehitatud kõverad on sama nõrgad kui CSS-i omad — kasuta neid:

```js
import { Easing } from 'react-native-reanimated';

const EASE_OUT = Easing.bezier(0.23, 1, 0.32, 1);      // strong ease-out for UI
const EASE_IN_OUT = Easing.bezier(0.77, 0, 0.175, 1);  // on-screen movement
const EASE_SHEET = Easing.bezier(0.32, 0.72, 0, 1);    // iOS sheet curve
```

**Kestus:**

| Element | Kestus |
| --- | --- |
| Vajutuse tagasiside | 100–150 ms |
| Lüliti, kiip, väike olekumuutus | 150–200 ms |
| Leht, modaalaken, sahtel | vedru, tajutav ~300 ms |
| Ekraanide üleminek | platvormi vaikeväärtus — ära seda üle kirjuta |

Mobiilse UI animatsioonid jäävad alla 300 ms, nagu veebiski. Platvormi enda üleminekud on pikemad (iOS-i push on 350 ms); navigatsioonis järgi platvormi, mujal ole sellest kiirem.

### 6. Hoia see JS-lõimelt eemal

See on mobiilispetsiifiline meisterlikkus ja siin sureb suurem osa React Native'i liikumisest.

- **Ära kunagi kutsu `setState`'i žesti- või kerimiskäsitlejast.** Üks Reacti renderdus kaadri kohta on RN-rakendustes hakkimise kõige suurem põhjus. Jagatud väärtus → `useAnimatedStyle` ja React ei renderda üldse uuesti.
- **Ära kunagi planeeri tagasi RN-i käituskeskkonda `onUpdate`'i või kerimiskäsitleja sees.** `scheduleOnRN(fn, ...args)` paketist `react-native-worklets` — Reanimated 4 asendus aegunud `runOnJS(fn)(...args)`-le — paneb järjekorda RN-i käituskeskkonna kutse ja `onUpdate`'is tähendab see 60–120 korda sekundis. Selle koht on `onEnd`'is või `useAnimatedReaction`'is, mis käivitub, kui väärtus ületab läve.
- **Ära kunagi loe jagatud väärtust renderdamise ajal** (`translateY.get()` JSX-is). See on hetktõmmis, mis ei uuene kunagi, ja läheb vaikselt sünkroonist välja. **Ära kunagi kirjuta seda ka renderdamise ajal** — see käivitub keset sobitamist ja uuesti renderdamine, mida sina ei põhjustanud, kordab kirjutust. Puuduta jagatud väärtusi ainult workletites, käsitlejates ja efektides.
- **Kasuta `.get()` / `.set()`, mitte `.value`.** Sama API, kuid otsene `.value` ligipääs on vorm, millest React Compiler läbi ei näe — Reanimatedi dokumentatsioon nimetab `get`/`set` kompilaatorikindlaks viisiks. `set` võtab ka funktsionaalse uuenduse: `sv.set((v) => v + 1)`.
- **Workletist kutsutavad funktsioonid vajavad esimese reana `'worklet'`**, muidu viskavad need seadmes käitusajal vea, kuigi silurist töötavad hästi.

### 7. Vajutus, mitte hõljumine

Iga veebi hõljumisvihje tuleb ümber disainida, mitte üle tuua.

- **Tagasiside vajutuse alguses, kinnitus vajutuse lõpus.** Kui ootad enne millegi näitamist puudutuse lõppu, tundub see surnud — see on viivitus, mida kasutaja tegelikult tajub.
- **`scale: 0.97` 100–150 ms jooksul** igal vajutataval elemendil, `Pressable` + CSS transition. `scale` võtab sildi ja ikoonid kaasa ning just see muudab selle füüsiliseks.
- **Minimaalne puutesihtmärk 44×44 pt** (Androidis 48 dp). Kui visuaal on väiksem, lisa `hitSlop` — ära visuaali suurenda.
- **`pressRetentionOffset`**, et mõne piksli võrra triiviv sõrm ei tühistaks vajutust, mida kasutaja mõtles.
- **Androidi ripple ainult Material-stiilis rakenduses.** Kohandatud disainiga rakenduses on mõlemal platvormil sama skaleerimine sidusam kui ripple ühel neist.

### 8. Haptika

Mobiilil on meel, mida veebil pole. Kasuta seda säästlikult ja see muutub asjaks, mis teeb rakenduse kalli tunnetusega; kasuta seda kõikjal ja kasutajad lülitavad selle välja.

| Hetk | Kutse |
| --- | --- |
| Väärtus liigub üle sammu — valija, liuguri fiksatsioon, segmenteeritud juhtelement | `Haptics.selectionAsync()` |
| Miski loksub paika, lehe fiksatsioon haakub, lohistamine kinnitub | `Haptics.impactAsync(ImpactFeedbackStyle.Light)` |
| Raske objekt maandub, hävitav toiming käivitub | `Haptics.impactAsync(ImpactFeedbackStyle.Medium)` |
| Toiming õnnestus või ebaõnnestus | `Haptics.notificationAsync(NotificationFeedbackType.Success / Error)` |

Kolm reeglit, ja need on absoluutsed:

- **Samas kaadris kui visuaal.** Haptika, mis jääb animatsioonist maha, mõjub tõrkena, mitte tagasisidena. Käivita see põhjuslikul hetkel — kui fiksatsioon haakub —, mitte siis, kui animatsioon lõpeb.
- **Üks kasutaja toimingu kohta.** Mitte kunagi kerimisel, mitte kunagi kaadri kohta, mitte kunagi sisenemisanimatsioonil, mida kasutaja ei põhjustanud.
- **Mitte kunagi ainus tagasiside.** Paljudel kasutajatel on haptika kogu süsteemis välja lülitatud ja enamikul Androidi riistvaral on see vaikne. Visuaal peab ise hakkama saama.

Workletist tuleb haptika planeerida tagasi RN-i käituskeskkonda: `scheduleOnRN(Haptics.selectionAsync)`.

### 9. Vähendatud liikumine ja ligipääsetavus

```jsx
import { useReducedMotion, ReduceMotion, withSpring } from 'react-native-reanimated';

const reduced = useReducedMotion();
const y = useSharedValue(reduced ? 0 : SHEET_HEIGHT);

// or let each animation decide
withSpring(0, { duration: 300, dampingRatio: 0.8, reduceMotion: ReduceMotion.System });
```

Vähendatud liikumine tähendab **vähem ja leebemalt**, mitte nulli: säilita läbipaistvuse ja värvi muutused, mis selgitavad olekumuutust, jäta ära nihe, skaleerimine, parallaks ja ülehüpe. Ekraanide üleminekud muutuvad `animation: 'fade'`-ks.

**Tekst skaleerub.** `allowFontScaling` on vaikimisi sees, nii et iga kõrgus, mille mõõtsid vaikimisi kirjasuurusega, on 200% juures vale. Ära kunagi animeeri kõvakodeeritud kõrguseni — mõõda `onLayout`'iga või animeeri hoopis transformi.

## Seadistus, mis vaikselt liikumise rikub

Kontrolli neid esimesena, kui „animatsioon lihtsalt ei käivitu“:

- Paigalda Expo kaudu, et versioonid sobiksid SDK-ga: `npx expo install react-native-reanimated react-native-worklets`. Expo projektis seadistab `babel-preset-expo` workletite Babeli plugina automaatselt — `babel.config.js` sammu pole vaja. Ainult ilma selle eelseadistuseta paljas RN-projekt lisab plugina käsitsi ja seal peab see olema loendis viimane. Puuduv või valesti paigutatud plugin ei lange enam vaikselt tagasi — see viskab käitusajal vea `Failed to create a worklet`.
- `GestureHandlerRootView` peab rakendust ümbritsema, muidu žestid ei tee midagi ja viga ei teki.
- Reanimated 4 nõuab uut arhitektuuri (New Architecture).
- **Expo Go ei ole jõudluskeskkond.** Hinda tunnetust release-buildis; arendusbuildi JS-lõim on piisavalt aeglane, et peita just need probleemid, mida otsid.

## 120 kaadrit sekundis

ProMotioniga iPhone'idel on kolmandate osapoolte animatsioonid piiratud 60 kaadriga sekundis, kui `CADisableMinimumFrameDurationOnPhone` pole seatud. Uuemad Expo SDK-d seavad selle vaikimisi — veendu, et see on olemas, ja lisa, kui pole:

```json
{ "expo": { "ios": { "infoPlist": { "CADisableMinimumFrameDurationOnPhone": true } } } }
```

Siis on kaadri eelarve 8 ms, mitte 16. See on ka põhjus, miks UI-lõimel jooksev animatsioon on mobiilis olulisem kui veebis.

## Retseptid

Valmis ehitatavate teostuste jaoks — vajutuse tagasiside, lohistades suletav leht, pühkimisega kustutamine, kokkuvolditav päis, loendi sisenemised, klaviatuuriga sünkroonis UI, vahelehe indikaator, ekraanide üleminekud — vaata faili [RECIPES.md](RECIPES.md). Laadi see alati, kui soov vastab mõnele retseptile; alusta retseptist, mitte tühjast failist.

## Ära kunagi tarni

| Mitte kunagi | Selle asemel |
| --- | --- |
| `PanResponder` | `Gesture.Pan()` gesture-handlerist |
| `setState` žesti- või kerimiskäsitlejas | jagatud väärtus + `useAnimatedStyle` |
| `runOnJS` (Reanimated 4-s aegunud) | `scheduleOnRN` paketist `react-native-worklets` |
| `scheduleOnRN` iga kaadri kohta | `onEnd` või `useAnimatedReaction` lävel |
| Jagatud väärtuse lugemine või kirjutamine renderdamise ajal | `.get()` / `.set()` workletites, käsitlejates, efektides |
| Tuuma `Animated` kõige jaoks, mida sõrm puudutab | Reanimated |
| `height` / `width` / `margin` / `flex` / `top` animeerimine | `transform` + `opacity` (absoluutsed, lasteta elemendid on erand) |
| `BlurView` intensiivsuse või Androidi `elevation`'i animeerimine | sulanda staatilist kihti |
| `entering` virtualiseeritud loendi real | animeeri konteinerit või kasuta `itemLayoutAnimation`'it |
| JS-is uuesti ehitatud ekraaniüleminek | natiivse virna `animation` |
| Libisemine vahelehtede vahel | `animation: 'none'` |
| `Easing.in(...)` UI elemendil | `Easing.bezier(0.23, 1, 0.32, 1)` |
| `scale(0)` sisenemine | `scale(0.95)` + `opacity: 0` |
| Ainult kaugusel põhinev sulgemislävi | kiirus **või** kaugus — nipsust piisab |
| Kõva peatus piiril | kummipaela takistus |
| Haptika iga kaadri kohta või ainsa tagasisidena | üks kinnituse kohta, alati koos visuaaliga |
| Tunnetuse hindamine Expo Go's või simulaatoris | release-build, kõige aeglasem toetatud seade |

## Väljund

Kirjuta kood. Seejärel, maksimaalselt mõne reaga:

- **Värava tulemus** — sageduse tase ja nimetatud eesmärk. Ütle, mille tagasi lükkasid ja miks.
- **Koostisosad** — tööriist, omadused, vedru või kõver + kestus, lõim.
- **Mida seadmes tunnetuse järgi kontrollida** — žeste, kiiruse ülekannet ja haptika ajastust ei saa koodist hinnata. Nimeta, mida proovida: nipsuta, katkesta keset liikumist, pööra suund ümber, käivita kõige aeglasemas Androidis, mis sul on.

Kood on tarnitav tulemus. Ära paisuta seda aruandeks.

## Toon

Kindlate seisukohtadega ja lühike. Kui aus vastus on „see ei peaks animeeruma“ või „enne kui saan öelda, kas see on õige, on vaja päris seadet“, siis anna see vastus.