IlmHamroh
JavaScript Full-stack/20-qism. React: UI kutubxonalar va animatsiya26/30-dars27 daqiqa
Mundarija (38)

Motion'da scroll animatsiya va motion values: useScroll, useTransform, useSpring va LazyMotion

Qisqacha: Har kadrda o'zgaradigan qiymatni (scroll, sichqoncha, progress) useState da saqlasangiz, React sekundiga 60 marta qayta chizadi. Motion value (useMotionValue) — React'dan tashqaridagi qiymat. U o'zgarganda faqat elementning style i yangilanadi, komponent qayta chizilmaydi. useTransform bir qiymatdan boshqasini hosil qiladi, useSpring uni silliqlaydi, useScroll esa scroll holatini motion value qilib beradi. Hajm uchun — LazyMotion va yengil m komponentlari: vazifalar da Motion birinchi yuklashga 40 emas, 14 KB qo'shdi.

Bu darsda

  • useState va motion value farqini render soni bilan o'lchaysiz (31 va 1).
  • useTransform va useSpring bilan qiymatlarni bog'laysiz va silliqlaysiz.
  • useScroll, whileInView va useInView bilan scroll progress chizig'i, parallaks va "ko'ringanda paydo bo'lish" yasaysiz.
  • useAnimate bilan ketma-ket imperativ animatsiya yozasiz.
  • LazyMotion, m, domAnimation/domMax va strict qanday ishlashini o'lchov bilan tushuntirasiz.
  • useReducedMotion va MotionConfig bilan harakat sezgir foydalanuvchini himoya qilasiz.
  • vazifalar dagi Motion qadamini yakunlaysiz: yadro o'lchovi, lint qoidasi, v10 hujjatlari va commit (holat 25).

Oldin bilishingiz kerak: Motion III: chiqish va layout animatsiyalari, useRef I: render chaqirmaydigan xotira, requestAnimationFrame, IntersectionObserver, lazy va code splitting.

1. Nega bu kerak?

«Bahor» menyu sahifasi uzun: 30 ta taom, pastda bron formasi. Jasur aka ikki narsa so'radi. Birinchisi — tepada ingichka chiziq: sahifaning qancha qismi o'qilganini ko'rsatsin. Ikkinchisi — taom rasmlari scroll paytida matndan sekinroq siljisin, "chuqurlik" hissi bo'lsin (parallaks).

Sardor tanish yo'ldan bordi: scroll hodisasida setProgress(…). Kompyuterda ishladi. Arzon telefonda esa scroll "tishlab" qoldi. Sabab: scroll har kadrda hodisa beradi, har setState — butun komponent qayta chiziladi. 30 ta taom kartasi bilan sekundiga 60 marta.

Hayotiy o'xshatish: stadiondagi tablo. Gol urilsa, gazetani qaytadan bosib chiqarmaysiz — tablodagi raqamlar o'zi almashadi. useState — gazeta: har o'zgarishda butun sahifa qayta "bosiladi". Motion value — tablo: faqat raqam o'zgaradi.

Bu dars C qavatli: har da'voni o'lchaymiz — render soni, KB va hatto Motion manba kodidagi bitta shart.

2. Motion value: React'ni chetlab o'tadigan qiymat

2.1 Muammo o'lchovda: 30 kadr — 31 render

Avval useState bilan. Tugma bosilganda 30 kadr davomida (requestAnimationFrame bilan) quti 5 px dan o'ngga siljiydi. Oxirida komponent necha marta chizilganini chiqaramiz (useRef I dagi render hisoblagich):

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

export default function App() {
  const renders = useRef(0);
  renders.current += 1;
  const [x, setX] = useState(0);
  const report = () => console.log("render:", renders.current);
  function push() {
    let frame = 0;
    function step() {
      frame += 1;
      setX(frame * 5);
      if (frame < 30) requestAnimationFrame(step);
      else setTimeout(report, 50);
    }
    requestAnimationFrame(step);
  }
  return (
    <div>
      <button onClick={push}>Sur</button>
      <div style={{ transform: `translateX(${x}px)` }}>Quti</div>
    </div>
  );
}

Konsolda:

text
render: 31

Birinchi chizish + har kadrga bittadan. Bitta qutida bu sezilmaydi. Lekin komponent ichida 30 ta karta bo'lsa — har kadrda 30 ta karta qayta chiziladi.

2.2 useMotionValue — qiymat React'dan tashqarida

Endi xuddi shu siljish motion value bilan:

jsx
import { useRef } from "react";
import { motion, useMotionValue } from "motion/react";

export default function App() {
  const renders = useRef(0);
  renders.current += 1;
  const x = useMotionValue(0);
  function report() {
    const el = document.querySelector("#quti");
    console.log("render:", renders.current);
    console.log("x:", x.get());
    console.log("style:", el.style.transform);
  }
  function push() {
    let frame = 0;
    function step() {
      frame += 1;
      x.set(frame * 5);
      if (frame < 30) requestAnimationFrame(step);
      else setTimeout(report, 50);
    }
    requestAnimationFrame(step);
  }
  return (
    <div>
      <button onClick={push}>Sur</button>
      <motion.div id="quti" style={{ x }}>
        Quti
      </motion.div>
    </div>
  );
}

Konsolda:

text
render: 1
x: 150
style: translateX(150px)

Quti o'sha 150 px ga siljidi, lekin komponent bir marta chizildi. Qanday?

  • useMotionValue(0) — kichik obyekt qaytaradi: get() o'qiydi, set() yozadi. U React state emas: set() qayta chizishni boshlamaydi.
  • style={{ x }} — motion komponenti motion value'ga "obuna" bo'ladi. Qiymat o'zgarsa, Motion elementning style.transform ini to'g'ridan-to'g'ri yangilaydi (keyingi kadrda, bir marta).
  • x — Motion'ning "mustaqil transform" qisqartmasi: translateX ni alohida qiymat sifatida boshqarish mumkin (y, scale, rotate ham shunday).

useRef I darsidagi qoidani eslang: ref o'zgarishi render chaqirmaydi. Motion value — "animatsiya uchun ref": render chaqirmaydi va DOM'ni o'zi yangilaydi.

Tekshirib ko'ring: Komponent ichida <p>{x.get()}</p> yozildi. x.set(150) dan keyin ekranda 150 chiqadimi?

Javob

Yo'q, 0 qoladi. x.get() faqat chizish paytida o'qildi. set() qayta chizishni boshlamaydi, demak JSX qayta hisoblanmaydi. Motion value'ni ko'rsatishning ikki yo'li bor. Birinchisi — motion komponentining style iga berish (raqam emas, xususiyat bo'lsa). Ikkinchisi — useMotionValueEvent bilan o'zgarishni tinglab, DOM'ga o'zingiz yozish (pastdagi «Progress foizi» mashqidagidek). React state'ga qaytarsangiz (setState), yutuq yo'qoladi.

2.3 useTransform: bir qiymatdan boshqasi

Ko'pincha bitta harakatdan bir nechta effekt kerak: quti o'ngga ketgan sari xiralashsin. useTransform bir motion value'ni diapazon bo'yicha boshqasiga aylantiradi: x 0 dan 100 gacha → opacity 1 dan 0.2 gacha.

jsx
import { useRef } from "react";
import {
  motion,
  useMotionValue,
  useMotionValueEvent,
  useTransform,
} from "motion/react";

export default function App() {
  const renders = useRef(0);
  renders.current += 1;
  const x = useMotionValue(0);
  const opacity = useTransform(x, [0, 100], [1, 0.2]);
  useMotionValueEvent(opacity, "change", (v) =>
    console.log("opacity:", v.toFixed(2)),
  );
  function push() {
    x.set(100);
    console.log("x:", x.get(), "| render:", renders.current);
  }
  return (
    <div>
      <button onClick={push}>Sur</button>
      <motion.div style={{ x, opacity }}>Quti</motion.div>
    </div>
  );
}

Konsolda:

text
x: 100 | render: 1
opacity: 0.20

Ikki narsaga e'tibor bering:

  • opacity keyinroq chiqdi. Hosila qiymat darhol emas, Motion'ning keyingi kadr hisobida yangilanadi — x.set() dan keyingi qatorda opacity.get() hali eski bo'ladi.
  • useMotionValueEvent(qiymat, "change", fn) — motion value o'zgarishini tinglash. Effekt va obunani o'zi tozalaydi. "animationStart", "animationComplete" hodisalari ham bor.

Diapazon tashqarisida (x = 150) natija cheklanadi — 0.2 da qoladi (clamp: true standart). Ikki qiymatdan ko'p nuqta ham mumkin: useTransform(x, [-100, 0, 100], [0, 1, 0]) — markazda to'liq, chetlarda shaffof. Qiymatlar raqam bo'lishi shart emas: ranglar (["#fff", "#1d4ed8"]) va birlikli satrlar (["0%", "-30%"]) ham aralashadi. Murakkab hisob uchun funksiya shakli bor: useTransform(() => x.get() * 2) — ichida get() qilingan qiymatlarni Motion o'zi kuzatadi.

2.4 useSpring: silliqlash

useSpring(manba, sozlama) — manbaga prujina bilan ergashadigan motion value. Manba sakrasa, prujinali qiymat yetib olguncha yumshoq "tortiladi" (Motion I dagi type: "spring" — stiffness qattiqlik, damping so'nish).

jsx
import {
  motion,
  useMotionValue,
  useMotionValueEvent,
  useSpring,
} from "motion/react";

export default function App() {
  const x = useMotionValue(0);
  const smooth = useSpring(x, { stiffness: 300, damping: 30 });
  useMotionValueEvent(smooth, "animationComplete", () =>
    console.log("prujina yetib keldi:", Math.round(smooth.get())),
  );
  return (
    <div>
      <button onClick={() => x.set(100)}>Sur</button>
      <motion.div style={{ x: smooth }}>Quti</motion.div>
    </div>
  );
}

Konsolda:

text
prujina yetib keldi: 100

x bir zumda 100 bo'ldi, smooth esa bir necha yuz millisekundda yetib keldi. Scroll progress chizig'i va kursorga ergashuvchi elementlar uchun shu kerak: xom qiymat "titraydi", prujina uni tekislaydi.

3. Scroll'ga bog'langan animatsiya

3.1 useScroll: scroll — to'rtta motion value

useScroll() to'rtta motion value qaytaradi:

Qiymat Ma'nosi
scrollY, scrollX aylantirilgan masofa, px
scrollYProgress, scrollXProgress 0 dan 1 gacha: boshi → oxiri

Argumentsiz — butun sahifa. container: ref — ichki aylantiriladigan blok. target: ref — "shu element ekrandan o'tishi" progressi.

Bu yerda tekshiruvchimiz ojiz: jsdom'da layout yo'q, scroll balandligi 0. Shuning uchun scroll misollari faqat brauzerda — «Ishga tushir» bosing va blokni aylantiring.

3.2 O'qish progress chizig'i

jsx
import { useRef } from "react";
import { motion, useScroll, useSpring } from "motion/react";

const MENU = ["Osh", "Lag'mon", "Manti", "Somsa", "Chuchvara",
  "Shashlik", "Norin", "Dimlama", "Ko'k choy"];

const bar = {
  height: 6,
  background: "#1d4ed8",
  transformOrigin: "0 50%",
};
const box = {
  height: 160,
  overflowY: "auto",
  border: "1px solid #999",
};

export default function App() {
  const ref = useRef(null);
  const { scrollYProgress } = useScroll({ container: ref });
  const scaleX = useSpring(scrollYProgress, {
    stiffness: 200,
    damping: 30,
  });
  return (
    <div>
      <motion.div style={{ ...bar, scaleX }} />
      <div ref={ref} style={box}>
        {MENU.map((t) => (
          <p key={t} style={{ padding: "16px 12px", margin: 0 }}>
            {t}
          </p>
        ))}
      </div>
    </div>
  );
}

Blokni pastga aylantiring — ko'k chiziq chapdan o'ngga to'ladi. Asosiy fikr — scaleX: scrollYProgress: chiziq eni emas, masshtabi o'zgaradi. width ni animatsiya qilsak, har kadrda layout qayta hisoblanardi (Rendering pipeline). transformOrigin: "0 50%" — chap chetdan o'sadi.

Motion 14.0.0 manba kodida (use-scroll.mjs) yana bir optimallashtirish bor. Brauzerda ScrollTimeline bo'lsa va offset berilmagan bo'lsa, scrollYProgress ning animatsiyasi brauzerning native scroll vaqt chizig'iga ulanadi. Bu Scroll-driven animatsiyalar darsidagi animation-timeline: scroll() ning o'zi — JavaScript'siz ishlaydi.

3.3 Parallaks: target va offset

Parallaks — fon oldingi qatlamdan sekinroq siljiydi. Har kartaga o'z progressi kerak: "karta ekranga pastdan kirdi" (0) dan "tepadan chiqib ketdi" (1) gacha. Buni offset aytadi:

jsx
import { useRef } from "react";
import {
  motion,
  useReducedMotion,
  useScroll,
  useTransform,
} from "motion/react";

function Dish({ name, root }) {
  const ref = useRef(null);
  const { scrollYProgress } = useScroll({
    container: root,
    target: ref,
    offset: ["start end", "end start"],
  });
  const y = useTransform(scrollYProgress, [0, 1], [30, -30]);
  const reduce = useReducedMotion();
  return (
    <section ref={ref} style={{ padding: 24, overflow: "hidden" }}>
      <motion.div
        style={{ y: reduce ? 0 : y, fontSize: 40 }}
        aria-hidden="true"
      >
        🍲
      </motion.div>
      <h3>{name}</h3>
    </section>
  );
}

export default function App() {
  const root = useRef(null);
  return (
    <div ref={root} style={{ height: 220, overflowY: "auto" }}>
      {["Osh", "Lag'mon", "Manti", "Somsa"].map((t) => (
        <Dish key={t} name={t} root={root} />
      ))}
    </div>
  );
}

offset: ["start end", "end start"] — ikki juft: "elementning boshi ekranning oxiriga tegdi" (progress 0) va "elementning oxiri ekranning boshiga tegdi" (1). Ya'ni element ko'rinish oynasidan to'liq o'tadi. useTransform shu progressni +30 px dan −30 px gacha siljishga aylantiradi. Ikonka matndan sekinroq "suzadi".

Parallaks — vestibulyar buzilishi bor odamlarda bosh aylanishini qo'zg'atadigan harakatlardan biri (prefers-reduced-motion va harakat etikasi). Shuning uchun useReducedMotion() true bo'lsa, siljish o'chadi. Bu bezak — u yo'q bo'lsa, menyu baribir to'liq.

3.4 CSS yoki Motion?

Scroll-driven animatsiyalar darsida xuddi shunday progress chizig'ini CSS'ning o'zida yozganmiz. Qaysi birini tanlash kerak?

Vaziyat Tanlov
oddiy progress chizig'i, paydo bo'lish CSS animation-timeline — 0 KB JS
scroll qiymatiga mantiq kerak (foiz matni, "oxiriga yetdi" hodisasi) Motion useScroll + useMotionValueEvent
prujina, bir nechta bog'langan qiymat Motion
eski brauzerlarni ham qamrash Motion (CSS'da @supports bilan zaxira)

Qoida: CSS yetsa — CSS. Motion allaqachon loyihada bo'lsa va mantiq kerak bo'lsa — Motion.

4. Ko'ringanda paydo bo'lish: whileInView va useInView

4.1 whileInView

Element ko'rinish oynasiga kirganda animatsiya — eng ko'p so'raladigan effekt. Motion buni ichida IntersectionObserver bilan qiladi:

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

const DISHES = ["Osh", "Lag'mon", "Manti", "Somsa", "Norin"];

export default function App() {
  const root = useRef(null);
  return (
    <div ref={root} style={{ height: 180, overflowY: "auto" }}>
      {DISHES.map((t) => (
        <motion.p
          key={t}
          style={{ padding: 32, margin: 0 }}
          initial={{ opacity: 0, y: 16 }}
          whileInView={{ opacity: 1, y: 0 }}
          viewport={{ root, once: true, amount: 0.6 }}
          onViewportEnter={() => console.log("ko'rindi:", t)}
        >
          {t}
        </motion.p>
      ))}
    </div>
  );
}

viewport sozlamalari:

  • once: true — bir marta. Aks holda element chiqib-kirganda har safar qaytadan animatsiya bo'ladi va bu ko'pincha bezovta qiladi.
  • amount — elementning qancha qismi ko'rinsa, "kirdi" hisoblanadi: "some" (standart), "all" yoki 0…1.
  • root — ichki aylantiriladigan blok (sahifa bo'lsa — kerak emas); margin — chegarani kengaytirish ("0px 0px -100px 0px").

Bu blok sayt belgisi bilan: jsdom'da IntersectionObserver umuman yo'q, tekshiruvchida ReferenceError: IntersectionObserver is not defined bilan yiqiladi.

4.2 useInView — faqat "ko'rindimi?"

Animatsiya emas, mantiq kerak bo'lsa (ko'rinsa — video o'ynasin, statistika hisoblansin): const inView = useInView(ref, { once: true }) — oddiy true/false, bu safar React state. Bu yerda qayta chizish o'rinli: u bir marta o'zgaradi.

Diqqat: initial={{ opacity: 0 }} bilan boshlangan kontent JavaScript ishlaguncha ko'rinmaydi. Sekin internetda yoki skript xato bersa — bo'sh sahifa. Muhim matnni (narx, sarlavha) "paydo bo'luvchi" qilmang, faqat bezak qismlarni. opacity: 0 element ekran o'quvchi uchun ko'rinadi — Malika uni eshitadi. Bu yaxshi, lekin ko'zi ko'radigan foydalanuvchi bilan farq borligini unutmang.

5. useAnimate: ketma-ketlikni kod bilan

Ba'zan animatsiya holatdan emas, hodisadan kelib chiqadi: "Buyurtma berildi" — sarlavha chiqsin, keyin ro'yxat, keyin tugma silkinsin. useAnimate buni imperativ yozadi:

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

export default function App() {
  const [scope, animate] = useAnimate();
  async function order() {
    console.log("boshlandi");
    await animate("h2", { opacity: [0, 1] }, { duration: 0.3 });
    console.log("sarlavha tayyor");
    await animate("li", { x: [-20, 0] }, { duration: 0.2 });
    console.log("ro'yxat tayyor");
  }
  return (
    <div ref={scope}>
      <button onClick={order}>Buyurtma</button>
      <h2>Buyurtma qabul qilindi</h2>
      <ul>
        <li>Osh — 35 000</li>
        <li>Ko'k choy — 5 000</li>
      </ul>
    </div>
  );
}

Konsolda:

text
boshlandi
sarlavha tayyor
ro'yxat tayyor
  • scope — ref. Selektorlar ("h2", "li") faqat shu element ichidan qidiriladi — sahifadagi boshqa h2 larga tegmaydi.
  • animate(…) — va'daga o'xshash obyekt qaytaradi, shuning uchun await ketma-ketlik beradi. Kadrlar massivi ([0, 1]) — "qayerdan qayerga".
  • Komponent o'chsa, scope ichidagi animatsiyalar o'zi to'xtaydi — tozalash kerak emas.

Bir chaqiruvda ketma-ketlik ham bor: animate([["h2", {…}], ["li", {…}, { at: "-0.1" }]]) — at qachon boshlanishini aytadi. Bu GSAP timeline'iga o'xshash g'oya.

useAnimate qulay, lekin unutmang: u Motion'ning to'liq animatsiya kodini talab qiladi. Shuning uchun vazifalar dagi o'chirish animatsiyasi useAnimate emas, brauzerning element.animate i (oldingi dars). Nega — keyingi bo'limda raqam bilan.

6. Hajm: LazyMotion va m

6.1 O'lchov

Motion qulay, lekin bepul emas. Kanon tajribasi bir xil kichik ro'yxatni (uch qator, «Qo'sh» tugmasi) uch xil ulab o'lchadi. vazifalar da esa haqiqiy narx (tajriba/kutubxonalar.txt, tekshiruv-25.txt, gzip):

Motion birinchi yuklashga qancha qo'shadi
  • motion.* (to'liq)layout bilan40,4 KB
  • LazyMotion + domAnimation (sinxron)27,1 KB
  • vazifalar: m.* (yadro kechroq)yadro 14.5 KB — alohida chunk14,2 KB

Manba: Kanon o'lchovi: tajriba/kutubxonalar.txt va QADAMLAR #23–#26 (Vite 8.3.3, Chrome 155, 2026-10-14)

Uchinchi ustun — kanon yo'li. Birinchi yuklashda faqat m.* komponentlari va LazyMotion (14.2 KB). Animatsiya "yadrosi" — alohida 14.5 KB, sahifa chizilgandan keyin.

6.2 Qanday ishlaydi

motion.div — "hamma narsasi bor" komponent: animatsiya, gestures, drag, layout. Siz faqat opacity ni animatsiya qilsangiz ham, butun kod to'plamga kiradi. m.div — bo'sh komponent: tashqi ko'rinishi motion.div bilan bir xil, lekin imkoniyatlari yo'q. Imkoniyatlarni LazyMotion beradi:

jsx
import { useState } from "react";
import { LazyMotion, domAnimation, m } from "motion/react";

export default function App() {
  const [open, setOpen] = useState(false);
  return (
    <LazyMotion features={domAnimation} strict>
      <button onClick={() => setOpen(true)}>Ochish</button>
      {open && (
        <m.p
          initial={{ opacity: 0, y: -8 }}
          animate={{ opacity: 1, y: 0 }}
          onAnimationComplete={() => console.log("kirdi")}
        >
          Osh — 35 000 so'm
        </m.p>
      )}
    </LazyMotion>
  );
}

Konsolda:

text
kirdi

Ekranda:

text
Ochish
Osh — 35 000 so'm

Ikkita tayyor to'plam bor:

To'plam Nimalar bor
domAnimation animate, variants, exit (AnimatePresence), hover/tap/focus gestures, whileInView
domMax yuqoridagilar + drag, layout, layoutId

Loyihada m uchun alohida yo'l ham bor: import * as m from "motion/react-m" — kanon shunday yozadi. Natija oynasida faqat motion/react bor, shuning uchun bu yerda m o'sha paketdan.

6.3 Yadroni kechroq yuklash

features ga obyekt emas, funksiya bersangiz, LazyMotion uni birinchi chizishdan keyin chaqiradi va va'da qaytgach imkoniyatlarni ulaydi. Kanondagi HarakatSozlamasi.tsx aynan shunday:

tsx
const yadro = () =>
  import("./harakat-imkoniyatlari.ts").then((m) => m.default);
// ...
<LazyMotion features={yadro} strict>

harakat-imkoniyatlari.ts — bir qatorli fayl:

ts
// harakat-imkoniyatlari.ts — Motion'ning animatsiya "yadrosi"
// (20/#26).
// LazyMotion buni ALOHIDA chunk sifatida, birinchi chizishdan keyin
// yuklaydi. domAnimation — animate, exit, hover/tap; layout
// animatsiya (domMax) kerak emas
import { domAnimation } from "motion/react";

export default domAnimation;

Dinamik import() — lazy va code splitting darsidagi o'sha mexanizm: Vite bu faylni alohida chunk qiladi. Build chiqishida (tekshiruv-25.txt):

text
dist/assets/harakat-imkoniyatlari-CrNhNRDc.js   37.98 kB │ gzip:  14.60 kB │ map:   232.34 kB

Vite 14.60 kB deydi, kanon o'lchovi esa 14.5 KB (14 478 bayt, gzip -9 — eng kuchli siqish). Farq siqish darajasida, ma'nosi bir xil. Darsda kanon raqami — 14.5.

Yadro kelguncha nima bo'ladi? m.li lar oddiy li kabi chiziladi — animatsiyasiz. Sahifa ochilganda animatsiya kerak emas (eski qatorlar animatsiyasiz). Yangi qator qo'shilguncha esa yadro allaqachon kelgan bo'ladi.

6.4 strict: himoya qayerda ishlaydi — o'lchadik

strict — "shu LazyMotion ichida to'liq motion.* komponenti ishlatilsa, xato ber". Mantiq oddiy: kimdir motion.div yozsa, butun yadro jim birinchi yuklashga qaytib keladi. Kanonning birinchi izohida ham "strict — xato beradi" deyilgan edi. Biz buni ikki muhitda sinab ko'rdik.

Node'da (jsdom bilan, Vitest testlari ishlaydigan muhit) — LazyMotion strict ichida motion.p:

text
onUncaughtError: You have rendered a `motion` component within a `LazyMotion` component. This will break tree shaking. Import and render a `m` component instead.. For more information and steps for solving, visit https://motion.dev/troubleshooting/lazy-strict-mode

Tarjimasi: "Siz LazyMotion ichida motion komponentini chizdingiz. Bu tree shaking'ni buzadi. O'rniga m komponentini import qiling". Xato — React'ning onUncaughtError ushlagichidan.

Brauzerda esa (Vite bilan development build, Chrome 155) — xato yo'q, sahifada <p style="opacity: 1;">Salom</p>. Sababi Motion 14.0.0 ning motion-utils/dist/es/errors.mjs faylida:

js
let invariant = () => { };
if (typeof process !== "undefined" &&
    process.env.NODE_ENV !== "production") {
  // … faqat shu holda xato tashlaydi
}

Brauzerda global process yo'q — invariant bo'sh funksiya bo'lib qoladi. Ya'ni strict faqat Node muhitida (Vitest) qo'riqlaydi.

Kanon shu topilmadan keyin vazifalar ning o'zida sinadi (Chrome 155, natijalar/25-motion-strict.txt). Qator.tsx da vaqtincha m.li o'rniga to'liq motion.li qo'yildi:

Muhit Natija
build (CSP bilan) va dev server konsolda hech narsa yo'q, qator jonlanadi
birinchi yuklash JS (gzip) 201.8 → 227.8 KB (+25.9)
Vitest, App testlari xato yo'q — ular App ni HarakatSozlamasi siz chizadi

Ya'ni xato hech qayerda ko'rinmaydi, faqat ilova jim 26 KB og'irlashadi. Shuning uchun kanon strict ga tayanmadi. U ESLint'ning no-restricted-imports qoidasini (ESLint I: flat config darsidagi qoida sozlamasi) qo'shdi: motion/react dan motion ni import qilish — lint xatosi. Qoidaning o'zi — «Vazifalar qadami» da.

Bu darsning C qavati shu: "himoya bor" degan izohni o'lchaguncha ishonmang.

7. Harakatni kamaytirish: useReducedMotion va MotionConfig

Har animatsiya darsidagi qoida: tizimda "harakatni kamaytirish" yoqilgan bo'lsa (prefers-reduced-motion), harakat yo'qolishi yoki sezilarli kamayishi kerak. Motion'da ikki daraja bor:

  • <MotionConfig reducedMotion="user"> — butun ilova uchun. "user" — tizim sozlamasiga qarab transform va layout animatsiyalarini o'chiradi, opacity va ranglarni qoldiradi. "always" — har doim o'chiradi (sinash uchun qulay), "never" — sozlamaga qaramaydi.
  • useReducedMotion() — bitta komponentda qaror: true/false (yoki matchMedia yo'q muhitda null). Parallaks, avtomatik o'ynaydigan karusel, katta masshtab — bularni o'chiring yoki oddiy so'nish bilan almashtiring.
jsx
const reduce = useReducedMotion();
const variants = reduce
  ? { hidden: { opacity: 0 }, show: { opacity: 1 } }
  : { hidden: { opacity: 0, y: 24 }, show: { opacity: 1, y: 0 } };

vazifalar ikkalasini ham ishlatadi: MotionConfig reducedMotion="user" — umumiy; useQatorHarakati — opacity ni ham o'chirish uchun; chiqishAnimatsiyasi — matchMedia bilan, chunki u Motion emas. Kanon e2e tekshiruvi buni Chrome'da isbotlagan: harakat kamaytirilganda li uchun birorta ham element.animate chaqirilmadi.

8. Tezlik qoidalari — qisqa ro'yxat

  1. Har kadrda o'zgaradigan qiymat — motion value, useState emas (31 va 1 render).
  2. transform va opacity — width, top, margin emas. O'lcham va o'rin kerak bo'lsa — layout (Motion III), u ham transform bilan ishlaydi.
  3. Brauzerga topshiring. Motion 14.0.0 da opacity, transform, filter, clipPath, backgroundColor uchun brauzerning Web Animations API'si ishlatiladi (motion-dom dagi acceleratedValues ro'yxati). Kanon e2e ham Chrome'da qator kirishida element.animate ga faqat opacity va transform borganini ko'rgan.
  4. Scroll — useScroll, onScroll + setState emas.
  5. Hajm — LazyMotion + m, kerak bo'lsa yadro kechroq.
  6. Bezakni o'lchang. Animatsiya bilan INP o'zgardimi? vazifalar da Motion qatorlari oddiy rejimda katak bosishni 16 dan 24 ms ga oshirdi (tuzatish-inp.txt, TEXNIK-QARZ 59) — 200 ms chegarasidan ancha past, qabul qilindi.

9. Vazifalar qadami: LazyMotion yadrosi, v10 hujjatlari va commit (holat 25)

feat/motion branch'idagi Motion qadami shu dars bilan yakunlanadi. Motion I — kirish, HarakatSozlamasi; Motion II — "yangi" qatorlar; Motion III — o'chirishdagi chiqish. Bugun — yadro fayli, o'lchov, v10 hujjatlari, motion importini taqiqlovchi lint qoidasi va bitta umumiy commit.

9.1 Yadro fayli

HarakatSozlamasi.tsx dagi yadro funksiyasi src/ui/harakat-imkoniyatlari.ts ni dinamik import qiladi. Fayl — «Yadroni kechroq yuklash» bo'limidagi bir qatorli export default domAnimation. Nega domMax emas? vazifalar da drag (dnd-kit bor) va layout (Motion III — TEXNIK-QARZ 57) ishlatilmaydi. Kichik to'plam yetadi.

9.2 O'lchov

20-qism brauzer ssenariysining (e2e-20.mjs) Motion bo'limi — haqiqiy Chrome 155 da, CSP ostida (natija-20-25.txt):

text
--- 20/#23–#26 Motion
✅ LazyMotion: animatsiya yadrosi alohida chunk (birinchi chizishdan keyin): [true,0]
✅ ochilishda mavjud qatorlar animatsiyasiz: 0
✅ yangi qator: kirish animatsiyasi — faqat opacity va transform: true
✅ 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
✅ Motion: CSP buzilishi yo'q: []
✅ reduced-motion: qatorda animatsiya yo'q, o'chirilgan qator darhol yo'q: [0,false]

Bugungi mavzu — birinchi qator. Massiv: true — sahifa ochilgandan keyin harakat-imkoniyatlari fayli tarmoqdan yuklangan; 0 — serverdan kelgan HTML'da "harakat" so'zi bir marta ham yo'q. Ya'ni yadro birinchi yuklash ro'yxatida emas. Qolgan qatorlar — oldingi uch darsning xulqi, endi bitta holatda birga.

Narx zanjiri (tekshiruv-22.txt → tekshiruv-25.txt, birinchi yuklash JS gzip): 186.7 → 201.8 KB. Shundan Motion'ning m.* qismi va LazyMotion ~14 KB, qolgani — Rolldown ajratgan umumiy chunk'lar (QADAMLAR, 8-qaror). Yadro — alohida 14.5 KB, keyin.

Nega useAnimate emas. Agar o'chirish animatsiyasi useAnimate bilan yozilganda, u yadroni birinchi yuklashga qaytarardi — 14.5 KB ni "keyin" dan "darhol" ga. Brauzerning element.animate i esa 0 KB. Bitta 150 ms lik siljish uchun 14.5 KB — arzimaydi.

Boshqa tekshiruvlar (tekshiruv-25.txt va brauzer natijalari): npm test — node:test 203/203, Vitest 65/65; lint, format:check, tip, knip — toza. Brauzer: 159, 72, 100 va e2e-20 77. Oxirgisida Ctrl+K ning rus tartibidagi tekshiruvi ham bor (Command palette), usiz 76 edi. 18-qism ssenariysi (e2e-18) bu holatda bir necha marta qayta ishga tushirildi: Radix menyu fokusni setTimeout bilan ko'chiradi, ssenariyga har tugmadan keyin 60 ms kutish qo'shildi. Yakuniy natija — 72/72 (urinishlar natija-18-25-qayta*.txt da). axe — 8 ko'rinishda 0. Ko'rinish solishtiruvi (22 → 25): 236 element, farq 0 — animatsiya tugagach ilova o'sha ko'rinishda.

9.3 v10 hujjatlari

Bu commit bilan vazifalar v10 bo'ladi:

  • Service worker versiyasi — skriptlar/qobiq.js da v9-… → v10-… (va testi qobiq.test.js). Yangi versiya — foydalanuvchilarning eski keshi tozalanadi (PWA qismida chuqur).
  • README — 20-qism imkoniyatlari va narxi: "ochilishda gzip ~202 KB JS + ~4.6 KB CSS … Motion (+14; animatsiya yadrosi yana 14.5 KB — keyin yuklanadi)".
  • AGENTS.md — qoida: "Animatsiya — faqat opacity/transform, motion-safe: yoki useReducedMotion; Motion — LazyMotion + m.* (strict), motion.* import qilinmaydi."
  • TEXNIK-QARZ 57 — ro'yxatda AnimatePresence va layout yo'qligi, sababi o'lchov bilan.

9.4 Lint qoidasi: motion importi taqiqlanadi

«strict: himoya qayerda ishlaydi» bo'limidagi o'lchovdan keyin AGENTS qoidasi mashina tekshiradigan qoidaga aylandi. Qoida: "motion.* import qilinmaydi". eslint.config.js da xususiyat papkalari qoidasi (Katta frontend loyiha arxitekturasi) turgan blokka paths qo'shiladi (kanon, holat 25; izoh qatorlari telefonga sig'ishi uchun qayta bo'lingan):

diff
       "no-restricted-imports": [
         "error",
         {
+          // 20/#26: LazyMotion ichida faqat m.* — to'liq motion.*
+          // butun yadroni birinchi yuklashga qaytaradi. LazyMotion
+          // `strict` buni brauzerda USHLAMAYDI (tekshiruvi faqat
+          // Node'da ishlaydi — HarakatSozlamasi.tsx), shuning
+          // uchun — lint
+          paths: [
+            {
+              name: "motion/react",
+              importNames: ["motion"],
+              message:
+                'LazyMotion: "motion/react-m" dagi m.* ni ishlating.',
+            },
+          ],
           patterns: [

Nega alohida blok emas? Flat config'da bir xil fayllarga ikkinchi no-restricted-imports yozilsa, u birinchisini bosib ketadi — xususiyat papkalari qoidasi jim yo'qolardi. Shuning uchun paths va patterns bitta blokda.

importNames: ["motion"] — motion/react dan faqat motion taqiqlanadi; LazyMotion, MotionConfig, useReducedMotion mumkin. Kanon sinovida import { motion as m } (boshqa nom bilan) ham ushlandi. Vaqtincha Qator.tsx ga motion qo'yib, npm run lint ni ishga tushirsangiz:

text
src\komponentlar\Qator.tsx
  5:10  error  'motion' import from 'motion/react' is restricted. LazyMotion: "motion/react-m" dagi m.* ni ishlating  no-restricted-imports
✖ 1 problem (1 error, 0 warnings)

Nima bo'ldi? Brauzer jim turgan xatoni lint 5-qator 10-ustunda topdi va xabarda nima qilishni aytdi. Narxi — 0 KB (faqat lint vaqtida ishlaydi). HarakatSozlamasi.tsx dagi izoh ham shunga moslab yozilgan (Motion I da ko'rgan izohimiz).

9.5 Commit

bash
git add -A
git commit
text
feat: yangi qator animatsiyasi (Motion, LazyMotion), o'chirishda chiqish; v10 hujjatlari

HarakatSozlamasi (LazyMotion strict + MotionConfig reducedMotion="user"),
m.li faqat ochilgandan keyin qo'shilganlarga, chiqishAnimatsiyasi (WAAPI);
SW VERSIYA v10-; README, AGENTS, TEXNIK-QARZ v10.

Diff: 17 fayl, +283 −18. Halol izoh: kanonda lint qoidasi va HarakatSozlamasi izohining tuzatishi keyinroq, alohida commit bilan kirgan (fix: LazyMotion — motion importi lint bilan taqiqlandi, izohlar va TEXNIK-QARZ 55/56/59 tuzatildi). Siz ularni shu commit'ga qo'shasiz — natija o'sha holat 25. Commit'dan oldin npm run lint toza bo'lsin. Bu — 20-qismdagi oxirgi kanon commit'i. Keyingi to'rt dars (View Transitions, GSAP, Lottie/Rive, 3D) — tajriba va o'lchov, kanon o'zgarmaydi.

10. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
scroll'da "tishlash", Profiler'da yuzlab render scroll qiymati useState da useScroll / useMotionValue
<p>{x.get()}</p> yangilanmaydi motion value render chaqirmaydi style ga bering yoki useMotionValueEvent
useTransform qiymati set dan keyin eski hosila keyingi kadrda hisoblanadi "change" hodisasini tinglang
progress chizig'i sekin, layout ko'p width animatsiyasi scaleX + transformOrigin
whileInView testda IntersectionObserver is not defined jsdom'da yo'q testda shim yoki brauzer testi
m.div animatsiya qilmaydi LazyMotion yo'q yoki features kelmagan ilovani LazyMotion ga o'rang
layout m.div da ishlamaydi domAnimation da layout yo'q domMax
motion.div bilan to'plam katta, xato yo'q strict brauzerda jim lint qoidasi, ko'rik
parallaksdan boshi aylanadi reduced-motion e'tiborsiz useReducedMotion() bilan o'chirish

11. Mashqlar

Mashqlar kurs/mashqlar/20/26-motion-values/ da.

1-mashq (oson): qaysi hook?

  • Sahifa necha foiz o'qilgani →
  • x dan opacity hosil qilish →
  • Sakrovchi qiymatni yumshoq ergashtirish →
  • "Statistika bloki ko'rindimi?" — true/false →
Yechim
  • useScroll — scrollYProgress 0…1.
  • useTransform — bir motion value'dan boshqasi, diapazon bo'yicha.
  • useSpring — manbaga prujina bilan ergashadi.
  • useInView — mantiq uchun true/false (React state). Faqat animatsiya kerak bo'lsa — whileInView.

2-mashq (o'rta): sichqonchaga ergashuvchi nuqta

Sardor «Bahor» logotipi yonida kursorga ergashuvchi nuqta yasadi. Har harakatda butun sarlavha qayta chiziladi. Quyida harakat 20 kadr bilan taqlid qilingan (requestAnimationFrame), oxirida render soni chiqadi. Komponentni motion value'larga o'tkazing: nuqta o'sha joyga borsin, render soni 1 bo'lsin.

Ishora: «useMotionValue — qiymat React'dan tashqarida» bo'limi. x va y uchun ikkita motion value, style={{ x, y }}.

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

export default function App() {
  const renders = useRef(0);
  renders.current += 1;
  const [pos, setPos] = useState({ x: 0, y: 0 });
  const report = () => console.log("render:", renders.current);
  function move() {
    let i = 0;
    function step() {
      i += 1;
      setPos({ x: i * 4, y: i * 2 });
      if (i < 20) requestAnimationFrame(step);
      else setTimeout(report, 50);
    }
    requestAnimationFrame(step);
  }
  return (
    <header>
      <button onClick={move}>Harakat</button>
      <span
        style={{
          display: "inline-block",
          transform: `translate(${pos.x}px, ${pos.y}px)`,
        }}
      >
        •
      </span>
    </header>
  );
}

Konsolda:

text
render: 21
Yechim
jsx
import { useRef } from "react";
import { motion, useMotionValue } from "motion/react";

export default function App() {
  const renders = useRef(0);
  renders.current += 1;
  const x = useMotionValue(0);
  const y = useMotionValue(0);
  const report = () => console.log("render:", renders.current);
  function move() {
    let i = 0;
    function step() {
      i += 1;
      x.set(i * 4);
      y.set(i * 2);
      if (i < 20) requestAnimationFrame(step);
      else setTimeout(report, 50);
    }
    requestAnimationFrame(step);
  }
  return (
    <header>
      <button onClick={move}>Harakat</button>
      <motion.span style={{ display: "inline-block", x, y }}>
        •
      </motion.span>
    </header>
  );
}

Konsolda:

text
render: 1

Haqiqiy loyihada step o'rnida pointermove hodisasi bo'ladi: onPointerMove={(e) => { x.set(e.clientX); y.set(e.clientY); }}. Silliq ergashish uchun — useSpring(x). Render soni baribir 1: hodisa ham, prujina ham React'ni chetlab o'tadi.

3-mashq (qiyin): progress foizi — render'siz

«Yuklash» bosilganda progress motion value'si 0 dan 1 ga 0.3 s da o'tsin (Motion'ning animate(qiymat, maqsad, sozlama) funksiyasi motion value'ni ham animatsiya qiladi). Ekrandagi foiz matni (0% → 100%) yangilanib borsin, lekin komponent qayta chizilmasin. Oxirida konsolga tugadi: 100% | render: 1.

Ishora: foizni useTransform ning funksiya shakli bilan hosil qiling (Math.round), matnni useMotionValueEvent ichida ref orqali yozing (useRef II). animate(...) ni await qilsa bo'ladi.

Yechim
jsx
import { useRef } from "react";
import {
  animate,
  useMotionValue,
  useMotionValueEvent,
  useTransform,
} from "motion/react";

export default function App() {
  const renders = useRef(0);
  renders.current += 1;
  const label = useRef(null);
  const progress = useMotionValue(0);
  const percent = useTransform(() =>
    Math.round(progress.get() * 100),
  );
  useMotionValueEvent(percent, "change", (v) => {
    label.current.textContent = `${v}%`;
  });
  async function load() {
    await animate(progress, 1, { duration: 0.3 });
    const text = label.current.textContent;
    console.log("tugadi:", text, "| render:", renders.current);
  }
  return (
    <div>
      <button onClick={load}>Yuklash</button>
      <p ref={label} role="status">
        0%
      </p>
    </div>
  );
}

Konsolda:

text
tugadi: 100% | render: 1

Ekranda:

text
Yuklash
100%

textContent ni React emas, biz yozdik — bu React boshqaradigan matnni "orqasidan" o'zgartirish. Bu yerda xavfsiz: <p> ichidagi 0% ni React boshqa hech qachon qayta chizmaydi (komponent qayta chizilmaydi). Komponent boshqa sababdan qayta chizilsa, React 0% ni qaytarib qo'yadi. Shuning uchun bu naqsh faqat "tez o'zgaradigan raqam" uchun. role="status" ham ehtiyot bilan: har foizda e'lon — ekran o'quvchi uchun shovqin; haqiqiy ilovada faqat 100% da e'lon qiling.

4-mashq: Vazifalar qadami — o'lchang va commit qiling

feat/motion branch'ida src/ui/harakat-imkoniyatlari.ts ni yarating (agar hali yo'q bo'lsa), skriptlar/qobiq.js da SW versiyasini v10- ga o'zgartiring (testi bilan), README, AGENTS va TEXNIK-QARZ ni yangilang, eslint.config.js ga motion taqig'ini qo'shing. Keyin npm run build ni ishga tushiring va harakat-imkoniyatlari-*.js chunk'ini toping. Keyin vaqtincha Qator.tsx dagi m.li ni motion.li ga almashtiring (import { motion } from "motion/react"), qayta build qiling va birinchi yuklash JS'ini solishtiring. O'zgarishni qaytaring va «Commit» bo'limidagi xabar bilan commit qiling.

Yechim

Kutiladigan natija:

  • Asl holatda harakat-imkoniyatlari-*.js ~14.6 KB gzip — alohida chunk.
  • motion.li bilan birinchi yuklash JS o'sadi: to'liq motion komponenti o'z imkoniyatlarini (layout va drag bilan) asosiy to'plamga olib kiradi. Kanon aynan shu sinovni qilgan: 201.8 → 227.8 KB (+25.9). Sizning raqamingiz shunga yaqin bo'ladi.
  • Brauzerda xato chiqmaydi (strict brauzerda jim — «strict: himoya qayerda ishlaydi» bo'limi). npm run lint esa yiqiladi: 'motion' import from 'motion/react' is restricted — lint qoidasi ishladi.
  • git checkout src/komponentlar/Qator.tsx — asl holatga. Keyin npm test (node 203, Vitest 65), npm run tip, lint, knip — toza bo'lsa, git add -A va commit.

12. Real ishda

  • Landing va marketing sahifalari — scroll progress, parallaks, "ko'ringanda paydo bo'lish". Bunday sahifalarda Motion yoki GSAP deyarli doim bor; Lighthouse ballini kuzating.
  • Dashboard va grafiklar — raqamlarning sanalib o'sishi (animate(0, 1250, { onUpdate })), kursorga ergashuvchi tooltip — motion value bilan, render'siz.
  • Mobil veb — arzon Android telefonlarda useState bilan scroll animatsiyasi birinchi bo'lib "tishlaydi". O'zbekistondagi foydalanuvchilarning katta qismi aynan shunday qurilmalarda.
  • Intervyu savollari: "motion value nima va nega u re-render chaqirmaydi?", "scroll animatsiyasini qanday optimallashtirasiz?", "LazyMotion va m nima uchun?", "whileInView ichida nima ishlaydi?".

Xulosa

  • Motion value — React'dan tashqaridagi qiymat: set() faqat style ni yangilaydi, komponent qayta chizilmaydi (30 kadr: useState — 31 render, motion value — 1).
  • useTransform — diapazon yoki funksiya bilan hosila; useSpring — prujinali ergashish; useMotionValueEvent — o'zgarishni tinglash.
  • useScroll — scrollY va scrollYProgress; container, target, offset bilan; progress — scaleX, parallaks — useTransform. Oddiy holatda CSS animation-timeline yetadi.
  • whileInView / useInView — IntersectionObserver ustida; muhim kontentni yashirmang.
  • LazyMotion + m — 40.4 KB o'rniga 14.2 KB, yadro kechroq. strict faqat Node'da xato beradi, brauzerda jim — himoya no-restricted-imports lint qoidasi.
  • Harakatni kamaytirish: MotionConfig reducedMotion="user" + kerakli joyda useReducedMotion().
  • vazifalar v10: Motion qadami bitta commit'da — birinchi yuklash 201.8 KB, yadro keyin, motion importi lint bilan taqiqlangan, e2e-20 77/77, axe 0.

Keyingi dars: React'da View Transitions: <ViewTransition> va router integratsiyasi — kutubxonasiz, brauzerning o'zi chizadigan o'tishlar React 19.3 da qanday ishlaydi va router bilan to'qnashganda nima bo'ladi.

Manbalar

  • Motion: Motion values, useScroll, useTransform, useSpring, useInView, useAnimate, LazyMotion, MotionConfig — motion.dev/docs (motion 14.0.0, 2026-10)
  • Motion manba kodi (14.0.0): framer-motion/dist/es/value/use-scroll.mjs, motion-utils/dist/es/errors.mjs, motion-dom/dist/es/animation/waapi/utils/accelerated-values.mjs
  • MDN: Scroll-driven animations — developer.mozilla.org/docs/Web/CSS/CSS_scroll-driven_animations
  • web.dev: Animations and performance — web.dev/articles/animations-guide
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Motion'da scroll animatsiya va motion values: useScroll, useTransform, useSpring va LazyMotion — IlmHamroh