Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Animatsiya nima uchun kerak
- 2.1 To'rt vazifa
- 2.2 Bitta qoida
- 3. Davomiylik va easing
- 3.1 Qancha davom etsin
- 3.2 Easing: tezlik qanday o'zgaradi
- 3.3 Tween va spring
- 4. Brauzer kadrni qanday chizadi
- 4.1 60 fps va 16.7 ms
- 4.2 will-change
- 4.3 Layout thrashing animatsiyada
- 5. React'da animatsiya narxi
- 5.1 Har kadrda render
- 5.2 Ref bilan: React'ni chetlab o'tish
- 5.3 O'lchov: animatsiya ham INP'ga ta'sir qiladi
- 6. prefers-reduced-motion
- 6.1 "Kamaytirish" — "o'chirish" emas
- 6.2 React'da o'qish
- 7. Vazifalar qadami: animatsiya bosqichi boshlanadi (holat 22)
- 7.1 Audit: hozir nima bor
- 7.2 Toast: pastdan chiqsin — faqat kompozitorda
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): arzonmi, qimmatmi?
- 2-mashq (o'rta): progress chizig'i render'siz
- 3-mashq (qiyin): harakat o'rniga so'nish
- 4-mashq: Vazifalar qadami
- 10. Real ishda
- Xulosa
- Manbalar
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, chiquvchigaease-in. Arzon animatsiya faqattransformvaopacityda: brauzer ularni layout va bo'yashsiz, kompozitorda bajaradi. React'da animatsiya qiymatini har kadrda state'ga yozmang — har kadr render bo'ladi. Va har animatsiyaningprefers-reduced-motiondagi 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
transformvaopacityarzon,widthvatopesa 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-motionni React'da o'qiysiz vavazifalarning 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:
- 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".
- Fazoviy bog'lanish. Menyu tugmadan "o'sib" chiqadi — u qayerdan kelganini ko'rasiz. Drawer pastdan chiqadi — pastga surib yopish mumkinligini his qilasiz.
- Javob (feedback). Tugma bosilganda bir oz kichrayadi — "bosish qabul qilindi".
- 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
transitionvaanimation— 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):
// 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:
tween: 0 42 70 88 96 100 100 100 100 100 100
spring: 0 27 72 103 114 112 105 100 98 98 99Xuddi shu raqamlar grafikda:
- tween
- spring
| vaqt | tween | spring |
|---|---|---|
| 0 | 0 | |
| 50 | 42 | |
| 100 | 70 | |
| 150 | 88 | |
| 200 | 96 | |
| 250 | 100 | |
| 300 | 100 | |
| 350 | 100 | |
| 400 | 100 | |
| 450 | 100 | |
| 500 | 100 | |
| 0 | 0 | |
| 50 | 27 | |
| 100 | 72 | |
| 150 | 103 | |
| 200 | 114 | |
| 250 | 112 | |
| 300 | 105 | |
| 350 | 100 | |
| 400 | 98 | |
| 450 | 98 | |
| 500 | 99 |
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"] -.-> CNuqtali 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:
.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:
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:
kadr: 10 render: 1110 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:
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:
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:
- 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:
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:
harakatni kamaytirish: falseTizimingizda "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):
/* 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: noneqo'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:
git switch -c feat/css-animatsiyaHolat 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):
- transition: opacity 0.2s;
+ /* 20/#22: + translate — pastdan chiqadi (transform oilasi,
+ kompozitor) */
+ transition: opacity 0.2s, translate 0.2s;-.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:
✅ 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
transformvaopacity— 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:
- State yo'q — ref orqali DOM'ga yozing (render bitta bo'lsin).
widthemas,transform: scaleX(...)— chiziq chapdan o'ngga o'sishi uchuntransformOrigin: "left".- Oxirida konsolga render soni va yakuniy
transformchiqsin.
Ishora: «Ref bilan: React'ni chetlab o'tish» bo'limi. scaleX(1) — to'liq, scaleX(0.5) — yarmi.
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:
render: 11Yechim
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:
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.
usePrefersReducedMotionhook'idan foydalaning («React'da o'qish» bo'limi).- Animatsiyani
@keyframesbilan qiling: ikki xil nom —kirish-siljibvakirish-sonib. - Kartochka ostida qaysi animatsiya tanlangani yozilsin.
Ishora: style={{ animation: reduce ? "…" : "…" }}. <style> matnida ikkala @keyframes.
Yechim
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:
Yangi buyurtma: osh, 35 000 so'm
Animatsiya: kirish-siljib 300ms ease-outTizimda "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'datransition-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
transformanimatsiyasileftdan tezroq?", "will-changeqachon foydali, qachon zararli?", "spring va tween farqi?", "prefers-reduced-motionni 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-inva qisqaroq; ko'p takrorlanadigan — qisqaroq. - Tween — davomiylik + egri chiziq; spring — fizika, to'xtatilganda tezlikni saqlaydi.
- Faqat
transformvaopacity— 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
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!