IlmHamroh
JavaScript Full-stack/20-qism. React: UI kutubxonalar va animatsiya24/30-dars19 daqiqa
Mundarija (34)

Motion variants, stagger va gestures: whileHover, whileTap va drag — React'da jonli interfeys

Qisqacha: Variants — animatsiya holatlariga nom berish: { yopiq: {...}, ochiq: {...} }. Ota animate="ochiq" desa, ichidagi hamma motion bolalar ham shu nomdagi holatga o'tadi — props uzatmasdan. Ota esa bolalarni orkestrlaydi: delayChildren: stagger(0.08) — birin-ketin, when: "beforeChildren" — avval ota. Gestures — whileHover, whileTap, whileFocus (holat faqat harakat davomida) va drag (sudrash, dragConstraints bilan chegara). Amal har doim onClick da, gesture — faqat ko'rinish; sudrashning tugmali muqobili bo'lsin.

Bu darsda

  • Variants bilan holatlarga nom berasiz va ularni ota-boladan bolaga uzatasiz.
  • stagger, delayChildren, when bilan ketma-ketlikni boshqarasiz va dinamik variants (custom) yozasiz.
  • Stagger qachon o'rinli, qachon xalaqit berishini o'lchov bilan ajratasiz.
  • whileHover, whileTap, whileFocus ni a11y bilan to'g'ri ishlatasiz: sensor ekran, klaviatura, onTap va onClick farqi.
  • drag, dragConstraints, onDragEnd bilan "surib o'chirish" ni yozasiz va vazifalar da qaysi qator "yangi" ekanini hal qilasiz.

Oldin bilishingiz kerak: Motion I: motion komponentlar va asosiy animatsiya, Drag and drop: dnd-kit, Fokus boshqaruvi va klaviatura navigatsiyasi.

1. Nega bu kerak?

Oldingi darsda bitta elementni jonlantirdik: initial → animate. Real interfeysda elementlar ko'p va ular bir-biriga bog'liq.

«Bahor» menyusi sahifasini oching. Kategoriya ochilganda 6 ta taom kartochkasi paydo bo'ladi. Har biriga initial va animate yozish — 6 marta bir xil kod. Dizayner esa "birin-ketin chiqsin" dedi — har biriga delay: 0.05, 0.1, 0.15… qo'lda hisoblash kerak bo'ladi. Kartochka ustiga sichqoncha kelsa ko'tarilsin, bosilganda bir oz siqilsin. Telefonda esa savatdagi taomni chapga surib o'chirish mumkin bo'lsin.

Sardorning birinchi kodi uch ekranga cho'zildi. Bu darsdagi uch vosita uni bir ekranga sig'diradi:

  • variants — holatlarga nom: bir joyda yoziladi, hamma joyda ishlatiladi;
  • orkestratsiya — ota bolalarning vaqtini boshqaradi;
  • gestures — foydalanuvchi harakatiga javob: hover, bosish, fokus, sudrash.

Hayotiy o'xshatish: orkestr. Har musiqachiga "uchinchi daqiqada, 5 soniyadan keyin chal" deb alohida aytmaysiz. Dirijyor bitta ishora qiladi — hamma o'z partiyasini o'z vaqtida boshlaydi. Variants — nota, ota — dirijyor.

2. Variants: holatlarga nom berish

2.1 Obyekt o'rniga nom

Motion I da animate={{ opacity: 1, y: 0 }} deb yozdik. Variants bilan bu qiymatlar nomlangan obyektga ko'chadi:

jsx
const card = {
  yopiq: { opacity: 0, y: 8 },
  ochiq: { opacity: 1, y: 0 },
};

<motion.li variants={card} initial="yopiq" animate="ochiq" />

initial va animate endi obyekt emas — nom. Bitta komponent uchun farq kichik. Kuch ota-bola munosabatida ochiladi.

2.2 Nom bolalarga o'tadi

Ota animate="ochiq" desa, uning ichidagi har motion bola (o'zining animate i bo'lmasa) xuddi shu nomni o'z variants idan qidiradi. Props uzatish kerak emas — xuddi Context kabi, daraxt bo'ylab:

jsx
import { motion, stagger } from "motion/react";

const list = {
  yopiq: {},
  ochiq: { transition: { delayChildren: stagger(0.08) } },
};
const item = {
  yopiq: { opacity: 0, y: 8 },
  ochiq: { opacity: 1, y: 0 },
};

export default function App() {
  return (
    <motion.ul
      variants={list}
      initial="yopiq"
      animate="ochiq"
      onAnimationComplete={(name) => console.log("ro'yxat:", name)}
    >
      {["Osh", "Manti", "Lag'mon"].map((name) => (
        <motion.li
          key={name}
          variants={item}
          onAnimationComplete={() => console.log("chiqdi:", name)}
        >
          {name}
        </motion.li>
      ))}
    </motion.ul>
  );
}

Konsolda:

text
chiqdi: Osh
chiqdi: Manti
chiqdi: Lag'mon
ro'yxat: ochiq

Ekranda:

text
Osh
Manti
Lag'mon

Natija oynasida taomlar birin-ketin, 80 ms oraliq bilan paydo bo'ladi. Qatorma-qator:

  • <motion.li> larda initial ham, animate ham yo'q. Ular faqat variants ni biladi. "Qachon qaysi holat" ni ota aytdi.
  • Ota variants ida ochiq: {} — otaning o'zi hech narsani animatsiya qilmaydi, faqat transition bilan bolalarni boshqaradi.
  • delayChildren: stagger(0.08) — har keyingi bola 0.08 s kechroq boshlaydi. Konsoldagi tartib — Osh, Manti, Lag'mon — shuning natijasi.
  • onAnimationComplete argumenti — variant nomi ("ochiq"). Ota bolalar tugagach tugadi.

2.3 staggerChildren — eski kodda

Ko'p maqola va eski loyihalarda boshqa yozuvni ko'rasiz:

jsx
// eski yozuv — hali ishlaydi, lekin eskirgan (deprecated)
ochiq: { transition: { staggerChildren: 0.08 } }

Motion 14 ning tur fayllarida staggerChildren "delayChildren: stagger(interval) ni ishlating" izohi bilan eskirgan deb belgilangan. stagger() funksiyasi kuchliroq: stagger(0.08, { from: "last" }) — oxiridan boshlab, from: "center" — o'rtadan ikki tomonga.

Tekshirib ko'ring: Ichki motion.li ga animate={{ opacity: 1 }} qo'shsak, ota animate="ochiq" desa ham u stagger'ga qo'shiladimi?

Javob

Yo'q. O'z animate props'i bor bola otadan nom kutmaydi — u mustaqil. Stagger va ota holati unga ta'sir qilmaydi. Variants'ning "nom bolaga o'tadi" qoidasi faqat animate siz (yoki animate o'rniga variant nomi bergan) bolalarda ishlaydi. Eng ko'p uchraydigan sabab: "stagger ishlamayapti".

3. Orkestratsiya

3.1 when: ota va bolalar navbati

Otaning o'zi ham animatsiya qilsa (masalan, fon so'nib chiqsa), kim birinchi?

jsx
const panel = {
  yopiq: {
    opacity: 0,
    transition: { when: "afterChildren" },
  },
  ochiq: {
    opacity: 1,
    transition: {
      when: "beforeChildren",
      delayChildren: stagger(0.05),
    },
  },
};
  • "beforeChildren" — avval ota (panel paydo bo'ladi), keyin bolalar (kartochkalar).
  • "afterChildren" — avval bolalar (kartochkalar yo'qoladi), keyin ota (panel). Yopishda shu mantiqiy.
  • when yozilmasa — hammasi bir vaqtda boshlanadi.

3.2 Dinamik variants: custom

Variant obyekt emas, funksiya bo'lishi ham mumkin. Uning argumenti — elementning custom props'i:

jsx
import { motion } from "motion/react";

const item = {
  yopiq: { opacity: 0, x: -10 },
  ochiq: (i) => ({
    opacity: 1,
    x: 0,
    transition: { delay: i * 0.1 },
  }),
};

export default function App() {
  return (
    <ul>
      {["Osh", "Manti"].map((name, i) => (
        <motion.li
          key={name}
          custom={i}
          variants={item}
          initial="yopiq"
          animate="ochiq"
          onAnimationComplete={() => console.log("chiqdi:", name)}
        >
          {name}
        </motion.li>
      ))}
    </ul>
  );
}

Konsolda:

text
chiqdi: Osh
chiqdi: Manti

Bu yerda ota oddiy <ul> — orkestrlovchi yo'q. Har bola o'z kechikishini custom dan hisobladi. Qachon kerak? Kechikish yoki yo'nalish ma'lumotga bog'liq bo'lsa: "o'chirilgan qator chapga, qo'shilgani o'ngga", "ro'yxatdagi joyiga qarab".

3.3 Stagger qachon o'rinli

UI animatsiya tamoyillari darsidagi savolni eslang: 30 qator × 100 ms — oxirgi qator 3 soniyadan keyin. Stagger uchun amaliy qoidalar:

  • Umumiy vaqt qisqa. Oraliq 30–80 ms. Element ko'p bo'lsa — faqat birinchi 6–10 tasini kechiktiring, qolganlari birga.
  • Faqat birinchi tanishuvda. Kategoriya ochilganda — ha. Har filtr o'zgarganda butun ro'yxat qaytadan "oqsa" — yo'q, foydalanuvchi ma'lumotni kutadi.
  • Ko'z yo'nalishida. Ro'yxat tepadan pastga o'qiladi — stagger ham tepadan.
flowchart TD
  A{"Element birinchi marta chiqyaptimi?"} -->|yo'q| N["Stagger yo'q"]
  A -->|ha| B{"Elementlar 10 tadan ko'pmi?"}
  B -->|ha| C["Faqat birinchi 6–10 tasi"]
  B -->|yo'q| D{"Ko'p takrorlanadigan ekranmi?"}
  D -->|ha| N
  D -->|yo'q| S["Stagger, 30–80 ms"]

Sxemada nimaga qarang: stagger'ga olib boradigan yo'l bitta va u tor. Ko'pincha javob "yo'q" — vazifalar ham shunday qaror qildi («Vazifalar qadami» bo'limida).

4. Gestures: foydalanuvchi harakatiga javob

4.1 whileHover, whileTap, whileFocus

while… props'lari — "harakat davom etayotganda shu holatda bo'l". Harakat tugashi bilan element animate holatiga qaytadi:

jsx
<motion.button
  whileHover={{ y: -2 }}
  whileTap={{ scale: 0.97 }}
  whileFocus={{ outline: "2px solid #1d4ed8" }}
>
  Savatga
</motion.button>

Ular ham variant nomini qabul qiladi va bolalarga o'tadi. Kartochka whileHover="faol" desa, ichidagi ikonka ham o'z faol variantiga o'tadi.

Uchtasining o'z qoidalari bor (manba: Motion 14 manba kodi):

  • whileHover — faqat sichqoncha va qalam. Barmoq bilan (touch) teginish hover hisoblanmaydi — telefonda "yopishib qolgan hover" bo'lmaydi. Demak muhim ma'lumotni hover'ga yashirmang: telefonda u hech qachon ko'rinmaydi.
  • whileTap — bosish davomida. Klaviaturada Enter ham bosish hisoblanadi: element fokusda bo'lsa, Enter bosilganda whileTap holati yoqiladi.
  • whileFocus — faqat :focus-visible bo'lganda, ya'ni klaviatura bilan kelganda. Sichqoncha bilan bosilganda fokus ramkasi chiqmaydi — brauzerning o'z qoidasi kabi.

4.2 onTap va onClick: amal qayerda?

Motion'da bosish hodisalari ham bor: onTap, onTapStart, onTapCancel, onHoverStart, onHoverEnd. Ular onClick ning o'rnini bosadimi? Sinab ko'ramiz: sichqoncha bilan bosamiz, keyin klaviaturada Space va Enter:

jsx
import { useState } from "react";
import { motion } from "motion/react";

export default function App() {
  const [taps, setTaps] = useState(0);
  const [clicks, setClicks] = useState(0);
  return (
    <div>
      <motion.button
        whileTap={{ scale: 0.95 }}
        onTap={() => setTaps((t) => t + 1)}
        onClick={() => setClicks((c) => c + 1)}
      >
        Sevimli
      </motion.button>
      <p>
        onTap: {taps}, onClick: {clicks}
      </p>
    </div>
  );
}

Ekranda:

text
Sevimli
onTap: 2, onClick: 3

Qadamma-qadam:

Qadam Fokus Ekranda
sichqoncha bilan bosish «Sevimli» da (bosilgan tugma fokus oladi) onTap: 1, onClick: 1
Space «Sevimli» da onTap: 1, onClick: 2
Enter «Sevimli» da onTap: 2, onClick: 3

Uchta bosish — onClick uchalasini ham oldi. onTap esa Space'ni o'tkazib yubordi: Motion'ning klaviatura qo'llab-quvvatlashi faqat Enter'ni tekshiradi. Brauzerning o'zi esa tugma uchun Enter'ni ham, Space'ni ham click ga aylantiradi (React'da accessibility darsida).

Qoida: amal — onClick da (saqlash, o'chirish, savatga qo'shish). whileTap — faqat ko'rinish ("bosildi" hissi). onTap — faqat tugma bo'lmagan elementlar uchun va klaviatura muqobilini o'zingiz berganda.

4.3 Bosish javobi — qancha?

Bosilganda scale: 0.97 — sezilarli, lekin bezovta qilmaydi. 0.8 — tugma "qochib ketadi". Hover'da y: -2 yoki soya — yetadi. Bular ko'p takrorlanadigan harakatlar: UI animatsiya tamoyillari darsidagi "~100 ms" qatori. Motion scale uchun sukut bo'yicha spring ishlatadi — tez-tez bosilsa ham harakat sinmaydi.

whileHover ning CSS muqobili bor: :hover. Agar faqat rang yoki soya o'zgarsa — CSS yetadi va JavaScript'siz. Motion'ning whileHover i spring, bolalarga uzatish yoki sudrash bilan birga ishlash kerak bo'lganda foydali.

Tekshirib ko'ring: Taom kartochkasida narx faqat whileHover da paydo bo'ladi. Telefon foydalanuvchisi nimani ko'radi?

Javob

Narxni hech qachon ko'rmaydi. Motion barmoq teginishini hover deb hisoblamaydi, telefonda esa sichqoncha yo'q. Klaviatura foydalanuvchisi (Bobur) ham ko'rmaydi — hover fokus emas. Narx doim ko'rinsin, hover esa faqat bezak (ko'tarilish, soya) bo'lsin.

5. Drag: sudrash

5.1 drag va dragConstraints

drag props'i elementni sudraladigan qiladi. drag="x" — faqat gorizontal, drag="y" — faqat vertikal. Telefondagi savat: taomni chapga sursangiz, o'chiriladi.

Sudrashni kurs tekshiruvchisi bajara olmaydi (haqiqiy sichqoncha yo'q). Natija oynasida o'zingiz sinang — sichqoncha yoki barmoq bilan:

jsx
import { useState } from "react";
import { motion } from "motion/react";

export default function App() {
  const [items, setItems] = useState(["Osh", "Manti", "Ko'k choy"]);

  function remove(name) {
    setItems((all) => all.filter((x) => x !== name));
  }

  return (
    <ul style={{ padding: 0, maxWidth: 280 }}>
      {items.map((name) => (
        <motion.li
          key={name}
          drag="x"
          dragConstraints={{ left: -120, right: 0 }}
          dragElastic={0.1}
          dragSnapToOrigin
          onDragEnd={(e, info) => {
            if (info.offset.x < -80) remove(name);
          }}
          style={{
            listStyle: "none",
            margin: 4,
            padding: 12,
            background: "#e8edfb",
            borderRadius: 8,
            touchAction: "pan-y",
          }}
        >
          {name}{" "}
          <button onClick={() => remove(name)}>O'chirish</button>
        </motion.li>
      ))}
    </ul>
  );
}

Har props:

  • dragConstraints={{ left: -120, right: 0 }} — chegara: 120 px chapga, o'ngga umuman yo'q. Chegarani ref bilan ham berish mumkin: dragConstraints={konteynerRef} — element konteynerdan chiqmaydi.
  • dragElastic={0.1} — chegaradan tashqariga qanchalik "cho'ziladi" (0 — umuman yo'q, 1 — erkin).
  • dragSnapToOrigin — qo'yib yuborilganda joyiga qaytadi (prujina bilan).
  • onDragEnd(e, info) — info.offset.x — boshlanishdan beri qancha surildi, info.velocity.x — qo'yib yuborilgandagi tezlik. 80 px dan ko'p chapga — o'chirish.
  • touchAction: "pan-y" — telefonda tik aylantirish brauzerda qoladi, gorizontal harakatni esa Motion oladi. Busiz qatorni surayotganda sahifa ham siljiydi.

Yana: dragMomentum (qo'yib yuborilgandan keyin inersiya bilan davom etish, sukut — bor), whileDrag (sudralayotgan paytdagi holat, masalan { scale: 1.03 }), onDragStart, onDrag.

5.2 A11y: sudrash — yagona yo'l bo'lmasin

Bobur sichqoncha ishlatmaydi. Malika qatorni "ko'rmaydi". Sudrash ular uchun yo'q. Shuning uchun misolda har qatorda «O'chirish» tugmasi bor — sudrash tezlashtiruvchi yo'l, asosiy yo'l emas. WCAG 2.2 da bu alohida mezon: 2.5.7 "Dragging Movements" — sudrash bilan qilinadigan ishni bitta bosish bilan ham qilish mumkin bo'lsin.

5.3 Motion drag yoki dnd-kit?

Drag and drop: dnd-kit darsida ro'yxatni tartiblashni qurdik. Ikkalasi ham "sudrash", lekin vazifasi boshqa:

Motion drag dnd-kit
Nima uchun erkin surish: swipe, slayder, "tashla" ro'yxat tartibi, ustunlar orasida ko'chirish
Klaviatura yo'q — o'zingiz qo'shasiz KeyboardSensor tayyor
Ekran o'quvchi yo'q e'lonlar tayyor
Fizika prujina, inersiya oddiy

Qoida: elementlarni tartiblash — dnd-kit. Elementni surib qo'yish yoki "uloqtirish" — Motion drag.

Tekshirib ko'ring: Swipe misolidan touchAction: "pan-y" olib tashlansa, telefonda nima bo'ladi?

Javob

Barmoq qatorga tekkanda brauzer va Motion "kim boshqaradi?" deb tortishadi. Qatorni chapga sursangiz, brauzer buni sahifani aylantirish deb ham qabul qilishi mumkin. Natija: qator siljimaydi yoki sahifa bilan birga titraydi. touch-action: pan-y brauzerga aytadi: "tik harakat — senga, gorizontal — sahifadagi kodga".

6. Harakatni kamaytirish: variants va gestures

Motion I dagi <MotionConfig reducedMotion="user"> bu darsdagi hamma narsaga ham tegishli. Tizimda "harakatni kamaytirish" yoqilgan bo'lsa, variantlardagi y, x, scale darhol maqsadda bo'ladi, opacity esa qoladi. Stagger'li ro'yxat endi uchmaydi — faqat birin-ketin so'nib chiqadi. Ma'no (yangi narsa paydo bo'ldi) saqlanadi.

Ikki narsani o'zingiz hal qilasiz:

  • Uzun stagger. Kamaytirilgan rejimda ham kutish qoladi. useReducedMotion() (Motion IV da batafsil) true bo'lsa, delayChildren ni olib tashlang — hammasi birga.
  • Sudrash. drag — foydalanuvchining o'z harakati, u o'chmaydi. Lekin qo'yib yuborilgandan keyingi inersiya va "sakrab qaytish" bezak: kamaytirilgan rejimda dragMomentum={false} qo'yish mumkin.

7. Narx va LazyMotion

Gestures va variants ham Motion'ning "yadro"sida. Motion IV darsida yadroni LazyMotion bilan bo'lakka bo'lamiz. Hozir bitta faktni biling: yadroning ikki varianti bor. Birinchisi — domAnimation: animatsiyalar, variants, hover, tap va focus. Ikkinchisi — domMax: shularning hammasi, ustiga drag va layout. Manba — framer-motion 14.0.0 kodi. vazifalar domAnimation bilan cheklangan: unda Motion drag ham, layout animatsiya ham yo'q, shuning uchun yadro kichik (14.5 KB).

8. Vazifalar qadami: qaysi qator "yangi"? (holat 25 davomi)

8.1 Muammo

Oldingi darsda useQatorHarakati(yangi) yozdik: yangi bo'lsa — kirish animatsiyasi, bo'lmasa initial: false. Lekin yangi ni kim hisoblaydi?

Birinchi xayol — stagger: sahifa ochilganda hamma qatorlar birin-ketin chiqsin. Kanon bunga yo'q dedi:

  • vazifalar — ish quroli, kuniga ko'p marta ochiladi. Ochilishda kutish — har safar.
  • 200 tagacha qator to'liq ro'yxatda — stagger bilan oxirgisi soniyalab kechikadi.
  • Mavjud qatorlar "yangi" emas — ularni jonlantirish hech narsani tushuntirmaydi (to'rt vazifadan hech biri).

Qoida: faqat ro'yxat ochilgandan keyin qo'shilgan qator jonlanadi.

8.2 Yechim: birinchi chizishdagi id'lar

VazifaRoyxati.tsx (kanon, holat 25):

diff
-import { memo, useMemo } from "react";
+import { memo, useMemo, useState } from "react";
...
+  // 20/#25: ochilishdagi qatorlar animatsiyasiz — faqat keyin
+  // qo'shilganlari "yangi" (kirish animatsiyasi). useState
+  // initializer — bir marta, birinchi chizishda (effekt va
+  // ref'siz)
+  const [boshlangich] = useState(
+    () => new Set(vazifalar.map((v) => v.id)),
+  );
   return vazifalar.map((vazifa) => (
     <VazifaQatori
       key={vazifa.id}
       vazifa={vazifa}
       sorov={sorov}
       tahrirda={vazifa.id === tahrirId}
+      yangi={!boshlangich.has(vazifa.id)}
     />
   ));

Bu bir necha qatorda React'ning ikki g'oyasi ishlaydi:

  • useState boshlang'ich funksiyasi (useState) — () => new Set(...) faqat birinchi chizishda chaqiriladi. Keyingi render'larda React saqlangan Set ni qaytaradi. Setter (setBoshlangich) yo'q — qiymat hech qachon o'zgarmaydi.
  • Hisoblanadigan qiymat — yangi alohida state emas, har renderda id'ni solishtirib topiladi.

Nega effekt yoki ref emas? Effekt birinchi chizishdan keyin ishlaydi — bitta render davomida hamma qator "yangi" bo'lib qolardi va jonlanardi. Ref'ga render paytida yozish esa React qoidasiga zid (useRef I darsida).

yangi props'i zanjir bo'ylab Qator gacha tushadi. VazifaQatori.tsx (kanon):

diff
+  // 20/#25: ochilgandan keyin qo'shilgan — kirish animatsiyasi
+  yangi?: boolean;
...
-  return <OddiyQator vazifa={vazifa} sorov={sorov} joy={qatorJoyi} />;
+  return (
+    <OddiyQator
+      vazifa={vazifa}
+      sorov={sorov}
+      joy={qatorJoyi}
+      yangi={yangi}
+    />
+  );

OddiyQator.tsx uni Qator ga beradi (yangi={yangi}). Tutqichli qatorga ham (tutqichliQator({ vazifa, yangi, children })).

8.3 Sudrash va Motion bitta qatorda

«Tartiblash» rejimidagi qator (Drag and drop: dnd-kit) ham m.li bo'ldi. Bu yerda ikki kutubxona bitta elementning transform ini boshqaradi. SudraladiganQator.tsx (kanon):

diff
+  const harakat = useQatorHarakati(yangi);
+  // 20/#25: paydo bo'lish — Qator.tsx dagidek. Motion faqat
+  // opacity va x/y ni animatsiya qiladi; dnd-kit'ning transform'i
+  // (style) sudrash paytida — ikkalasi bir vaqtda ishlamaydi
   return (
-    <li
+    <m.li
+      {...harakat}
       ref={setNodeRef}

Izohdagi argumentga qarang: ikkalasi transform ga yozadi, lekin bir vaqtda emas. Motion — qator paydo bo'lgan 150 ms da. dnd-kit — faqat sudrash paytida. Agar Motion'ning drag i yoki whileHover={{ y: -2 }} qo'shilsa, ular dnd-kit bilan to'qnashardi. Kanonda qatorlarda gesture yo'qligining ikkinchi sababi shu (birinchisi — hover'ni CSS :hover yetarli qiladi).

8.4 Kanon tekshiruvi

Holat 25 ning 20-qism ssenariysidan (Chrome 155):

text
✅ ochilishda mavjud qatorlar animatsiyasiz: 0
✅ yangi qator: kirish animatsiyasi — faqat opacity va transform: true
✅ qo'shish/o'chirish: CLS 0 (hadRecentInput hisobga olinmaydi): 0

Birinchi qator: sahifa ochilganda <li> larga topshirilgan animatsiyalar soni — 0. Uchinchisi: yangi qator paydo bo'lganda sahifa "sakramadi" (CLS — Cumulative Layout Shift, kutilmagan siljish, Core Web Vitals). Animatsiya faqat opacity va transform da bo'lgani uchun boshqa qatorlar joyidan qimirlamadi. Commit — Motion darslarining oxirida.

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
stagger ishlamaydi bolada o'z animate i bor bolada faqat variants
bola animatsiya qilmaydi variant nomlari mos emas (ochiq / open) bir xil nomlar
staggerChildren haqida eskirganlik izohi eski API delayChildren: stagger(…)
Space bilan "Sevimli" ishlamaydi amal onTap da amal onClick da
telefonda hover ma'lumoti ko'rinmaydi whileHover touch'da yo'q muhim narsa doim ko'rinsin
sudralgan element ekrandan uchib ketadi dragConstraints yo'q chegara yoki ref
qatorni surganda sahifa ham siljiydi touch-action yo'q touchAction: "pan-y"
sudrash bilan o'chiriladi, boshqa yo'l yo'q a11y tugma muqobili (WCAG 2.5.7)
ro'yxat ochilganda hamma qator "uchadi" hamma qatorda initial faqat yangilariga

10. Mashqlar

Mashqlar kurs/mashqlar/20/24-motion-ii/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): qaysi props?

  • Ota bolalarni 0.05 s oraliq bilan chiqarsin → delayChildren:
  • Yopishda avval bolalar, keyin ota → when:
  • Faqat gorizontal sudrash → drag=
  • Klaviatura bilan kelganda fokus holati →
Yechim
  • delayChildren: stagger(0.05) — staggerChildren emas (eskirgan).
  • when: "afterChildren" — yopilishda bolalar oldin ketadi.
  • drag="x".
  • whileFocus — faqat :focus-visible da ishlaydi.

2-mashq (o'rta): menyu kartochkalari

«Bahor» kategoriya sahifasi: 4 ta taom (Osh, Lag'mon, Manti, Ko'k choy).

  1. Ota motion.ul va bolalar motion.li — variants bilan (yopiq/ochiq).
  2. Bolalar 0.06 s oraliq bilan, oxiridan boshlab chiqsin (from: "last").
  3. Otaning o'zi avval so'nib chiqsin (opacity 0 → 1), keyin bolalar.
  4. Har bola chiqqanda konsolga nomi yozilsin.

Ishora: «when: ota va bolalar navbati» bo'limi; stagger(0.06, { from: "last" }).

Yechim
jsx
import { motion, stagger } from "motion/react";

const menu = ["Osh", "Lag'mon", "Manti", "Ko'k choy"];

const list = {
  yopiq: { opacity: 0 },
  ochiq: {
    opacity: 1,
    transition: {
      when: "beforeChildren",
      delayChildren: stagger(0.06, { from: "last" }),
    },
  },
};
const card = {
  yopiq: { opacity: 0, y: 8 },
  ochiq: { opacity: 1, y: 0 },
};

export default function App() {
  return (
    <motion.ul variants={list} initial="yopiq" animate="ochiq">
      {menu.map((name) => (
        <motion.li
          key={name}
          variants={card}
          onAnimationComplete={() => console.log("chiqdi:", name)}
        >
          {name}
        </motion.li>
      ))}
    </motion.ul>
  );
}

Konsolda:

text
chiqdi: Ko'k choy
chiqdi: Manti
chiqdi: Lag'mon
chiqdi: Osh

Ekranda:

text
Osh
Lag'mon
Manti
Ko'k choy

Ekranda tartib o'zgarmadi — faqat chiqish tartibi teskari. when: "beforeChildren" — avval ul ning o'zi so'nib chiqdi, keyin bolalar. from: "last" qachon kerak? Masalan pastdan chiqadigan menyuda: ko'z pastdan yuqoriga qaraydi.

3-mashq (qiyin): "Sevimli" tugmasi

Taom kartochkasidagi yurak tugmasi:

  1. motion.button, aria-pressed bilan; amal — onClick (Enter va Space ikkalasi ishlasin).
  2. whileTap={{ scale: 0.9 }} — bosish hissi.
  3. Holat o'zgarganda ichidagi belgi (♥ / ♡) variants bilan "lip" etsin: faol: { scale: [1, 1.3, 1] }, nofaol: { scale: 1 }. Variant nomi otadan bolaga o'tsin.
  4. Ostida: "Sevimlilarda: ha/yo'q".

Tekshirish: sichqoncha bilan bir marta, keyin Space bilan bir marta — natija "yo'q" (ikki marta almashdi).

Ishora: otada animate={fav ? "faol" : "nofaol"}, bolada faqat variants. «Nom bolalarga o'tadi» bo'limi.

Yechim
jsx
import { useState } from "react";
import { motion } from "motion/react";

const heart = {
  faol: { scale: [1, 1.3, 1], transition: { duration: 0.3 } },
  nofaol: { scale: 1 },
};

export default function App() {
  const [fav, setFav] = useState(false);
  return (
    <div>
      <motion.button
        aria-pressed={fav}
        aria-label="Sevimli"
        initial={false}
        animate={fav ? "faol" : "nofaol"}
        whileTap={{ scale: 0.9 }}
        onClick={() => setFav(!fav)}
        onAnimationComplete={(name) => console.log("variant:", name)}
      >
        <motion.span
          variants={heart}
          style={{ display: "inline-block" }}
          aria-hidden="true"
        >
          {fav ? "♥" : "♡"}
        </motion.span>
      </motion.button>
      <p>Sevimlilarda: {fav ? "ha" : "yo'q"}</p>
    </div>
  );
}

Ekranda:

text
♡
Sevimlilarda: yo'q

Sichqoncha — onClick, va "lip". Space — yana onClick, . Amal onClick da bo'lgani uchun Space ham ishladi — onTap da bo'lsa, Space'da hech narsa bo'lmasdi («onTap va onClick» bo'limi). aria-label="Sevimli" va aria-pressed — Malika "Sevimli, almashtirgich tugma, bosilgan" ni eshitadi. Belgining o'zi aria-hidden: yurak belgisi ekran o'quvchida g'alati o'qilishi mumkin. Belgi otaning variant nomini oldi — unga hech qanday props uzatmadik.

4-mashq: Vazifalar qadami

feat/motion branch'ida davom eting: VazifaRoyxati.tsx — boshlangich (useState boshlang'ich funksiyasi bilan) va yangi props'i; VazifaQatori.tsx, OddiyQator.tsx, Qator.tsx, SudraladiganQator.tsx — yangi zanjiri va useQatorHarakati(yangi). Commit qilmang.

Yechim

Tekshiruv ro'yxati:

  • Sahifani oching — mavjud qatorlar jim. Yangi vazifa qo'shing — faqat u jonlanadi.
  • Filtrni almashtiring: filtrdan qaytgan eski qatorlar "yangi" emas (ularning id'lari boshlangich da) — jonlanmaydi.
  • «Tartiblash» rejimiga kiring va qator qo'shing — sudrash paytida Motion aralashmaydi.
  • DevTools Performance → yangi qator qo'shishda "Layout Shift" yozuvi yo'q.
  • npm run lint — React Compiler lint qoidalari (render paytida ref'ga yozish yo'q) toza.

11. Real ishda

  • Landing va marketing sahifalari — variants + stagger: sarlavha, matn, tugmalar birin-ketin. Bu yerda stagger o'rinli: sahifa bir marta ko'riladi.
  • Mobil-veb ilovalar — swipe (o'chirish, arxivlash), pastdan chiqadigan panel (drag="y"), karusel. Sensor ekran fizikasi — Motion'ning kuchli tomoni.
  • Dizayn tizimlari — tugmalarning whileTap javobi umumiy komponentda bir marta yoziladi (motion.create(Tugma)), sahifalarda takrorlanmaydi.
  • Intervyu savollari: "variants qanday ishlaydi va nima uchun kerak?", "stagger'ni qanday qilasiz va qachon qilmaysiz?", "whileHover telefonda nima qiladi?", "sudrash bilan qilinadigan ishni a11y uchun qanday ochiq qilasiz?".

Xulosa

  • Variants — holatlarga nom; ota animate="nom" desa, animate siz bolalar o'z variants idan shu nomni oladi.
  • Orkestratsiya: delayChildren: stagger(t) (staggerChildren — eskirgan), when: "beforeChildren"/"afterChildren", dinamik variant — custom.
  • Stagger — faqat birinchi tanishuvda, qisqa oraliq bilan, elementlar ko'p bo'lsa cheklab.
  • whileHover — touch'da yo'q; whileTap — Enter'da ham, Space'da emas; whileFocus — faqat :focus-visible. Amal — onClick da.
  • drag, dragConstraints, dragElastic, onDragEnd(info.offset), touch-action; sudrashning tugmali muqobili majburiy. Tartiblash — dnd-kit.
  • Kanon: stagger yo'q — faqat ochilgandan keyin qo'shilgan qator jonlanadi (useState boshlang'ich funksiyasi).

Keyingi dars: Motion III: chiqish va layout animatsiyalari — AnimatePresence, exit, layout va layoutId; nega kanon ro'yxatda AnimatePresence ni ishlatmadi (o'lchov bilan).

Manbalar

  • Motion for React: Animation (variants, orchestration), Gestures, Drag — motion.dev/docs/react (motion 14.0.0)
  • Motion: stagger — motion.dev/docs/stagger
  • framer-motion / motion-dom 14.0.0 manba kodi: hover (touch filtri), klaviatura bilan bosish (Enter), :focus-visible, domAnimation/domMax
  • WCAG 2.2: 2.5.7 Dragging Movements — w3.org/WAI/WCAG22/Understanding/dragging-movements
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Motion variants, stagger va gestures: whileHover, whileTap va drag — React'da jonli interfeys — IlmHamroh