Mundarija (38)
- Bu darsda
- 1. Nega bu kerak?
- 2. Motion value: React'ni chetlab o'tadigan qiymat
- 2.1 Muammo o'lchovda: 30 kadr — 31 render
- 2.2 useMotionValue — qiymat React'dan tashqarida
- 2.3 useTransform: bir qiymatdan boshqasi
- 2.4 useSpring: silliqlash
- 3. Scroll'ga bog'langan animatsiya
- 3.1 useScroll: scroll — to'rtta motion value
- 3.2 O'qish progress chizig'i
- 3.3 Parallaks: target va offset
- 3.4 CSS yoki Motion?
- 4. Ko'ringanda paydo bo'lish: whileInView va useInView
- 4.1 whileInView
- 4.2 useInView — faqat "ko'rindimi?"
- 5. useAnimate: ketma-ketlikni kod bilan
- 6. Hajm: LazyMotion va m
- 6.1 O'lchov
- 6.2 Qanday ishlaydi
- 6.3 Yadroni kechroq yuklash
- 6.4 strict: himoya qayerda ishlaydi — o'lchadik
- 7. Harakatni kamaytirish: useReducedMotion va MotionConfig
- 8. Tezlik qoidalari — qisqa ro'yxat
- 9. Vazifalar qadami: LazyMotion yadrosi, v10 hujjatlari va commit (holat 25)
- 9.1 Yadro fayli
- 9.2 O'lchov
- 9.3 v10 hujjatlari
- 9.4 Lint qoidasi: motion importi taqiqlanadi
- 9.5 Commit
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): qaysi hook?
- 2-mashq (o'rta): sichqonchaga ergashuvchi nuqta
- 3-mashq (qiyin): progress foizi — render'siz
- 4-mashq: Vazifalar qadami — o'lchang va commit qiling
- 12. Real ishda
- Xulosa
- Manbalar
Motion'da scroll animatsiya va motion values: useScroll, useTransform, useSpring va LazyMotion
Qisqacha: Har kadrda o'zgaradigan qiymatni (scroll, sichqoncha, progress)
useStateda saqlasangiz, React sekundiga 60 marta qayta chizadi. Motion value (useMotionValue) — React'dan tashqaridagi qiymat. U o'zgarganda faqat elementningstylei yangilanadi, komponent qayta chizilmaydi.useTransformbir qiymatdan boshqasini hosil qiladi,useSpringuni silliqlaydi,useScrollesa scroll holatini motion value qilib beradi. Hajm uchun —LazyMotionva yengilmkomponentlari:vazifalarda Motion birinchi yuklashga 40 emas, 14 KB qo'shdi.
Bu darsda
useStateva motion value farqini render soni bilan o'lchaysiz (31 va 1).useTransformvauseSpringbilan qiymatlarni bog'laysiz va silliqlaysiz.useScroll,whileInViewvauseInViewbilan scroll progress chizig'i, parallaks va "ko'ringanda paydo bo'lish" yasaysiz.useAnimatebilan ketma-ket imperativ animatsiya yozasiz.LazyMotion,m,domAnimation/domMaxvastrictqanday ishlashini o'lchov bilan tushuntirasiz.useReducedMotionvaMotionConfigbilan harakat sezgir foydalanuvchini himoya qilasiz.vazifalardagi 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):
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:
render: 31Birinchi 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:
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:
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 }}—motionkomponenti motion value'ga "obuna" bo'ladi. Qiymat o'zgarsa, Motion elementningstyle.transformini to'g'ridan-to'g'ri yangilaydi (keyingi kadrda, bir marta).x— Motion'ning "mustaqil transform" qisqartmasi:translateXni alohida qiymat sifatida boshqarish mumkin (y,scale,rotateham 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.
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:
x: 100 | render: 1
opacity: 0.20Ikki narsaga e'tibor bering:
opacitykeyinroq chiqdi. Hosila qiymat darhol emas, Motion'ning keyingi kadr hisobida yangilanadi —x.set()dan keyingi qatordaopacity.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).
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:
prujina yetib keldi: 100x 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
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:
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:
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: 0element 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:
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:
boshlandi
sarlavha tayyor
ro'yxat tayyorscope— ref. Selektorlar ("h2","li") faqat shu element ichidan qidiriladi — sahifadagi boshqah2larga tegmaydi.animate(…)— va'daga o'xshash obyekt qaytaradi, shuning uchunawaitketma-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.* (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:
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:
kirdiEkranda:
Ochish
Osh — 35 000 so'mIkkita 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:
const yadro = () =>
import("./harakat-imkoniyatlari.ts").then((m) => m.default);
// ...
<LazyMotion features={yadro} strict>harakat-imkoniyatlari.ts — bir qatorli fayl:
// 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):
dist/assets/harakat-imkoniyatlari-CrNhNRDc.js 37.98 kB │ gzip: 14.60 kB │ map: 232.34 kBVite 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:
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-modeTarjimasi: "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:
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 qarabtransformvalayoutanimatsiyalarini o'chiradi,opacityva ranglarni qoldiradi."always"— har doim o'chiradi (sinash uchun qulay),"never"— sozlamaga qaramaydi.useReducedMotion()— bitta komponentda qaror:true/false(yokimatchMediayo'q muhitdanull). Parallaks, avtomatik o'ynaydigan karusel, katta masshtab — bularni o'chiring yoki oddiy so'nish bilan almashtiring.
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
- Har kadrda o'zgaradigan qiymat — motion value,
useStateemas (31 va 1 render). transformvaopacity—width,top,marginemas. O'lcham va o'rin kerak bo'lsa —layout(Motion III), u hamtransformbilan ishlaydi.- Brauzerga topshiring. Motion 14.0.0 da
opacity,transform,filter,clipPath,backgroundColoruchun brauzerning Web Animations API'si ishlatiladi (motion-domdagiacceleratedValuesro'yxati). Kanon e2e ham Chrome'da qator kirishidaelement.animatega faqatopacityvatransformborganini ko'rgan. - Scroll —
useScroll,onScroll+setStateemas. - Hajm —
LazyMotion+m, kerak bo'lsa yadro kechroq. - Bezakni o'lchang. Animatsiya bilan INP o'zgardimi?
vazifalarda 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):
--- 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.jsdav9-…→v10-…(va testiqobiq.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
AnimatePresencevalayoutyo'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):
"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:
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
git add -A
git commitfeat: 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 →
xdanopacityhosil qilish →- Sakrovchi qiymatni yumshoq ergashtirish →
- "Statistika bloki ko'rindimi?" —
true/false→
Yechim
useScroll—scrollYProgress0…1.useTransform— bir motion value'dan boshqasi, diapazon bo'yicha.useSpring— manbaga prujina bilan ergashadi.useInView— mantiq uchuntrue/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 }}.
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:
render: 21Yechim
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:
render: 1Haqiqiy 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
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:
tugadi: 100% | render: 1Ekranda:
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.libilan birinchi yuklash JS o'sadi: to'liqmotionkomponenti 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 (
strictbrauzerda jim — «strict: himoya qayerda ishlaydi» bo'limi).npm run lintesa yiqiladi:'motion' import from 'motion/react' is restricted— lint qoidasi ishladi. git checkout src/komponentlar/Qator.tsx— asl holatga. Keyinnpm test(node 203, Vitest 65),npm run tip,lint,knip— toza bo'lsa,git add -Ava 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
useStatebilan 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?", "
LazyMotionvamnima uchun?", "whileInViewichida nima ishlaydi?".
Xulosa
- Motion value — React'dan tashqaridagi qiymat:
set()faqatstyleni 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—scrollYvascrollYProgress;container,target,offsetbilan; progress —scaleX, parallaks —useTransform. Oddiy holatda CSSanimation-timelineyetadi.whileInView/useInView—IntersectionObserverustida; muhim kontentni yashirmang.LazyMotion+m— 40.4 KB o'rniga 14.2 KB, yadro kechroq.strictfaqat Node'da xato beradi, brauzerda jim — himoyano-restricted-importslint qoidasi.- Harakatni kamaytirish:
MotionConfig reducedMotion="user"+ kerakli joydauseReducedMotion(). vazifalarv10: Motion qadami bitta commit'da — birinchi yuklash 201.8 KB, yadro keyin,motionimporti 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
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!