IlmHamroh
JavaScript Full-stack/20-qism. React: UI kutubxonalar va animatsiya21/30-dars21 daqiqa
Mundarija (32)

UI animatsiya tamoyillari: davomiylik, easing, spring va 60 fps — React'da tez va ma'noli harakat

Qisqacha: Animatsiya bezak emas — u holat o'zgarishini tushuntiradi: nima paydo bo'ldi, qayerga ketdi, bosish qabul qilindimi. Interfeysda 150–300 ms yetadi, kiruvchi narsaga ease-out, chiquvchiga ease-in. Arzon animatsiya faqat transform va opacity da: brauzer ularni layout va bo'yashsiz, kompozitorda bajaradi. React'da animatsiya qiymatini har kadrda state'ga yozmang — har kadr render bo'ladi. Va har animatsiyaning prefers-reduced-motion dagi harakatsiz varianti bo'lsin.

Bu darsda

  • Animatsiya qachon ma'no beradi va qachon xalaqit berishini ajratasiz.
  • Davomiylik va easing'ni vaziyatga qarab tanlaysiz, tween va spring farqini tushuntira olasiz.
  • Nega transform va opacity arzon, width va top esa qimmat ekanini kadr jarayoni bilan isbotlaysiz.
  • React'da animatsiya render'larni ko'paytirmasligi uchun ref'dan foydalanasiz va INP o'lchovini o'qiysiz.
  • prefers-reduced-motion ni React'da o'qiysiz va vazifalar ning animatsiya bosqichini boshlaysiz.

Oldin bilishingiz kerak: Transition va timing funksiyalari, Rendering pipeline va animatsiya samaradorligi, prefers-reduced-motion va harakat etikasi, requestAnimationFrame, useEffect I.

1. Nega bu kerak?

Oldingi darsda kichik kutubxonalarni o'lchadik. Endi 20-qismning animatsiya moduli boshlanadi.

Jasur aka "ilova jonliroq bo'lsin" dedi. Sardor bir kechada hamma narsaga animatsiya qo'shdi: tugmalar sakraydi, vazifalar 600 ms da "uchib" kiradi, menyu aylanib ochiladi. Ertasiga uch xil shikoyat keldi.

Bobur: "Menyuni ochib, strelka bosaman — hali ochilib bo'lmagan, birinchi bandni o'tkazib yuboraman". Kassir qiz: "Har vazifani belgilaganda yarim soniya kutaman, kuniga 300 marta". Bitta mijoz esa: "Ekrandagi harakatdan boshim aylandi".

Hayotiy o'xshatish: yaxshi ofitsiant. U taomni qo'yganda bir oz ehtiyotkorlik bilan qo'yadi — siz taom kelganini sezasiz. Lekin har likopchani raqs bilan olib kelsa — birinchi kuni qiziq, ikkinchi kuni charchatadi. Harakat xizmat qilsin, tomosha emas.

2. Animatsiya nima uchun kerak

2.1 To'rt vazifa

UI animatsiyasi to'rt ishdan birini qilsa — o'rinli:

  1. Holat o'zgarishini ko'rsatish. Vazifa o'chirildi — qator so'nib yo'qoladi. Ko'z "nima bo'ldi?" ni sezadi, aks holda ro'yxat shunchaki "sakraydi".
  2. Fazoviy bog'lanish. Menyu tugmadan "o'sib" chiqadi — u qayerdan kelganini ko'rasiz. Drawer pastdan chiqadi — pastga surib yopish mumkinligini his qilasiz.
  3. Javob (feedback). Tugma bosilganda bir oz kichrayadi — "bosish qabul qilindi".
  4. E'tibor. Yangi xabar kelganda belgi bir marta "lip" etadi.

Va animatsiya kerak bo'lmagan joylar: har kuni yuzlab marta bosiladigan narsalar (katakcha, filtr), matnni o'qishga xalaqit beradigan harakat, sabr sinovidan o'tkazadigan uzun "yuklanmoqda" effektlari.

2.2 Bitta qoida

Har animatsiya uchun bitta savol: "Bu foydalanuvchiga nimani tushuntiradi?" Javob topilmasa — animatsiya kerak emas. Javob "chiroyli bo'ladi" bo'lsa — eng kami bilan cheklaning: qisqa, kichik masofa, faqat opacity.

Tekshirib ko'ring: «Bahor» admin panelida jadval qatorlari sahifa ochilganda birin-ketin (har biri 100 ms kechikib) paydo bo'ladi. 30 qator. Bu qaysi vazifani bajaradi va muammosi nima?

Javob

To'rt vazifadan hech birini: ma'lumot o'zgarmadi, hech narsa qayerdandir kelmadi. Muammosi — oxirgi qator 3 soniyadan keyin ko'rinadi (30 × 100 ms). Admin har kuni bu sahifani o'nlab marta ochadi va har safar kutadi. Ketma-ket paydo bo'lish (stagger) birinchi tanishuvda, kam elementda, qisqa umumiy vaqt bilan o'rinli — Motion II darsida o'lchaymiz.

3. Davomiylik va easing

3.1 Qancha davom etsin

Interfeys animatsiyalari uchun amaliy oraliqlar:

Nima Davomiylik
Bosish javobi (tugma, katak) ~100 ms
Menyu, tooltip, kichik o'tish 120–200 ms
Oyna, drawer, katta o'tish 200–300 ms
Ekrandan ekranga katta harakat 300–500 ms

Qoidalar:

  • Masofa katta — vaqt ko'proq. 8 px siljish va butun ekran bo'ylab uchish bir xil vaqtda bo'lmaydi.
  • Chiqish kirishdan qisqaroq. Yopilayotgan narsaga foydalanuvchi endi qaramaydi.
  • Ko'p takrorlanadigan — qisqaroq. Kuniga 300 marta ochiladigan menyu 120 ms, oyiga bir marta chiqadigan "xush kelibsiz" oynasi — 400 ms.
  • 300 ms dan oshsa — foydalanuvchi kuta boshlaydi. Bu endi "javob" emas, "to'siq".

vazifalar dagi raqamlar shu jadvalga mos: menyu 120 ms, tasdiq oynasi 150 ms, toast 200 ms.

3.2 Easing: tezlik qanday o'zgaradi

Easing (yumshatish) — animatsiya davomida tezlik qanday o'zgarishi. Transition va timing funksiyalari darsida cubic-bezier egri chizig'ini o'rganganmiz. Qaysi biri qachon:

  • ease-out — tez boshlanadi, sekin to'xtaydi. Kiruvchi narsalar uchun: menyu ochildi, oyna chiqdi. Foydalanuvchi bosgan zahoti harakat boshlanadi — tez javob his qilinadi.
  • ease-in — sekin boshlanadi, tez tugaydi. Chiquvchi narsalar uchun: ekrandan ketayotgan narsa "tezlashib" chiqib ketadi.
  • ease-in-out — ekran ichida bir joydan boshqasiga ko'chish.
  • linear — tabiatda deyarli yo'q, mexanik ko'rinadi. Faqat aylanish (spinner) va progress uchun.

Quyidagi vidjetda ikki quti bor: yuqoridagisi siz tanlagan easing bilan, pastdagisi doim linear. Davomiylikni o'zgartirib, farqni o'z ko'zingiz bilan ko'ring:

Nimaga qarang: ikkala quti bir vaqtda boshlab, bir vaqtda yetib keladi — davomiylik bir xil. Farq — yo'l davomidagi tezlikda. ease-out dagi quti boshida oldinga "otiladi". 1500 ms da hamma easing sust tuyuladi, 100 ms da esa farqni ko'rish qiyin. Tizimingizda "harakatni kamaytirish" yoqilgan bo'lsa, qutilar joyidan qimirlamaydi — CSS'dagi @media qoidasi ishladi.

3.3 Tween va spring

Oxirgi cubic-bezier(0.34, 1.56, 0.64, 1) maqsaddan bir oz o'tib qaytdi. Bu prujinaga taqlid. Animatsiyani tasvirlashning ikki usuli bor:

  • Tween (oraliq kadrlar) — davomiylik va easing egri chizig'i bilan. "300 ms da, ease-out bilan". CSS transition va animation — tween.
  • Spring (prujina) — fizika bilan: qattiqlik (stiffness — qanchalik kuchli tortadi) va so'ndirish (damping — qanchalik tez tinchlanadi). Davomiylik oldindan berilmaydi — prujina tinchlanguncha davom etadi.

Farqni raqamda ko'ramiz. Ikkala usul ham 0 dan 100 gacha. Har 50 ms da qiymatni yozamiz (prujinani har 1 ms da hisoblaymiz):

js
// Ikki egri chiziq: 0 dan 100 gacha, har 50 ms da qiymat
function easeOut(t) {
  return 1 - (1 - t) ** 3; // tez boshlanadi, sekin to'xtaydi
}

function tween(ms, duration) {
  const t = Math.min(ms / duration, 1);
  return Math.round(100 * easeOut(t));
}

// Prujina: tortish kuchi (qattiqlik) va so'ndirish (ishqalanish)
function springValues(stiffness, damping, until) {
  const values = [];
  let x = 0;
  let v = 0;
  const dt = 1 / 1000; // 1 ms qadam
  for (let ms = 0; ms <= until; ms++) {
    if (ms % 50 === 0) values.push(Math.round(x));
    const force = stiffness * (100 - x) - damping * v;
    v += force * dt;
    x += v * dt;
  }
  return values;
}

const times = [0, 50, 100, 150, 200, 250, 300, 350, 400, 450, 500];
console.log("tween:", times.map((ms) => tween(ms, 300)).join(" "));
console.log("spring:", springValues(300, 18, 500).join(" "));

Konsolda:

text
tween: 0 42 70 88 96 100 100 100 100 100 100
spring: 0 27 72 103 114 112 105 100 98 98 99

Xuddi shu raqamlar grafikda:

Tween (ease-out, 300 ms) va spring (qattiqlik 300, so'ndirish 18)
qiymat
11400500vaqt, mstween: 0 ms → 0tween: 50 ms → 42tween: 100 ms → 70tween: 150 ms → 88tween: 200 ms → 96tween: 250 ms → 100tween: 300 ms → 100tween: 350 ms → 100tween: 400 ms → 100tween: 450 ms → 100tween: 500 ms → 100spring: 0 ms → 0spring: 50 ms → 27spring: 100 ms → 72spring: 150 ms → 103spring: 200 ms → 114spring: 250 ms → 112spring: 300 ms → 105spring: 350 ms → 100spring: 400 ms → 98spring: 450 ms → 98spring: 500 ms → 99
  • tween
  • spring
Tween (ease-out, 300 ms) va spring (qattiqlik 300, so'ndirish 18)
vaqttweenspring
00
5042
10070
15088
20096
250100
300100
350100
400100
450100
500100
00
5027
10072
150103
200114
250112
300105
350100
40098
45098
50099

Manba: Darsdagi tween/spring hisobi (yuqoridagi js bloki, Node 24 da ishga tushirilgan)

Grafikda nimaga qarang:

  • Tween 300 ms da aniq 100 ga yetadi va to'xtaydi. Oldindan bilinadi — CSS uchun qulay.
  • Spring 150 ms da 100 dan o'tib ketadi (114 gacha), keyin orqaga qaytib tinchlanadi. Bu "tirik" his beradi.

Spring'ning asl kuchi boshqa joyda — to'xtatish (interrupt). Menyu ochilayotganda foydalanuvchi uni yopsa, tween yangi animatsiyani noldan boshlaydi: harakat "sinadi". Spring esa joriy tezlikni saqlab, orqaga buriladi — xuddi qo'l bilan itarilgan narsa kabi. Sudrash (drag) va surib tashlash (swipe) uchun shuning uchun spring ishlatiladi. Motion kutubxonasi x, scale kabi "jismoniy" qiymatlar uchun sukut bo'yicha spring, opacity va rang uchun esa tween oladi (Motion I darsida).

Tekshirib ko'ring: Toast pastdan chiqadi va 3 soniyadan keyin yo'qoladi. Kirish va chiqishga qanday easing va davomiylik berasiz?

Javob

Kirish: ease-out, 200 ms atrofida — tez paydo bo'ladi, yumshoq to'xtaydi. Chiqish: ease-in, 150 ms — qisqaroq, chunki foydalanuvchi endi unga qaramayapti. Masofa kichik (8–16 px) va opacity bilan birga. vazifalar dagi toast — 200 ms, opacity va translate.

4. Brauzer kadrni qanday chizadi

4.1 60 fps va 16.7 ms

Ekran soniyasiga 60 marta yangilanadi (ko'p telefonlarda 90 yoki 120 marta). Animatsiya silliq bo'lishi uchun brauzer har kadrni o'z vaqtida tayyorlashi kerak: 1000 ÷ 60 ≈ 16.7 ms, 120 Hz ekranda — 8.3 ms. Bu vaqtga JavaScript, React render, uslub hisobi va chizish — hammasi sig'ishi kerak. Sig'masa, kadr tashlab yuboriladi va harakat "qaltiraydi" (jank).

Rendering pipeline darsidagi to'rt bosqichni eslaymiz:

flowchart LR
  S["Style: uslub hisobi"] --> L["Layout: joylashuv"]
  L --> P["Paint: bo'yash"]
  P --> C["Composite: qatlamlarni yig'ish"]
  W["width, top, margin"] -.-> L
  B["color, background, shadow"] -.-> P
  T["transform, opacity"] -.-> C

Nuqtali o'qlar — xususiyat o'zgarganda qaysi bosqichdan boshlab hamma narsa qayta ishlanadi:

  • width, height, top, left, margin — layout'dan boshlab hammasi: sahifadagi qo'shni elementlar ham qayta joylashadi.
  • color, background, box-shadow — layout'siz, lekin qayta bo'yash bilan.
  • transform, opacity — faqat yig'ish. Element tayyor qatlam sifatida siljiydi yoki xiralashadi. Bu ishni brauzer kompozitor (compositor) oqimida, ko'pincha videokarta bilan bajaradi. Asosiy oqim (JavaScript, React) band bo'lsa ham, bunday animatsiya silliq davom etishi mumkin.

Shuning uchun qoida: animatsiya — faqat transform va opacity da. "Kattalashsin" — scale, width emas. "Pastdan chiqsin" — translate, top emas. "Paydo bo'lsin" — opacity, display emas.

4.2 will-change

Brauzer elementni alohida qatlamga ko'pincha animatsiya boshlanganda chiqaradi. Birinchi kadr shu sababli ba'zan kechikadi. will-change — brauzerga oldindan "bu xususiyat o'zgaradi" deb aytish:

css
.drawer {
  will-change: transform;
}

Ehtiyot bo'ling: har qatlam xotira oladi. * { will-change: transform } kabi "hammaga" qo'yish sahifani tezlashtirmaydi, aksincha sekinlashtiradi — ayniqsa telefonda. Qoida: faqat haqiqatan sekin bo'lgan, o'lchangan animatsiyaga, iloji bo'lsa animatsiya oldidan qo'yib, keyin olib tashlang.

4.3 Layout thrashing animatsiyada

Rendering pipeline va layout thrashing darsida ko'rganmiz: JavaScript'da o'qish (offsetHeight, getBoundingClientRect) va yozish (style.height = …) navbatma-navbat kelsa, brauzer har o'qishda layout'ni qayta hisoblaydi. Animatsiyada bu har kadrda takrorlanadi.

Klassik tuzoq — "elementning balandligini o'lchab, unga qarab boshqasini siljitish" har kadrda. Yechim: avval hamma o'qishlar, keyin hamma yozishlar. Animatsiya kutubxonalari elementning joyini bir marta o'lchaydi va keyin faqat transform bilan harakatlantiradi. Bu usulning nomi FLIP (First, Last, Invert, Play). Motion'ning layout animatsiyalari shunday ishlaydi (Motion III darsida).

Tekshirib ko'ring: Dizayner "kartochka bosilganda biroz kattalashsin" dedi. Sardor width: 110% ga transition qo'ydi. Nima noto'g'ri va qanday tuzatasiz?

Javob

width — layout xususiyati. Kartochka kengaygan sari qo'shni kartochkalar ham har kadrda qayta joylashadi — ro'yxat "qaltiraydi" va 60 fps qiyinlashadi. To'g'risi — transform: scale(1.05) (yoki scale: 1.05): kartochka qatlam sifatida kattalashadi, qo'shnilar joyidan qimirlamaydi. Kattalashgan kartochka qo'shnisining ustiga chiqishi mumkin — kerak bo'lsa z-index bilan.

5. React'da animatsiya narxi

5.1 Har kadrda render

React'da birinchi xayolga keladigan yo'l — animatsiya qiymatini state'da saqlash. Har kadrda setX — har kadrda render. Taksi belgisini 10 kadrda 100 px suramiz va render'larni sanaymiz:

jsx
import { useEffect, useRef, useState } from "react";

export default function App() {
  const [x, setX] = useState(0);
  const renders = useRef(0);
  renders.current++;

  useEffect(() => {
    let frame = 0;
    let id;
    function step() {
      frame++;
      setX(frame * 10);
      if (frame < 10) {
        id = requestAnimationFrame(step);
      } else {
        setTimeout(() => {
          console.log("kadr:", frame, "render:", renders.current);
        }, 50);
      }
    }
    id = requestAnimationFrame(step);
    return () => cancelAnimationFrame(id);
  }, []);

  return (
    <div style={{ transform: `translateX(${x}px)` }}>Taksi</div>
  );
}

Konsolda:

text
kadr: 10 render: 11

10 kadr — 11 render (birinchisi va har kadrda bittadan). Bu komponent kichik, lekin u katta ro'yxat ichida bo'lsa, har kadrda React daraxt bo'ylab o'tadi. 16.7 ms ning bir qismini React yeb qo'yadi va animatsiya qaltiraydi.

requestAnimationFrame (requestAnimationFrame darsida) — "keyingi kadr oldidan chaqir". cancelAnimationFrame effekt tozalashida — komponent o'chsa, sikl to'xtasin.

5.2 Ref bilan: React'ni chetlab o'tish

Animatsiya qiymati React'ga kerak emas — u faqat DOM'ga kerak. Shuning uchun uni ref orqali to'g'ridan-to'g'ri DOM'ga yozamiz:

jsx
import { useEffect, useRef } from "react";

export default function App() {
  const box = useRef(null);
  const renders = useRef(0);
  renders.current++;

  useEffect(() => {
    let frame = 0;
    let id;
    function step() {
      frame++;
      box.current.style.transform = `translateX(${frame * 10}px)`;
      if (frame < 10) {
        id = requestAnimationFrame(step);
      } else {
        console.log("kadr:", frame, "render:", renders.current);
        console.log(box.current.style.transform);
      }
    }
    id = requestAnimationFrame(step);
    return () => cancelAnimationFrame(id);
  }, []);

  return <div ref={box}>Taksi</div>;
}

Konsolda:

text
kadr: 10 render: 1
translateX(100px)

Bir xil harakat, bitta render. Animatsiya kutubxonalari (Motion, GSAP) aynan shunday ishlaydi: qiymat React state'ida emas, kutubxona ichida. Uni har kadrda DOM'ga yozadi yoki brauzerning Web Animations API'siga (Web Animations API) topshiradi. React faqat "qayerga" degan buyruqni beradi.

5.3 O'lchov: animatsiya ham INP'ga ta'sir qiladi

INP (Interaction to Next Paint) — foydalanuvchi bosgandan keyingi kadr chizilguncha o'tgan vaqt (Performance API va Core Web Vitals darsida). 200 ms gacha — "yaxshi". Bosish paytida qancha ko'p JavaScript ishlasa, INP shuncha yomonlashadi.

Kanon vazifalar ga 20-qism kutubxonalari (dnd-kit, Motion) qo'shilgach, katak bosishni o'lchadi (natijalar/21-inp.txt). 150 vazifa, Chrome, 3 sahifa × 6 bosish, Event Timing API:

Katak bosish INP, 150 vazifa (mediana)
  • v9 (19-qism oxiri)32 ms
  • v10, dnd-kit + Motioneng uzuni 12872 ms
  • v10, dnd-kit o'chirilgan32 ms
  • v10, qatorlarda Motion yo'q40 ms

Manba: Kanon o'lchovi: natijalar/21-inp.txt (Chrome 155, 2026-10-14)

Grafikda nimaga qarang: hammasi 200 ms dan ancha past, lekin o'sish ikki barobardan ko'p. Asosiy sabab — har qatordagi dnd-kit'ning sudrash mexanizmi: u o'chirilsa — v9 darajasi. Motion'ning qatorlardagi ulushi kichikroq, lekin bor. Kanon shundan keyin tuzatildi: dnd-kit alohida «Tartiblash» rejimiga ko'chdi. Oddiy rejimda INP mediana 24 ms bo'ldi (v9 da 16, natijalar/tuzatish-inp.txt, bitta sessiyada). Qolgan +8 ms — Motion qatorlari (TEXNIK-QARZ 59).

Saboq: animatsiya kutubxonasi ham "bepul" emas — u har bosishda ishlaydi. O'lchang.

6. prefers-reduced-motion

6.1 "Kamaytirish" — "o'chirish" emas

Ba'zi odamlarda ekrandagi harakat bosh aylanishi va ko'ngil aynishini chaqiradi (vestibulyar buzilish). Ular operatsion tizimda "harakatni kamaytirish" ni yoqadi. Brauzer buni prefers-reduced-motion: reduce media so'rovi bilan beradi (prefers-reduced-motion darsida).

Bu "hamma animatsiyani o'chir" degani emas. Xavfli — katta harakat: uchish, aylanish, parallaks, ekran bo'ylab siljish. opacity o'zgarishi (so'nish) ko'pincha qoladi — u holat o'zgarishini tushuntiradi va harakat emas. Qoida: harakatni olib tashlang, ma'noni qoldiring.

6.2 React'da o'qish

CSS'da @media (prefers-reduced-motion: reduce) yetadi. Lekin JavaScript animatsiyasini (Motion, ref'li sikl) boshqarish uchun qiymat React'ga kerak. U tashqi manba va o'zgarishi mumkin (foydalanuvchi sozlamani o'zgartiradi). Bunday narsa uchun useSyncExternalStore bor:

jsx
import { useEffect, useSyncExternalStore } from "react";

const QUERY = "(prefers-reduced-motion: reduce)";

function subscribe(callback) {
  const mq = window.matchMedia(QUERY);
  mq.addEventListener("change", callback);
  return () => mq.removeEventListener("change", callback);
}

function getSnapshot() {
  return window.matchMedia(QUERY).matches;
}

function usePrefersReducedMotion() {
  return useSyncExternalStore(subscribe, getSnapshot, () => false);
}

export default function App() {
  const reduce = usePrefersReducedMotion();
  useEffect(() => {
    console.log("harakatni kamaytirish:", reduce);
  }, [reduce]);
  return (
    <p>{reduce ? "Animatsiyasiz rejim" : "Animatsiyalar yoqiq"}</p>
  );
}

Konsolda:

text
harakatni kamaytirish: false

Tizimingizda "harakatni kamaytirish" yoqilgan bo'lsa, natija oynasida true ko'rasiz. Sozlamani ochiq oyna yonida o'zgartirsangiz, yozuv darhol almashadi — subscribe dagi change hodisasi ishladi.

Uchta qism bor. subscribe — "o'zgarsa, menga ayt": matchMedia (matchMedia darsida o'tganmiz) qaytargan obyektning change hodisasiga obuna. getSnapshot — hozirgi qiymat (true yoki false). Uchinchi argument (() => false) — server uchun qiymat: serverda (keyingi qismlarda) window umuman yo'q, React shu funksiyani chaqiradi.

Eski maqolalarda window.matchMedia?.(…) ni ko'rasiz. Bu Vitest kabi test muhiti (jsdom) uchun: unda matchMedia yo'q. Brauzerda u hamma joyda bor, testda esa o'rinbosar (mock) yoziladi.

Kanon qaysi yo'lni tanladi? CSS animatsiyalari uchun — global qoida (asos.css, 13-qismdan):

css
/* Harakatni kamaytirish so'ralgan: o'tishlar deyarli darhol */
@media (prefers-reduced-motion: reduce) {
  *, ::before, ::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }
}

0.01ms nega 0 emas? Animatsiya baribir "bo'ladi" va tugaydi. transitionend va animationend hodisalari keladi, ularga tayangan kod (masalan "animatsiya tugagach o'chir") buzilmaydi. Motion uchun esa Motion I darsida MotionConfig ishlatamiz.

Tekshirib ko'ring: Reduced-motion yoqilgan foydalanuvchi uchun Sardor animation: none qo'ydi. Natijada toast o'chirilgandan keyin ham ekranda qolib ketdi. Nega?

Javob

Toast kodi "chiqish animatsiyasi tugagach (animationend) — DOM'dan olib tashla" deb yozilgan. animation: none da animatsiya umuman bo'lmaydi va animationend hech qachon kelmaydi. Kanondagi 0.01ms usuli shuning uchun: animatsiya "bir zumda" bo'lib tugaydi va hodisa keladi. Yana bir yo'l — kodda reduced-motion'ni tekshirib, hodisani kutmasdan darhol o'chirish.

7. Vazifalar qadami: animatsiya bosqichi boshlanadi (holat 22)

7.1 Audit: hozir nima bor

CSS animatsiyalari React'da darsida vazifalar ga birinchi to'liq animatsiyalarni qo'shamiz. Bugun yangi branch ochamiz, mavjud harakatlarni tekshiramiz va bitta kichik qadam qilamiz:

bash
git switch -c feat/css-animatsiya

Holat 17 dagi animatsiyalar, bugungi qoidalar bilan:

Joy Hozir Muammo
Tasdiq oynasi @keyframes, 150 ms, opacity + transform faqat kirish; yopilganda oyna darhol yo'qoladi
Toast transition: opacity 0.2s to'g'ri, lekin "qayerdan keldi" yo'q
Menyu, tooltip animatsiya yo'q ochilishi "sakraydi"
Skeleton motion-safe:animate-pulse, 300 ms kechikib yaxshi
Reduced-motion global qoida yaxshi

7.2 Toast: pastdan chiqsin — faqat kompozitorda

Toast pastda turadi. Bir oz pastdan chiqsa, ko'z uni "pastdan keldi" deb qabul qiladi. Siljish — translate (transform oilasi), bottom emas. asos.css da (kanon, holat 22):

diff
-  transition: opacity 0.2s;
+  /* 20/#22: + translate — pastdan chiqadi (transform oilasi,
+     kompozitor) */
+  transition: opacity 0.2s, translate 0.2s;
diff
-.bildirishnoma:empty { opacity: 0; }
+.bildirishnoma:empty { opacity: 0; translate: 0 0.5rem; }

Bo'sh toast — ko'rinmas va 0.5rem pastda. Matn kelganda :empty tushib ketadi va u 200 ms da joyiga ko'tariladi. Toast ekranda doim turadi, faqat bo'sh. Uni hidden qilmaymiz: bu jonli hudud, ekran o'quvchi keyingi matnni o'qishi uchun DOM'da bo'lishi kerak (Toast bildirishnomalar).

translate — transform ning alohida xususiyati (individual transform). U transform: translateY(…) bilan bir xil ishlaydi va kompozitorda bajariladi. Reduced-motion foydalanuvchisi uchun global qoida o'tishni 0.01 ms qiladi — toast shunchaki paydo bo'ladi.

Kanon e2e tekshiruvi (holat 22, Chrome 155) aynan shu tamoyilni tekshiradi:

text
✅ toast: o'tish — opacity va translate (kompozitor): "opacity, translate"

Commit hali yo'q — oyna, menyu va tooltip animatsiyalari bilan birga keyingi darsda.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
animatsiya "qaltiraydi" (jank) width, top, margin animatsiyasi transform / opacity
menyu sust tuyuladi 400–600 ms, ease-in kirishda 120–200 ms, ease-out
ro'yxat har kadrda qayta chiziladi animatsiya qiymati state'da ref yoki kutubxona
sahifa sekinlashdi, xotira oshdi hamma joyda will-change faqat o'lchangan joyda, vaqtincha
animationend kelmaydi, element qolib ketdi reduced-motion'da animation: none 0.01ms yoki kodda tekshirish
bosh aylanishi shikoyati katta harakat, parallaks prefers-reduced-motion varianti

9. Mashqlar

Mashqlar kurs/mashqlar/20/21-animatsiya-tamoyillari/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): arzonmi, qimmatmi?

Har o'zgarish qaysi bosqichdan boshlab qayta ishlanadi: layout, paint yoki composite?

  • transform: scale(1.05) →
  • background-color: #1d4ed8 →
  • height: 120px →
  • opacity: 0.5 →
Yechim
  • transform va opacity — faqat composite: tayyor qatlam o'zgaradi.
  • background-color — paint: joylashuv o'zgarmaydi, lekin piksellar qayta bo'yaladi.
  • height — layout: element va uning qo'shnilari qayta joylashadi, keyin bo'yash va yig'ish ham.

2-mashq (o'rta): progress chizig'i render'siz

Yuklash chizig'i 0 dan 100% gacha 10 qadamda to'lsin. Hozirgi kod width ni state orqali o'zgartiradi. Uni shunday o'zgartiring:

  1. State yo'q — ref orqali DOM'ga yozing (render bitta bo'lsin).
  2. width emas, transform: scaleX(...) — chiziq chapdan o'ngga o'sishi uchun transformOrigin: "left".
  3. Oxirida konsolga render soni va yakuniy transform chiqsin.

Ishora: «Ref bilan: React'ni chetlab o'tish» bo'limi. scaleX(1) — to'liq, scaleX(0.5) — yarmi.

jsx
import { useEffect, useRef, useState } from "react";

export default function App() {
  const [width, setWidth] = useState(0);
  const renders = useRef(0);
  renders.current++;
  useEffect(() => {
    let step = 0;
    let id;
    function tick() {
      step++;
      setWidth(step * 10);
      if (step < 10) id = requestAnimationFrame(tick);
      else {
        setTimeout(() => {
          console.log("render:", renders.current);
        }, 50);
      }
    }
    id = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(id);
  }, []);
  return (
    <div style={{ height: 8, background: "#e8edfb" }}>
      <div
        style={{
          height: 8,
          width: `${width}%`,
          background: "#1d4ed8",
        }}
      />
    </div>
  );
}

Konsolda:

text
render: 11
Yechim
jsx
import { useEffect, useRef } from "react";

export default function App() {
  const bar = useRef(null);
  const renders = useRef(0);
  renders.current++;
  useEffect(() => {
    let step = 0;
    let id;
    function tick() {
      step++;
      bar.current.style.transform = `scaleX(${step / 10})`;
      if (step < 10) {
        id = requestAnimationFrame(tick);
      } else {
        console.log("render:", renders.current);
        console.log(bar.current.style.transform);
      }
    }
    id = requestAnimationFrame(tick);
    return () => cancelAnimationFrame(id);
  }, []);
  return (
    <div style={{ height: 8, background: "#e8edfb" }}>
      <div
        ref={bar}
        style={{
          height: 8,
          background: "#1d4ed8",
          transform: "scaleX(0)",
          transformOrigin: "left",
        }}
      />
    </div>
  );
}

Konsolda:

text
render: 1
scaleX(1)

Endi chiziq to'liq kenglikda, lekin scaleX bilan "siqilgan". Har qadamda faqat yig'ish bosqichi ishlaydi va React bir marta chizadi. transformOrigin: "left" bo'lmasa, chiziq o'rtadan ikki tomonga o'sardi. Haqiqiy loyihada qadam soni yuklash foiziga bog'lanadi — bu yerda 10 qadam faqat ko'rsatish uchun.

3-mashq (qiyin): harakat o'rniga so'nish

Yangi buyurtma kartochkasi tepadan 40 px siljib, 300 ms da chiqadi. Reduced-motion foydalanuvchi uchun siljish bo'lmasin, faqat so'nish (opacity) qolsin, 150 ms.

  1. usePrefersReducedMotion hook'idan foydalaning («React'da o'qish» bo'limi).
  2. Animatsiyani @keyframes bilan qiling: ikki xil nom — kirish-siljib va kirish-sonib.
  3. Kartochka ostida qaysi animatsiya tanlangani yozilsin.

Ishora: style={{ animation: reduce ? "…" : "…" }}. <style> matnida ikkala @keyframes.

Yechim
jsx
import { useSyncExternalStore } from "react";

const QUERY = "(prefers-reduced-motion: reduce)";
const css = `
  @keyframes kirish-siljib {
    from { opacity: 0; transform: translateY(-40px); }
  }
  @keyframes kirish-sonib {
    from { opacity: 0; }
  }
  .karta { padding: 12px; border: 1px solid #bbb;
    border-radius: 8px; max-width: 16rem; }
`;

function subscribe(callback) {
  const mq = window.matchMedia(QUERY);
  mq.addEventListener("change", callback);
  return () => mq.removeEventListener("change", callback);
}

function usePrefersReducedMotion() {
  return useSyncExternalStore(
    subscribe,
    () => window.matchMedia(QUERY).matches,
    () => false,
  );
}

export default function App() {
  const reduce = usePrefersReducedMotion();
  const animation = reduce
    ? "kirish-sonib 150ms ease-out"
    : "kirish-siljib 300ms ease-out";
  return (
    <div>
      <style>{css}</style>
      <div className="karta" style={{ animation }}>
        Yangi buyurtma: osh, 35 000 so'm
      </div>
      <p>Animatsiya: {animation}</p>
    </div>
  );
}

Ekranda:

text
Yangi buyurtma: osh, 35 000 so'm
Animatsiya: kirish-siljib 300ms ease-out

Tizimda "harakatni kamaytirish" yoqilgan bo'lsa, ikkinchi qatorda kirish-sonib 150ms ease-out ko'rinadi va kartochka joyidan qimirlamay paydo bo'ladi. Ma'no saqlandi ("yangi narsa keldi"), harakat yo'q. CSS-only variant ham mumkin: @media (prefers-reduced-motion: reduce) { .karta { animation-name: kirish-sonib; } } — JavaScript'siz. Hook esa JS animatsiyasi uchun kerak bo'ladi.

4-mashq: Vazifalar qadami

feat/css-animatsiya branch'ini oching va toast'ga translate o'tishini qo'shing (asos.css: transition va .bildirishnoma:empty). Commit qilmang — keyingi darsda davom etamiz.

Yechim

Tekshiruv ro'yxati:

  • DevTools → Elements → .bildirishnoma: Computed'da transition-property: opacity, translate.
  • Vazifa qo'shing — toast pastdan ko'tarilib chiqadi; DevTools → Rendering → "Emulate CSS media feature prefers-reduced-motion: reduce" — shunchaki paydo bo'ladi.
  • DevTools Performance'da yozib ko'ring: toast chiqishida "Layout" yozuvi yo'q, faqat "Composite Layers".
  • npm run format:check — toza.

10. Real ishda

  • Dizayn tizimlari animatsiyani ham tokenlaydi: --duration-fast: 120ms, --ease-out: cubic-bezier(…). Butun ilova bir xil "ritmda" harakat qiladi. Material Design va Apple'ning Human Interface Guidelines'ida "Motion" bo'limlari bor.
  • Performans ko'rigi: DevTools Performance panelidagi qizil kadrlar va "Layout" yozuvlari — animatsiya xususiyatlarini tekshirish uchun birinchi joy (DevTools darsida).
  • A11y talablari: WCAG'da harakat va animatsiya bo'yicha mezonlar bor (masalan 2.3.3 — o'zaro ta'sirdan kelib chiqqan animatsiyani o'chirish imkoniyati). Davlat va bank loyihalarida a11y auditi buni tekshiradi.
  • Intervyu savollari: "Nega transform animatsiyasi left dan tezroq?", "will-change qachon foydali, qachon zararli?", "spring va tween farqi?", "prefers-reduced-motion ni React'da qanday o'qiysiz?".

Xulosa

  • Animatsiya to'rt ishdan birini qilsin: holat o'zgarishi, fazoviy bog'lanish, javob, e'tibor. Aks holda — yo'q.
  • 150–300 ms; kirish — ease-out, chiqish — ease-in va qisqaroq; ko'p takrorlanadigan — qisqaroq.
  • Tween — davomiylik + egri chiziq; spring — fizika, to'xtatilganda tezlikni saqlaydi.
  • Faqat transform va opacity — kompozitorda; will-change — o'lchangan joyda; o'qish va yozishni aralashtirmang (FLIP).
  • React'da animatsiya qiymati state'da emas — ref yoki kutubxona ichida; animatsiya ham INP'ga ta'sir qiladi.
  • prefers-reduced-motion: harakatni olib tashlang, ma'noni (so'nishni) qoldiring.

Keyingi dars: CSS animatsiyalari React'da — transition va keyframes React komponentlarida, Radix data-state, @starting-style va allow-discrete bilan kirish va chiqish; vazifalar ning tasdiq oynasi va menyulari jonlanadi.

Manbalar

  • web.dev: Animations guide, "How to create high-performance CSS animations" — web.dev/articles/animations-guide
  • MDN: will-change, prefers-reduced-motion, translate — developer.mozilla.org
  • web.dev: Interaction to Next Paint (INP) — web.dev/articles/inp
  • WCAG 2.2: 2.3.3 Animation from Interactions — w3.org/WAI/WCAG22/Understanding/animation-from-interactions
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
UI animatsiya tamoyillari: davomiylik, easing, spring va 60 fps — React'da tez va ma'noli harakat — IlmHamroh