Mundarija (36)
- Bu darsda
- 1. Nega bu kerak?
- 2. exit va AnimatePresence
- 2.1 React elementni kutmaydi
- 2.2 AnimatePresence — o'chirishni kechiktiradi
- 2.3 Uchta qoida
- 3. Bitta joyda almashinuv: key va mode
- 3.1 key o'zgarsa — eski chiqadi, yangisi kiradi
- 3.2 mode="wait" — navbat bilan
- 3.3 mode="popLayout" — chiquvchini oqimdan "sug'urib olish"
- 4. Chiqishni o'zingiz boshqarish: usePresence
- 5. Layout animatsiyasi: layout prop
- 5.1 FLIP: o'lcha, siljit, qaytar
- 5.2 Birinchi layout
- 5.3 Cho'zilish va layout="position"
- 5.4 layoutId: bir element boshqa joyga "uchadi"
- 5.5 LayoutGroup: qo'shni komponentlar bir-birini bilsin
- 5.6 Narxi: layout — og'ir imkoniyat
- 6. Ro'yxatda AnimatePresence: kanon nega rad etdi
- 6.1 Birinchi variant va o'lchov
- 6.2 Muammoning ildizi
- 6.3 Kanon yechimi: chiqish — faqat o'chirishda, o'chirishdan oldin
- 7. Vazifalar qadami: o'chirishda chiqish (holat 25 davomi)
- 7.1 Nima qo'shiladi
- 7.2 chiqishAnimatsiyasi
- 7.3 App.tsx — avval chiqish, keyin o'chirish
- 7.4 Kanon tekshiruvlari (holat 25)
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): qaysi rejim?
- 2-mashq (o'rta): "Saqlandi" xabari chiqib ketmayapti
- 3-mashq (qiyin): o'chirishdan oldin chiqish — Motion bilan
- 4-mashq: Vazifalar qadami
- 10. Real ishda
- Xulosa
- Manbalar
Motion'da chiqish va layout animatsiyalari: AnimatePresence, exit, layout va layoutId
Qisqacha: React o'chirilgan komponentning elementini DOM'dan darhol olib tashlaydi. Shuning uchun
exitanimatsiyasiga vaqt qolmaydi.AnimatePresenceelementni chiqish animatsiyasi tugaguncha ushlab turadi.mode="wait"da avval eski element chiqadi, keyin yangisi kiradi.layoutprop joylashuv o'zgarishinitransformbilan silliq qiladi,layoutIdesa elementni boshqa joyga "uchiradi". Ehtiyot bo'ling: chiqayotgan element state'da yo'q, DOM'da bor.vazifalarda u filtr va testlarni buzdi. Shuning uchun kanonda chiqish animatsiyasi o'chirishdan oldin bajariladi.
Bu darsda
exitnegaAnimatePresencesiz ishlamasligini va u qanday "kechiktirib o'chirish"ini konsol bilan ko'rasiz.keyalmashganda eski va yangi element navbatinimode(sync,wait,popLayout) bilan boshqarasiz.usePresencebilan chiqishni o'zingiz boshqarasiz — Motion animatsiyasisiz ham.layout,layoutIdvaLayoutGroupbilan joylashuv o'zgarishini silliq qilasiz va FLIP g'oyasini tushuntira olasiz.- Kanon o'lchovi bilan ro'yxatdagi
AnimatePresencening xavfini ko'rasiz vavazifalarga o'chirishdagi chiqish animatsiyasini qo'shasiz (holat 25 davomi).
Oldin bilishingiz kerak: Motion II: variants, stagger va gestures, Motion I: motion komponentlar, CSS animatsiyalari React'da, Web Animations API, Ro'yxatlarni render qilish va key.
1. Nega bu kerak?
Oldingi ikki darsda elementlarni ekranga kiritdik: initial → animate, variantlar, ketma-ketlik. Endi teskari savol. Jasur aka vazifalar ni sinab ko'rdi: yangi vazifa yumshoq paydo bo'ladi, o'chirilgani esa "pat" etib yo'qoladi. "Kirishi chiroyli-yu, chiqishi kesib tashlangandek", dedi u.
Sababi React'da. State o'zgardi, komponent endi chizilmaydi — React uning DOM elementini shu commit'dayoq olib tashlaydi. Animatsiya qilish uchun element kerak, u esa allaqachon yo'q.
Hayotiy o'xshatish: teatr. Aktyor rolini tugatdi, rejissyor esa pardani darhol tushirib, sahnani bo'shatib qo'ydi — aktyor ta'zim qilishga ulgurmadi. AnimatePresence — sahna ortidagi yordamchi: "parda tushsin, lekin aktyor ta'zimini tugatsin, keyin u sahnadan chiqadi".
Ikkinchi muammo — joylashuv. Ro'yxatdan qator o'chsa, pastdagilar bir zumda yuqoriga "sakraydi". Karta kengaysa, qo'shnilari ham sakraydi. Ko'z bu sakrashni kuzatolmaydi va "nima bo'ldi?" deb qoladi. layout prop shu sakrashni silliq harakatga aylantiradi. Oxirida esa eng muhim savol: chiroyli chiqish ro'yxatni buzmaydimi? vazifalar kanonida buzdi — o'lchov bilan ko'ramiz.
2. exit va AnimatePresence
2.1 React elementni kutmaydi
Avval exit ni AnimatePresence siz yozib ko'ramiz. Karta useEffect bilan DOM'ga qo'shilgan va olib tashlangan paytini yozadi (useEffect I darsidagi tozalash funksiyasi):
import { useEffect, useState } from "react";
import { motion } from "motion/react";
function Card() {
useEffect(() => {
console.log("karta DOM'da");
return () => console.log("karta olib tashlandi");
}, []);
return (
<motion.p
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
onAnimationComplete={(d) => console.log("tugadi:", d)}
>
Osh — 35 000 so'm
</motion.p>
);
}
export default function App() {
const [show, setShow] = useState(true);
return (
<div>
<button onClick={() => setShow(false)}>Yashirish</button>
{show && <Card />}
</div>
);
}Konsolda:
karta DOM'da
karta olib tashlandiEkranda:
Yashirishtugadi: qatori yo'q — chiqish animatsiyasi umuman boshlanmadi. exit prop'i yozilgan, lekin uni ishga tushiradigan hech kim yo'q: React Card ni ro'yxatdan chiqardi va DOM'dan o'chirdi. Xato ham, ogohlantirish ham chiqmaydi — exit jim e'tiborsiz qoldi.
2.2 AnimatePresence — o'chirishni kechiktiradi
Endi o'sha kartani AnimatePresence ichiga qo'yamiz. Bitta shart: bola key ga ega bo'lsin — AnimatePresence bolalarni shu kalit bo'yicha taniydi (Ro'yxatlar va key darsidagi o'sha key):
import { useEffect, useState } from "react";
import { AnimatePresence, motion } from "motion/react";
function Card() {
useEffect(() => {
console.log("karta DOM'da");
return () => console.log("karta olib tashlandi");
}, []);
return (
<motion.p
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
onAnimationComplete={(d) => console.log("tugadi:", d)}
>
Osh — 35 000 so'm
</motion.p>
);
}
export default function App() {
const [show, setShow] = useState(true);
return (
<div>
<button onClick={() => setShow(false)}>Yashirish</button>
<AnimatePresence
onExitComplete={() => console.log("onExitComplete")}
>
{show && <Card key="karta" />}
</AnimatePresence>
</div>
);
}Konsolda:
karta DOM'da
tugadi: {opacity: 0}
onExitComplete
karta olib tashlandiEkranda:
YashirishTartibni o'qiymiz:
- «Yashirish» bosildi —
showendifalse, ReactAppni qayta chizdi vaCardni bolalar ro'yxatida topmadi. AnimatePresencebuni sezdi va eski elementni (o'shakey="karta") chizishda davom etdi. Ichidaexitanimatsiyasini boshladi.- 300 ms o'tdi —
tugadi: {opacity: 0}. Animatsiyaexitdagi qiymatga yetdi. onExitComplete— barcha chiquvchilar tugadi.- Shundan keyingina React kartani haqiqatan olib tashladi: effekt tozalanishi oxirida ishladi.
sequenceDiagram
participant U as Foydalanuvchi
participant R as React
participant P as AnimatePresence
participant D as DOM
U->>R: setShow(false)
R->>P: yangi bolalar: karta yo'q
P->>D: eski kartani qoldiradi, exit boshlanadi
D-->>P: 300 ms — animatsiya tugadi
P->>R: endi olib tashla
R->>D: karta o'chdi, effekt tozalandiDiagrammadagi asosiy narsa — o'rtadagi qadam: state allaqachon "karta yo'q" deydi, DOM'da esa karta hali bor. Bu farq butun dars davomida qaytib keladi.
2.3 Uchta qoida
AnimatePresence faqat shu shartlarda ishlaydi:
- O'zi doim chizilgan bo'lsin. Shart uning ichida turadi:
<AnimatePresence>{show && …}</AnimatePresence>. Teskarisi —{show && <AnimatePresence>…</AnimatePresence>}—AnimatePresencening o'zini o'chiradi va u hech narsani ushlab tura olmaydi. - To'g'ridan-to'g'ri bolalarda
key. Shartli bitta bola uchun ham. Ro'yxatda — har elementning barqaror id'si (indeks emas). exitbola ichida bo'lsin.exitistalgan chuqurlikdagimotionkomponentida yozilishi mumkin —AnimatePresencehammasini kutadi. Bizda uCardichidagimotion.pda.
Tekshirib ko'ring: Sardor shunday yozdi:
{open && <AnimatePresence><motion.div key="m" exit={{ opacity: 0 }} /></AnimatePresence>}. Menyu yopilganda chiqish animatsiyasi bormi?
Javob
Yo'q. open false bo'lganda React AnimatePresence ning o'zini o'chiradi — bolasini ushlab turadigan komponent qolmaydi. Konsol jim, xuddi «React elementni kutmaydi» bo'limidagidek. To'g'risi — <AnimatePresence>{open && <motion.div key="m" … />}</AnimatePresence>: AnimatePresence doim joyida, faqat bolasi kelib-ketadi.
3. Bitta joyda almashinuv: key va mode
3.1 key o'zgarsa — eski chiqadi, yangisi kiradi
«Bahor» kassasidagi "Kun taomi" kartasi: bugun osh, «Keyingi» bosilsa — lag'mon. Komponent bitta, faqat key o'zgaradi. State'ning saqlanishi va key darsidan bilasiz: yangi key — React uchun boshqa komponent. Demak eski karta "o'chdi", yangisi "qo'shildi" — AnimatePresence uchun bu chiqish + kirish.
Har karta animatsiya boshlanishi va tugashini yozadi. initial={false} — sahifa ochilgandagi birinchi karta kirish animatsiyasisiz chiqsin:
import { useState } from "react";
import { AnimatePresence, motion } from "motion/react";
const DISHES = ["Osh", "Lag'mon"];
function Dish({ name }) {
const what = (d) => (d.opacity === 0 ? "chiqish" : "kirish");
return (
<motion.p
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
onAnimationStart={(d) => console.log(name, what(d), "...")}
onAnimationComplete={(d) => console.log(name, what(d), "✓")}
>
{name}
</motion.p>
);
}
export default function App() {
const [i, setI] = useState(0);
return (
<div>
<button onClick={() => setI(1)}>Keyingi</button>
<AnimatePresence initial={false}>
<Dish key={DISHES[i]} name={DISHES[i]} />
</AnimatePresence>
</div>
);
}Konsolda:
Osh chiqish ...
Lag'mon kirish ...
Osh chiqish ✓
Lag'mon kirish ✓Ekranda:
Keyingi
Lag'monStandart rejim — sync: chiqish va kirish bir vaqtda. 200 ms davomida ekranda ikkala karta bor: osh so'nmoqda, lag'mon paydo bo'lmoqda. Ular ustma-ust emas, ketma-ket turadi — lag'mon oshning ostida paydo bo'ladi va osh ketgach yuqoriga sakraydi.
3.2 mode="wait" — navbat bilan
Sakrashni yo'qotishning birinchi yo'li — kutish: avval eski chiqib bo'lsin, keyin yangisi kirsin. O'sha komponent, faqat mode:
export default function App() {
const [i, setI] = useState(0);
return (
<div>
<button onClick={() => setI(1)}>Keyingi</button>
<AnimatePresence initial={false} mode="wait">
<Dish key={DISHES[i]} name={DISHES[i]} />
</AnimatePresence>
</div>
);
}Konsolda:
Osh chiqish ...
Osh chiqish ✓
Lag'mon kirish ...
Lag'mon kirish ✓Ekranda:
Keyingi
Lag'monEndi lag'mon osh chiqib bo'lgandan keyin boshlandi. Ekranda bir paytda faqat bitta karta — sakrash yo'q. Narxi — vaqt: almashinuv 200 emas, 400 ms davom etdi. Slayder, bosqichli forma ("1-qadam → 2-qadam"), sahifa sarlavhasi — wait uchun tabiiy joylar.
Diqqat:
mode="wait"bir paytda faqat bitta bola bilan ishlaydi. Unga ro'yxat bersangiz, Motion konsolga ogohlantirish yozadi va xulq kutilmagan bo'ladi. Ro'yxat —syncyokipopLayout.
3.3 mode="popLayout" — chiquvchini oqimdan "sug'urib olish"
Uchinchi rejim boshqacha yo'l tanlaydi: chiqayotgan elementni position: absolute qilib, sahifa oqimidan chiqarib oladi. U o'z joyida so'nib ketadi, qo'shnilar esa darhol yangi joyiga o'tadi. layout bilan birga (keyingi bo'limda) qo'shnilar silliq siljiydi.
<AnimatePresence mode="popLayout">
{items.map((item) => (
<motion.li key={item.id} layout exit={{ opacity: 0 }}>
{item.text}
</motion.li>
))}
</AnimatePresence>Ikki shart bor. Birinchisi: bola o'z komponentingiz bo'lsa, u ref ni DOM elementga o'tkazishi kerak — Motion elementni o'lchaydi (useRef II). Ikkinchisi — position: absolute ni Motion qanday qo'yadi? Motion 14.0.0 manba kodida (PopChild): <style> tegini yaratib, <head> ga qo'shadi.
Bu tanish tuzoq. Radix Primitives chuqur darsida modal menyu ham <style> qo'shgan va vazifalar CSP'si (style-src 'self') uni to'sgan edi. Biz popLayout ni ham o'sha CSP ostida Chrome 155 da sinab ko'rdik (alohida tajriba sahifasi, uchta qator, birinchisi o'chiriladi):
pop sync (200 ms): ["Osh:static:16","Lag'mon:static:34","Manti:static:52"] style teglari: 0
pop popLayout (200 ms): ["Osh:static:16","Lag'mon:static:34","Manti:static:52"] style teglari: 1
{"csp":["style-src-elem inline"]}Har qator — matn:position:tepadan_px. popLayout da <style> qo'shildi, CSP uni to'sdi — chiqayotgan «Osh» static qoldi, qo'shnilar joyidan qimirlamadi. Ya'ni qattiq CSP ostida popLayout jim sync ga aylanadi, konsolda esa Applying inline style violates … 'style-src 'self'' xatosi. Saytdagi natija oynasi inline <style> ga ruxsat beradi — u yerda ishlaydi. Motion'da MotionConfig ning nonce sozlamasi bor, lekin u faqat CSP'ingiz nonce bilan qurilgan bo'lsa yordam beradi.
| Rejim | Eski va yangi | Qachon |
|---|---|---|
sync (standart) |
bir vaqtda, ikkalasi oqimda | ro'yxatlar, mustaqil elementlar |
wait |
avval chiqish, keyin kirish | bitta element almashinuvi: slayd, qadam |
popLayout |
chiquvchi oqimdan chiqadi | layout li ro'yxat; CSP <style> ga ruxsat bersa |
Tekshirib ko'ring: Bosqichli bron formasida «Keyingi» bosilganda 1-qadam chapga chiqib, 2-qadam o'ngdan kirishi kerak. Ikki qadam bir paytda ko'rinmasin. Qaysi rejim?
Javob
mode="wait". Bola bitta — joriy qadam, uning key i — qadam raqami. wait avval 1-qadamning exit ini tugatadi. Keyin 2-qadamning kirishini boshlaydi — ikkalasi bir paytda ekranda bo'lmaydi. sync da esa 200 ms davomida ikkala forma birga turadi va sahifa balandligi sakraydi.
4. Chiqishni o'zingiz boshqarish: usePresence
exit prop'i Motion animatsiyasi uchun. Lekin chiqish paytida boshqa ish kerak bo'lsa-chi — CSS animatsiyasi, Web Animations API, "saqlanmoqda" xabari? usePresence hooki komponentga ikki narsa beradi:
isPresent—falsebo'lsa, siz allaqachon "o'chirilgansiz", lekinAnimatePresencesizni ushlab turibdi.safeToRemove— "ishim tugadi, endi olib tashlang" signali.
import { useEffect, useState } from "react";
import { AnimatePresence, usePresence } from "motion/react";
function Card() {
const [isPresent, safeToRemove] = usePresence();
useEffect(() => {
if (isPresent) return;
console.log("chiqish boshlandi");
const timer = setTimeout(() => {
console.log("endi olib tashlash mumkin");
safeToRemove();
}, 200);
return () => clearTimeout(timer);
}, [isPresent, safeToRemove]);
return <p>{isPresent ? "Karta" : "Karta (chiqmoqda)"}</p>;
}
export default function App() {
const [show, setShow] = useState(true);
return (
<div>
<button onClick={() => setShow(false)}>Yashirish</button>
<AnimatePresence>{show && <Card key="k" />}</AnimatePresence>
</div>
);
}Konsolda:
chiqish boshlandi
endi olib tashlash mumkinEkranda:
Yashirishekran("chiqmoqda") oraliq holatni tekshiradi: bosishdan keyin karta hali DOM'da va o'z matnini o'zgartirgan. 200 ms dan keyin safeToRemove() chaqirildi — karta ketdi.
Faqat "chiqyapmanmi?" ni bilish kerak bo'lsa (masalan, chiqayotgan formadagi tugmani o'chirib qo'yish) — useIsPresent() yetadi: u faqat isPresent ni qaytaradi.
Diqqat:
safeToRemoveni chaqirishni unutsangiz, element DOM'da abadiy qoladi. Shuning uchun taymer yokiPromise.racebilan "ko'pi bilan N ms" chegarasini qo'ying — «Vazifalar qadami» da kanon aynan shunday qiladi.
5. Layout animatsiyasi: layout prop
5.1 FLIP: o'lcha, siljit, qaytar
Layout animatsiyasining muammosi: width, height, top ni animatsiya qilish — har kadrda brauzer layout ni qayta hisoblaydi. UI animatsiya tamoyillari va Rendering pipeline darslaridan bilasiz: bu qimmat, telefonda kadrlar tushib qoladi. Arzoni — transform va opacity.
Motion layout prop bilan FLIP usulini qo'llaydi. Nomi to'rt qadamning bosh harflari:
flowchart LR
F["First: eski o'rin va o'lcham"] --> L["Last: React yangi holatni chizdi"]
L --> I["Invert: transform bilan eski joyga qaytar"]
I --> P["Play: transform → none"]- First — o'zgarishdan oldin element o'lchanadi (
getBoundingClientRect). - Last — React yangi holatni chizadi; element darhol yangi joyda. Yana o'lchanadi.
- Invert — farq hisoblanadi va
transform: translate(…) scale(…)bilan element ko'zga eski joyida ko'rinadi. - Play —
transformasta-sekinnonega qaytadi. Ko'z silliq siljishni ko'radi.
Hiyla shundaki, layout bir marta hisoblanadi (Last), animatsiyaning o'zi esa faqat transform — kompozitorda.
5.2 Birinchi layout
«Bahor» menyusidagi karta bosilsa kengayib, tarkibini ko'rsatadi:
import { useState } from "react";
import { motion } from "motion/react";
const box = {
border: "2px solid #1d4ed8",
borderRadius: 12,
padding: 12,
width: 140,
background: "#fff",
color: "#111",
};
export default function App() {
const [open, setOpen] = useState(false);
return (
<motion.button
layout
onClick={() => setOpen(!open)}
style={{ ...box, width: open ? 260 : 140 }}
transition={{ duration: 0.3 }}
>
<motion.b layout="position">Osh</motion.b>
{open && <p>Guruch, sabzi, go'sht — 35 000 so'm</p>}
</motion.button>
);
}Ekranda:
Osh
Guruch, sabzi, go'sht — 35 000 so'm«Ishga tushir» dan keyin kartani bir necha marta bosing: u siljib kengayadi va torayadi. layout bo'lmasa — bir zumda sakraydi. Tekshiruvchi bu yerda faqat yakuniy matnni solishtiradi: jsdom'da layout o'lchab bo'lmaydi (hamma o'lcham 0), animatsiya faqat haqiqiy brauzerda ko'rinadi.
5.3 Cho'zilish va layout="position"
scale bilan o'lcham o'zgartirilganda bolalar ham cho'ziladi: matn bir lahza "ezilgan" ko'rinadi. Motion buni ikki yo'l bilan tuzatadi:
border-radiusvabox-shadowni avtomatik to'g'rilaydi — burchaklar cho'zilmaydi (shuning uchun ularstyleda bo'lsin, sinfda emas).- Bola ham
layoutolsa, Motion uning teskari masshtabini hisoblaydi. Yuqoridagi<motion.b layout="position">— shu: sarlavha faqat o'rnini animatsiya qiladi, o'lchami cho'zilmaydi.
layout ning qiymatlari: true (o'rin va o'lcham), "position" (faqat o'rin), "size" (faqat o'lcham).
5.4 layoutId: bir element boshqa joyga "uchadi"
Ikki turli element bir xil layoutId olsa, Motion ularni bitta deb biladi: biri yo'qolib, ikkinchisi paydo bo'lganda, o'tish eski joydan yangi joyga uchish bo'ladi. Klassik misol — tablar ostidagi chiziq:
import { Fragment, useState } from "react";
import { motion } from "motion/react";
const TABS = ["Osh", "Lag'mon", "Manti"];
const line = {
display: "block",
height: 3,
background: "#1d4ed8",
};
export default function App() {
const [tab, setTab] = useState("Osh");
return (
<div>
{TABS.map((t) => (
<Fragment key={t}>
<button
aria-pressed={tab === t}
onClick={() => setTab(t)}
style={{ padding: 8 }}
>
{t}
{tab === t && (
<motion.span layoutId="tab-chiziq" style={line} />
)}
</button>{" "}
</Fragment>
))}
<p>Tanlangan: {tab}</p>
</div>
);
}Ekranda:
Osh Lag'mon Manti
Tanlangan: MantiHar tugmada chiziq o'zining span i: «Manti» bosilganda oshdagi span o'chadi, mantidagi chiziladi. layoutId ularni bog'laydi — chiziq osh ostidan manti ostiga suriladi. Kartadan to'liq sahifaga o'tuvchi rasm (galereya), savatchaga "uchadigan" mahsulot — hammasi shu g'oya.
aria-pressed ni unutmang: chiziq — faqat ko'rinish. Malika (ekran o'quvchi) qaysi tab tanlanganini atributdan biladi, chiziqdan emas.
5.5 LayoutGroup: qo'shni komponentlar bir-birini bilsin
layout faqat o'zi qayta chizilgan komponentni o'lchaydi. Akkordeonda bitta band ochilsa, boshqa komponentdagi pastki band siljiydi — lekin u qayta chizilmadi, o'zgarishni bilmaydi va sakraydi. Ularni LayoutGroup bilan o'rang: guruhdagi birortasi o'zgarsa, hammasi o'lchanadi.
import { useState } from "react";
import { LayoutGroup, motion } from "motion/react";
function Item({ title, text }) {
const [open, setOpen] = useState(false);
return (
<motion.div layout style={{ borderBottom: "1px solid #999" }}>
<motion.button layout="position" onClick={() => setOpen(!open)}>
{title}
</motion.button>
{open && <p>{text}</p>}
</motion.div>
);
}
export default function App() {
return (
<LayoutGroup>
<Item title="Osh" text="35 000 so'm, har kuni" />
<Item title="Manti" text="30 000 so'm, juma kuni" />
</LayoutGroup>
);
}Ekranda:
Osh
35 000 so'm, har kuni
MantiLayoutGroup ning ikkinchi vazifasi — nomlar fazosi. Sahifada ikkita tab paneli bo'lsa va ikkalasida layoutId="tab-chiziq" bo'lsa, chiziq bir paneldan boshqasiga uchib ketadi. <LayoutGroup id="menyu"> va <LayoutGroup id="buyurtma"> — ichidagi layoutId lar endi alohida.
5.6 Narxi: layout — og'ir imkoniyat
layout uchun Motion o'lchash va "proyeksiya" kodini yuklaydi. Keyingi darsda ko'radigan LazyMotion da bu farq aniq: kichik domAnimation to'plamida layout yo'q, u faqat katta domMax da. Kanon tajribasida (tajriba/kutubxonalar.txt, gzip): to'liq motion (layout bilan) — +40.4 KB, LazyMotion + domAnimation — +27.1 KB. vazifalar da layout ishlatilmadi: qo'shimcha sababi — «Tartiblash» rejimidagi dnd-kit ham transform ni boshqaradi, ikki kutubxona bitta xususiyatda to'qnashadi (TEXNIK-QARZ 57).
Harakatni kamaytirish: <MotionConfig reducedMotion="user"> (Motion I) layout va layoutId siljishlarini ham o'chiradi — element darhol yangi joyida bo'ladi. exit dagi opacity esa qoladi: chiqayotgan narsa joyida so'nadi.
Tekshirib ko'ring: Karta kengayganda uning ichidagi matn bir lahza cho'zilib, keyin to'g'rilanyapti. Nima qilasiz?
Javob
Matnni o'rab turgan elementga ham layout (ko'pincha layout="position") bering. Ota scale bilan cho'zilganda, Motion layout li bolaga teskari masshtab qo'llaydi va matn o'z o'lchamida qoladi. Burchak yumaloqligi cho'zilsa — borderRadius ni sinf orqali emas, style orqali bering: Motion uni faqat shunda to'g'rilay oladi.
6. Ro'yxatda AnimatePresence: kanon nega rad etdi
6.1 Birinchi variant va o'lchov
Eng tabiiy yechim — ro'yxatni AnimatePresence ga o'rash: har li ga exit, o'chirilgani so'nib ketadi. Kanon holat 25 ni avval aynan shunday yozdi (exit 150 ms) va brauzer testlarini ishga tushirdi. Natija (natijalar/25-animatepresence.txt, Chrome 155, qisqartirilgan):
## e2e.mjs (159): ❌ 7 ta (hammasi — exit paytida DOM'da qolgan qatorlar: filtr, qidiruv)
❌ Orqaga → faol: ["3:Onamga qo'ng'iroq:-","4:Sabzi olish #bozor:-","1:Non olish:x"] (kutilgan ["3:Onamga qo'ng'iroq:-","4:Sabzi olish #bozor:-"])
❌ qidiruv: topilmadi — bosh xabar: [9,false,"«zzz» bo'yicha vazifa topilmadi.","0 ta topildi"] (kutilgan [0,false,"«zzz» bo'yicha vazifa topilmadi.","0 ta topildi"])
## e2e-18.mjs: forma rad etildi — exit
❌ forma: rad etildi — vazifa yo'q, matn maydonda qoldi (useActionState): [true,"Yo'qolmaydigan matn"] (kutilgan [false,"Yo'qolmaydigan matn"])
## e2e-19.mjs:508 — p fon tabda (p2 ochilgan): 'so'ramasdan o'chirish' — li[data-id=1] 10 s da DOM'dan ketmadi (exit animatsiyasi rAF kutadi)Qatorlarni o'qiymiz:
- «Orqaga → faol» — "faol" filtriga qaytildi. Bajarilgan «Non olish» (
x) ro'yxatdan chiqishi kerak edi. U chiqib ketyapti — DOM'da hali bor. Test ro'yxatni shu paytda o'qidi va uchta qator ko'rdi. - «qidiruv: topilmadi» — «zzz» bo'yicha hech narsa topilmadi, xabar to'g'ri ("0 ta topildi"). Lekin DOM'da 9 ta
li— hammasi chiqib ketyapti. Ekranda bir lahza "topilmadi" xabari va 9 ta so'nayotgan qator birga turadi. - «forma: rad etildi» — server qo'shishni rad etdi, optimistik qator qaytarib olindi. U ham chiqish animatsiyasi bilan "ketdi" — test uni hali bor deb ko'rdi.
- Fon tab — eng og'iri. Ikkinchi tabda vazifa o'chirildi, birinchi tab fonda. Motion animatsiyasi kadrlar bilan yuradi (
requestAnimationFrame), fon tabda esa brauzer kadrlarni to'xtatadi. O'chirilgan qator 10 soniyada ham DOM'dan ketmadi.
6.2 Muammoning ildizi
«AnimatePresence — o'chirishni kechiktiradi» bo'limidagi diagrammani eslang: state "yo'q" deydi, DOM esa "bor". Bitta kartada bu zararsiz. Ro'yxatda esa bu farqni hamma ko'radi:
- Testlar — DOM'ni o'qiydi va eski qatorni sanaydi.
- Ekran o'quvchi — Malika "o'chirilgan" vazifani hali o'qishi mumkin; chiqayotgan qatordagi tugma fokus ham olishi mumkin.
- Foydalanuvchi — filtr yoki qidiruvda har harfdan keyin o'nlab qatorlar so'nib ketadi. Bu "tez ishlaydigan" filtrni sekin ko'rsatadi.
- Fon tab — animatsiya tugamasa, element hech qachon ketmaydi.
Testlarni "animatsiyani kutadigan" qilib o'zgartirish mumkin edi. Lekin testlar shu yerda haqiqiy muammoni ko'rsatdi: ilova holati va ekran bir-biriga mos emas. Qoida: animatsiya — bezak, DOM holati darhol to'g'ri bo'lsin.
6.3 Kanon yechimi: chiqish — faqat o'chirishda, o'chirishdan oldin
Ro'yxatdan qator uch xil sabab bilan ketadi: foydalanuvchi o'chirdi, filtr/qidiruv uni yashirdi, server rad etdi. Chiqish animatsiyasi faqat birinchisiga kerak — odam o'zi bosgan narsaning natijasini ko'rishi kerak. Qolganlari darhol yo'qolsin.
Shuning uchun tartib teskari: avval animatsiya, keyin state.
flowchart LR
B["O'chirish bosildi"] --> A["li.animate: 150 ms"]
A --> R{"tugadi yoki 300 ms"}
R --> S["state'dan o'chirish"]
S --> D["React qatorni olib tashlaydi"]Animatsiya paytida qator haqiqatan hali ro'yxatda — state ham, DOM ham "bor" deydi, ziddiyat yo'q. Animatsiya tugagach state o'zgaradi va React qatorni odatdagidek darhol olib tashlaydi. Fon tab uchun — Promise.race: animatsiya 300 ms da tugamasa ham, o'chirish baribir bajariladi.
Animatsiya uchun Motion emas, brauzerning o'zidagi Web Animations API (element.animate). Sababi hajm: Motion'ning useAnimate i animatsiya yadrosini birinchi yuklashga qaytarardi. Kanon esa uni alohida, kechroq yuklaydi — bu keyingi darsning mavzusi.
7. Vazifalar qadami: o'chirishda chiqish (holat 25 davomi)
7.1 Nima qo'shiladi
Motion I va Motion II darslarida feat/motion branch'ida yangi qatorning kirishi tayyor bo'ldi: useQatorHarakati, m.li, HarakatSozlamasi, "yangi" qatorlar to'plami. Bugun ikkinchi yarmi — chiqish: foydalanuvchi o'chirgan qator o'ngga siljib so'nadi (150 ms, WAAPI), keyin ro'yxatdan o'chadi. Filtr, qidiruv va server rad etishida esa qator darhol yo'qoladi — «Kanon yechimi» bo'limidagi qoida.
O'zgaradigan fayllar ikkita: src/ui/qator-harakati.ts (yangi funksiya) va App.tsx (o'chirish tartibi).
7.2 chiqishAnimatsiyasi
src/ui/qator-harakati.ts ning kirish qismi Motion I da yozilgan. Endi faylga ikkinchi funksiya qo'shiladi:
/**
* O'chirilayotgan qator — avval ko'rinishdan chiqadi, keyin
* ro'yxatdan. Brauzerning o'z Web Animations API'si (Motion'ning
* useAnimate'i butun animatsiya yadrosini birinchi yuklashga
* qo'shardi). Ko'pi bilan 300 ms kutadi: fon tabda animatsiya
* tugashi kechikishi mumkin
*/
export async function chiqishAnimatsiyasi(
li: Element | null,
): Promise<void> {
if (
li === null ||
typeof li.animate !== "function" || // jsdom (Vitest)
matchMedia("(prefers-reduced-motion: reduce)").matches
) {
return;
}
const animatsiya = li.animate(
[
{ opacity: 1, transform: "none" },
{ opacity: 0, transform: "translateX(1rem)" },
],
{ duration: 150, easing: "ease-in", fill: "forwards" },
);
await Promise.race([
animatsiya.finished.then(
() => undefined,
() => undefined,
),
new Promise<void>((tugadi) => setTimeout(tugadi, 300)),
]);
}Uchta himoya qatori:
- Uchta erta
return— qator topilmadi,animateyo'q (Vitest'dagi jsdom) yoki harakat kamaytirilgan: animatsiyasiz, darhol davom. fill: "forwards"— animatsiya tugagach qatoropacity: 0holatida qoladi. Bu bo'lmasa, state o'zgarguncha bir lahza qayta ko'rinib "miltillardi".Promise.race— ikkita va'daning qaysi biri oldin bajarilsa, shu. Animatsiya tugadi yoki 300 ms o'tdi.finishedrad etilsa ham (animatsiya bekor qilinsa) —undefined, xato tashlanmaydi.
7.3 App.tsx — avval chiqish, keyin o'chirish
App.tsx dagi ochir — chiqish, keyin haqiqiy o'chirish:
+import { chiqishAnimatsiyasi } from "./ui/qator-harakati.ts";
...
+ // 20/#25: avval qator ko'rinishdan chiqadi (150 ms),
+ // keyin o'chadi
+ const li =
+ tugmalar.current.get(`${id}:ochir`)?.closest("li") ?? null;
+ void chiqishAnimatsiyasi(li).then(() =>
+ ochirishniBajar({ id, matn, zaxira }),
+ );
+ }
+
+ function ochirishniBajar({ id, matn, zaxira }: OchirishSorovi) {
const amal = { tur: "ochirildi", id } as const;
elonQil(`«${matn}» o'chirildi.`);Qatorning li si «O'chirish» tugmasi ref'idan topiladi (Fokus boshqaruvi uchun tugmalar ro'yxati allaqachon bor). void — va'dani ataylab kutmaymiz, lint'ga "unutmadim" deymiz.
Ilgari ochir funksiyasi bitta edi. Endi u ikkiga bo'lindi: ochir — animatsiya, ochirishniBajar — e'lon, fokus va serverga so'rov (eski tana o'zgarmagan). Animatsiya paytida qator state'da ham, DOM'da ham bor — ziddiyat yo'q.
7.4 Kanon tekshiruvlari (holat 25)
20-qism brauzer ssenariysidagi (e2e-20.mjs, Chrome 155, CSP ostida) chiqishga tegishli qatorlar (natija-20-25.txt):
✅ o'chirish: avval chiqish (opacity+transform, 150 ms), keyin qator yo'q: [true,"opacity+transform:150",false]
✅ o'chirish: e'lon va fokus qo'shniga: ["«Animatsiya sinovi» o'chirildi.","ochir"]
✅ qo'shish/o'chirish: CLS 0 (hadRecentInput hisobga olinmaydi): 0
✅ reduced-motion: qatorda animatsiya yo'q, o'chirilgan qator darhol yo'q: [0,false]Ssenariy Element.prototype.animate ni "kuzatadi". Birinchi qatordagi massiv: tasdiqdan 40 ms keyin qator hali bor (true), oxirgi animatsiya — opacity+transform, 150 ms; keyin qator yo'q (false). Ikkinchi qator: e'lon chiqdi, fokus qo'shni qatorning «O'chirish» tugmasida — 18-qismdagi fokus qoidasi animatsiya bilan ham buzilmadi. CLS 0 — chiqish transform bilan, qo'shnilar sakramaydi. Oxirgisi: harakat kamaytirilganda qator animatsiyasiz, darhol o'chadi.
Commit hali yo'q: holat 25 butun Motion qadamini (#23–#26) qamraydi. Bitta commit va vazifalar ning v10 yakuni — Motion IV darsida, LazyMotion o'lchovidan keyin.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
exit ishlamaydi, konsol jim |
AnimatePresence yo'q yoki u ham shart bilan o'chadi |
<AnimatePresence>{shart && …}</AnimatePresence> |
| chiqish yo'q, kirish bor | bolada key yo'q yoki indeks |
barqaror key |
| almashinuvda ikki element, sahifa sakraydi | sync rejim |
mode="wait" yoki popLayout + layout |
mode="wait" da ro'yxat g'alati |
wait — bitta bola uchun |
sync / popLayout |
popLayout ishlamaydi, style-src xatosi |
Motion <style> qo'shadi, CSP to'sadi |
sync + layout yoki nonce'li CSP |
| element DOM'da qolib ketdi | safeToRemove chaqirilmadi / fon tab |
taymer yoki Promise.race |
layout da matn cho'ziladi |
bola layout siz |
bolaga layout="position" |
layoutId chiziq boshqa panelga uchadi |
ikkita bir xil layoutId |
LayoutGroup id="…" |
| filtrdan keyin eski qatorlar ko'rinadi | ro'yxat AnimatePresence da |
chiqish — faqat o'chirishda, state'dan oldin |
9. Mashqlar
Mashqlar kurs/mashqlar/20/25-chiqish-layout/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): qaysi rejim?
Har vaziyat uchun AnimatePresence ning mode qiymatini yozing (qo'shtirnoqsiz):
- Rasm galereyasi: bitta katta rasm, «Keyingi» bilan almashadi, ikki rasm bir paytda ko'rinmasin →
- Bildirishnomalar ro'yxati: har biri o'z vaqtida yopiladi, boshqalari joyida →
layoutli ro'yxat: o'chirilgan qator so'nayotganda pastdagilar darhol yuqoriga sirpanib chiqsin (CSP<style>ga ruxsat beradi) →
Yechim
wait— bitta bola, ketma-ket almashinuv. Ikki rasm bir paytda bo'lmaydi.sync— standart. Bildirishnomalar mustaqil: biri chiqayotganda boshqasi kirsa, kutish shart emas.popLayout— chiquvchi oqimdan chiqadi,layoutli qo'shnilar darhol siljiydi. Faqat CSP inline<style>ga ruxsat bersa:vazifalardagidekstyle-src 'self'da u jimsyncga aylanadi.
2-mashq (o'rta): "Saqlandi" xabari chiqib ketmayapti
«Bahor» buyurtma formasida «Saqlash» bosilsa, "Saqlandi" xabari chiqadi va 300 ms dan keyin yo'qolishi kerak. Yo'qolishi — 200 ms so'nish bilan. Hozir xabar so'nmasdan "pat" etib yo'qoladi. Kodni tuzating: xabar exit bilan chiqsin, chiqish tugaganda konsolga xabar ketdi yozilsin.
Ishora: «Uchta qoida» bo'limi — AnimatePresence qayerda turishi kerak? Tugaganini bilish — onExitComplete.
import { useState } from "react";
import { AnimatePresence, motion } from "motion/react";
export default function App() {
const [saved, setSaved] = useState(false);
function save() {
setSaved(true);
setTimeout(() => setSaved(false), 300);
}
return (
<div>
<button onClick={save}>Saqlash</button>
{saved && (
<AnimatePresence>
<motion.p
key="xabar"
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
Saqlandi
</motion.p>
</AnimatePresence>
)}
</div>
);
}Ekranda:
SaqlashYechim
import { useState } from "react";
import { AnimatePresence, motion } from "motion/react";
export default function App() {
const [saved, setSaved] = useState(false);
function save() {
setSaved(true);
setTimeout(() => setSaved(false), 300);
}
return (
<div>
<button onClick={save}>Saqlash</button>
<AnimatePresence
onExitComplete={() => console.log("xabar ketdi")}
>
{saved && (
<motion.p
key="xabar"
role="status"
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
Saqlandi
</motion.p>
)}
</AnimatePresence>
</div>
);
}Konsolda:
xabar ketdiEkranda:
SaqlashShart AnimatePresence ning ichiga ko'chdi. Endi u doim chizilgan: saved false bo'lganda bolasini 200 ms ushlab turadi. ekran("Saqlandi") bosishdan keyin xabar chiqqanini tekshiradi. role="status" — Malika uchun: xabar paydo bo'lganda ekran o'quvchi uni o'qiydi (jonli hudud).
3-mashq (qiyin): o'chirishdan oldin chiqish — Motion bilan
Kanon yechimini Motion'ning useAnimate hooki bilan takrorlang. Bu hook hali o'tilmagan — to'liq Motion IV da. Mashq uchun shu yetadi: useAnimate() ikki narsa qaytaradi. scope — ro'yxatga beriladigan ref. animate(element, kadrlar, sozlama) — elementni animatsiya qiladi va va'da (Promise) qaytaradi, uni await qilish mumkin. Talablar:
- Ro'yxat: Osh, Lag'mon, Manti. Har qatorda «O'chirish» tugmasi (
aria-label="Osh: o'chirish"kabi). - Bosilganda: avval qator
opacity: 0vax: 16ga 150 ms da animatsiya qilinadi, konsolgachiqdi: Osh. Keyin state'dan o'chiriladi. - Animatsiya 300 ms da tugamasa ham o'chirish bajarilsin (
Promise.race). AnimatePresenceishlatilmasin.
Ishora: const [scope, animate] = useAnimate(), animate(element, kadrlar, sozlama) va'da qaytaradi. Element — e.currentTarget.closest("li") (DOM bo'ylab harakatlanish).
Yechim
import { useState } from "react";
import { useAnimate } from "motion/react";
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
export default function App() {
const [items, setItems] = useState(["Osh", "Lag'mon", "Manti"]);
const [scope, animate] = useAnimate();
async function remove(e, name) {
const li = e.currentTarget.closest("li");
const exit = animate(
li,
{ opacity: 0, x: 16 },
{ duration: 0.15, ease: "easeIn" },
);
await Promise.race([exit, wait(300)]);
console.log("chiqdi:", name);
setItems((list) => list.filter((x) => x !== name));
}
return (
<ul ref={scope}>
{items.map((name) => (
<li key={name}>
{name}{" "}
<button
aria-label={`${name}: o'chirish`}
onClick={(e) => remove(e, name)}
>
✕
</button>
</li>
))}
</ul>
);
}Konsolda:
chiqdi: OshEkranda:
Lag'mon ✕
Manti ✕ekran("Osh") — bosishdan keyin qator hali bor: animatsiya ketyapti, state o'zgarmagan. 150 ms dan keyin chiqdi: Osh va state'dan o'chirish. Ro'yxat holati va DOM hech qachon bir-biriga zid emas. e.currentTarget ni await dan oldin o'qidik: React hodisasi tugagach currentTarget null bo'ladi. Kanon nega useAnimate emas, WAAPI tanladi? Hajm: useAnimate Motion'ning animatsiya yadrosini birinchi yuklashga olib kiradi — keyingi darsda o'lchaymiz.
4-mashq: Vazifalar qadami
feat/motion branch'ida davom eting: src/ui/qator-harakati.ts ga chiqishAnimatsiyasi ni qo'shing, App.tsx da ochir ni ikkiga bo'ling — chiqishAnimatsiyasi → ochirishniBajar. Commit qilmang: holat 25 commit'i Motion IV darsida.
Yechim
Tekshiruv ro'yxati:
- Vazifani o'chiring — o'ngga siljib so'nadi, keyin yo'qoladi; fokus qo'shni «O'chirish» da, e'lon
«…» o'chirildi. - Filtrni almashtiring va qidiring — qatorlar darhol yo'qoladi (chiqish animatsiyasi yo'q).
- Ikkinchi tab ochiq, birinchisi fonda: ikkinchisida o'chiring — fondagi tabda qator 300 ms dan kechiktirmay yo'qoladi (
Promise.race). - DevTools → Rendering →
prefers-reduced-motion: reduce— o'chirilgan qator animatsiyasiz, darhol yo'qoladi. npm test— Vitest jsdom'daanimateyo'q, funksiya darhol qaytadi: mavjud testlar o'zgarishsiz o'tadi.
10. Real ishda
- Modal, toast, menyu —
AnimatePresencening eng ko'p uchraydigan joylari: bitta element, chiqishi kutiladi. Radix va shadcn komponentlari esa chiqishni CSS bilan qiladi (data-[state=closed], CSS animatsiyalari React'da) — g'oya o'sha: yopilish animatsiyasi tugaguncha element DOM'da qoladi. - Mahsulot galereyasi, onlayn do'konlar —
layoutIdbilan kartadan to'liq sahifaga "uchuvchi" rasm, tab ostidagi chiziq, savatchaga uchadigan mahsulot. - Admin panellar — jadval va ro'yxatlarda chiqish animatsiyasini ehtiyot bilan: testlar (Playwright, Cypress) DOM'ni o'qiydi. Ko'p jamoalar test muhitida animatsiyani butunlay o'chiradi (
MotionGlobalConfig.skipAnimations) — lekin bu haqiqiy foydalanuvchi ko'radigan nomuvofiqlikni yashiradi. - Intervyu savollari: "React'da chiqish animatsiyasi nega qiyin?", "
AnimatePresenceqanday ishlaydi va negakeykerak?", "FLIP nima va negawidthemas,transform?", "chiqayotgan element ekran o'quvchi va testlarga qanday ta'sir qiladi?".
Xulosa
- React o'chirilgan elementni darhol olib tashlaydi;
exitfaqatAnimatePresenceichida ishlaydi — u doim chizilgan, boladakey. mode:sync— birga,wait— navbat bilan (bitta bola),popLayout— chiquvchi oqimdan chiqadi, lekin<style>qo'shadi va qattiq CSP'da ishlamaydi.usePresence— chiqishni o'zingiz boshqarasiz;safeToRemoveni albatta chaqiring.layout— FLIP: layout bir marta, animatsiyatransformbilan;layoutId— element "uchadi";LayoutGroup— qo'shnilar va nomlar fazosi.- Chiqayotgan element — state'da yo'q, DOM'da bor: ro'yxatda u filtr, qidiruv, testlar va fon tabni buzdi. Kanon: chiqish faqat o'chirishda, state'dan oldin, WAAPI va 300 ms chegarasi bilan.
Keyingi dars: Motion IV: scroll animatsiya, motion values va optimallashtirish. U yerda React'ni qayta chizmaydigan motion values, scroll effektlari va LazyMotion bilan hajmni kamaytirishni o'rganamiz.
Manbalar
- Motion: AnimatePresence, Layout animations, LayoutGroup, usePresence — motion.dev/docs/react-animate-presence, motion.dev/docs/react-layout-animations (motion 14.0.0, 2026-10)
- Motion manba kodi:
framer-motion/dist/es/components/AnimatePresence/PopChild.mjs(14.0.0) —popLayoutuchun<style> - MDN: Element.animate(), Animation.finished — developer.mozilla.org/docs/Web/API/Element/animate
- Paul Lewis: FLIP Your Animations — aerotwist.com/blog/flip-your-animations
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!