IlmHamroh
JavaScript Full-stack/13-qism. Brauzer API'lari, performans va xavfsizlik10/32-dars23 daqiqa
Mundarija (38)

JavaScript Web Animations API: element.animate() bilan animatsiyani boshqarish

Qisqacha: Web Animations API — CSS animatsiyasini JavaScript'dan yaratish va boshqarish usuli. element.animate(keyframes, options) animatsiyani darhol boshlaydi va Animation obyektini qaytaradi: u bilan pauza qilish, orqaga aylantirish, tezlikni o'zgartirish va await animation.finished bilan tugashini kutish mumkin. Animatsiya CSS'dagidek brauzer ichida ishlaydi, transform va opacity esa asosiy thread band bo'lsa ham silliq qoladi.

Bu darsda

  • element.animate() bilan keyframe'lar va sozlamalar (duration, easing, iterations, fill) beriladigan animatsiya yoza olasiz.
  • Animation obyektini boshqarasiz: play, pause, reverse, cancel, finish, playbackRate, currentTime.
  • finished promise bilan "animatsiya tugagach — keyingi ish" ketma-ketligini qurasiz.
  • getAnimations() bilan CSS animatsiyalarini ham topib boshqara olasiz.
  • document.startViewTransition() ning JavaScript tomonini — ready, finished, skipTransition() — ishlata olasiz.

Oldin bilishingiz kerak: @keyframes va animation xususiyatlari, View Transitions, requestAnimationFrame, matchMedia va foydalanuvchi sozlamalari, async / await.

1. Nega bu kerak?

«Bahor» menyusida har taom kartasida «Savatga» tugmasi bor. Jasur aka shunday xohlaydi: tugma bosilganda savat belgisi biroz "sakrasin" — mehmon taom qo'shilganini sezsin.

Sardor @keyframes darsidagi bilim bilan yozdi: CSS'da .bounce klassi va animatsiya. JavaScript esa bosishda klassni qo'shadi. Birinchi bosish ishladi. Ikkinchisi — yo'q: klass allaqachon turibdi, brauzer "hech narsa o'zgarmadi" deb animatsiyani qayta boshlamaydi. Sardor animationend da klassni olib tashlaydigan kod qo'shdi. Keyin tez-tez bosilganda animatsiya o'rtada uzilib qolishini ko'rdi.

Muammo shundaki, CSS animatsiyasi holat bilan boshqariladi: klass bor — animatsiya bor. "Hozir boshla", "to'xta", "orqaga qayt", "tugadimi?" degan buyruqlar yo'q. Ularni klasslar bilan taqlid qilish kerak bo'ladi.

requestAnimationFrame darsida boshqa yo'lni ko'rdik: har kadrda qiymatni o'zimiz hisoblaymiz. Lekin u har kadrda JavaScript'ni ishga tushiradi. Oddiy "sakrash" uchun bu ortiqcha.

O'rtadagi yo'l — Web Animations API (qisqasi WAAPI). U CSS animatsiyasining dvigatelini JavaScript'ga ochadi. Animatsiyani bitta qatorda boshlaysiz, qolganini brauzer o'zi chizadi. Qo'lingizda esa pult qoladi.

2. Birinchi animatsiya: element.animate()

2.1 Ikki argument

Sintaksis:

js
const animation = element.animate(keyframes, options);
  • keyframes — keyframe'lar (kalit kadrlar): animatsiya qaysi holatlardan o'tadi. Bu CSS'dagi @keyframes ichidagi from, 50%, to bloklarining JavaScript ko'rinishi.
  • options — sozlamalar: davomiyligi, tezlanish egri chizig'i, necha marta takrorlanishi.
  • Qaytgan qiymat — Animation obyekti, ya'ni o'sha "pult".

Chaqirilgan zahoti animatsiya boshlanadi. Klass qo'shish, animationend kutish, klassni olib tashlash — hech biri kerak emas.

2.2 «Bahor»: savat sakraydi

Tugmani bir necha marta, tez-tez bosib ko'ring:

html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  .bar { display: flex; align-items: center; gap: 1rem; }
  .cart { font-size: 2.5rem; display: inline-block; }
  .count { font: 700 1.25rem/1 ui-monospace, monospace; }
  button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<div class="bar">
  <span class="cart" id="cart" aria-hidden="true">🛒</span>
  <span class="count" id="count">0</span>
  <button id="add" type="button">Oshni savatga</button>
</div>
<script>
  const cart = document.getElementById("cart");
  const count = document.getElementById("count");
  let items = 0;

  document.getElementById("add").addEventListener("click", () => {
    items++;
    count.textContent = items;
    cart.animate(
      [
        { transform: "translateY(0) scale(1)" },
        { transform: "translateY(-12px) scale(1.15)" },
        { transform: "translateY(0) scale(1)" },
      ],
      { duration: 300, easing: "ease-out" },
    );
  });
</script>

Qatorma-qator:

  • Birinchi argument — uchta keyframe'li ro'yxat: joyida → 12 px yuqorida va biroz katta → yana joyida. Keyframe'lar orasida brauzer o'zi oraliq kadrlarni chizadi.
  • duration: 300 — davomiyligi millisekundda (CSS'da 0.3s yozardik, bu yerda faqat son).
  • easing: "ease-out" — tezlanish: boshida tez, oxirida sekin. CSS'dagi qiymatlarning hammasi ishlaydi: ease-in-out, cubic-bezier(...), steps(4), linear(...).

Har bosish yangi animatsiya yaratadi va u eski animatsiyaning ustidan, boshidan boshlanadi. Klasslar bilan bo'lgan muammo yo'q. Biz tekshirdik: tugma uch marta bosilganda hisoblagich 3 ni ko'rsatdi va konsolda bitta ham xato chiqmadi.

Maslahat: Animatsiya faqat bezak — u tugamasa ham ma'lumot to'g'ri bo'lishi kerak. Shuning uchun count.textContent animatsiyadan oldin, o'zi alohida yangilanadi.

2.3 Keyframe'larni yozishning ikki shakli

Yuqoridagi shakl — obyektlar ro'yxati: har obyekt bitta kadr. Ikkinchi shakl — bitta obyekt, har xususiyatga qiymatlar ro'yxati:

js
// 1-shakl: kadrlar ro'yxati
card.animate(
  [{ opacity: 0, transform: "scale(0.9)" },
   { opacity: 1, transform: "scale(1)" }],
  400,
);

// 2-shakl: xususiyat → qiymatlar
card.animate(
  { opacity: [0, 1], transform: ["scale(0.9)", "scale(1)"] },
  400,
);

Ikkalasi bir xil animatsiya. Ikkinchi argument bu yerda shunchaki son — bu { duration: 400 } ning qisqa yozuvi.

Yana uch qoida:

  • Xususiyat nomlari camelCase'da: backgroundColor, borderRadius — xuddi element.style.backgroundColor dagi kabi (classList va stillar darsi). Qiymatlar esa CSS'dagidek satr: "translateX(10px)", "#2e7d32".
  • offset — kadrning vaqtdagi o'rni (0 dan 1 gacha), CSS'dagi foiz o'rniga: { opacity: 1, offset: 0.8 } — "80% da".
  • Bitta kadr ham yetadi. box.animate({ transform: "scale(1.5)" }, 200) — brauzer boshlang'ich holatni elementning hozirgi uslubidan oladi. Chrome 154 da getKeyframes() ham bitta kadr qaytardi.

Tekshirib ko'ring: card.animate({ opacity: [0, 1] }, 0.5) yozildi. Animatsiya qancha davom etadi?

Javob

0,5 millisekund — ya'ni ko'zga ko'rinmaydi. Ikkinchi argument son bo'lsa, u millisekund. CSS'dagi 0.5s ni ko'chirganda shu xato tez-tez uchraydi. To'g'risi — 500.

3. Sozlamalar (options)

3.1 Asosiylari

Sozlama Ma'nosi CSS'dagi juft
duration davomiyligi, ms animation-duration
easing tezlanish egri chizig'i animation-timing-function
delay / endDelay boshidan oldin / oxiridan keyin kutish, ms animation-delay
iterations necha marta (Infinity — cheksiz) animation-iteration-count
direction "normal", "reverse", "alternate" animation-direction
fill tugagach holat qoladimi animation-fill-mode

Yana biri — id: animatsiyaga nom. Keyin uni getAnimations() ro'yxatida topish oson.

Masalan, «Bahor» logotipidagi choynakdan bug' cheksiz "nafas oladi":

js
steam.animate(
  {
    opacity: [0.3, 1],
    transform: ["translateY(0)", "translateY(-6px)"],
  },
  { duration: 1200, iterations: Infinity, direction: "alternate",
    easing: "ease-in-out", id: "steam" },
);

alternate — bir marta oldinga, bir marta orqaga. Shuning uchun bug' sakramaydi, silliq tebranadi.

3.2 fill: tugagach nima bo'ladi

Sukut bo'yicha animatsiya tugagach element avvalgi uslubiga qaytadi. opacity 1 dan 0 ga borsa, oxirida element yana ko'rinib qoladi. Bu CSS'dagi bilan bir xil.

fill: "forwards" oxirgi kadrni ushlab turadi. Oson yechimga o'xshaydi, lekin tuzog'i bor. Biz Chrome 154 da sinadik: opacity ni fill: "forwards" bilan 0.2 ga olib bordik, keyin box.style.opacity = "1" yozdik. Ekrandagi qiymat baribir 0.2 qoldi. Animatsiya uslubdan ustun turadi va u hali ham "tirik".

To'g'ri yo'l — yakuniy holatni o'zingiz yozish:

js
// ✅ 1-usul: avval yakuniy uslub, keyin animatsiya
toast.style.opacity = "0";
toast.animate({ opacity: [1, 0] }, 300);

// ✅ 2-usul: tugagach animatsiya qiymatini uslubga "muhrlash"
const anim = toast.animate(
  { opacity: [1, 0] },
  { duration: 300, fill: "forwards" },
);
await anim.finished;
anim.commitStyles(); // hozirgi qiymat → style atributi
anim.cancel();       // animatsiya o'chadi, uslub qoladi

commitStyles() — animatsiyaning shu paytdagi qiymatlarini elementning style atributiga yozadi. Keyin cancel() animatsiyani olib tashlaydi. Sinovda shundan keyin getAnimations() bo'sh ro'yxat qaytardi, opacity esa 0.2 da qoldi — endi u oddiy uslub va uni o'zgartirsa bo'ladi.

Maslahat: Bitta xususiyatga fill: "forwards" li animatsiyalarni ketma-ket ko'p qo'ysangiz, Chrome eskilarini o'zi tozalaydi. Yangisi eskisini to'liq yopsa, eskisi o'chiriladi (replaceState: "removed"). Sinovda uchta shunday animatsiyadan keyin ro'yxatda bittasi qoldi.

4. Pult: Animation obyekti

4.1 Metodlar va xususiyatlar

animate() qaytargan obyekt bilan:

  • pause() — to'xtatib turish, play() — davom ettirish.
  • reverse() — yo'nalishni teskari qilish (yarmida bo'lsa, o'sha joydan orqaga ketadi).
  • finish() — darhol oxiriga sakrash, cancel() — butunlay bekor qilish va boshlang'ich holatga qaytish.
  • playbackRate — tezlik: 2 — ikki baravar tez, 0.5 — sekin, -1 — orqaga.
  • currentTime — animatsiyaning qaysi millisekundida turgani. Uni o'qish ham, yozish ham mumkin.
  • playState — holat: "running", "paused", "finished" yoki "idle".

Holatlar orasidagi o'tishlar:

stateDiagram-v2
  [*] --> running: animate()
  running --> paused: pause()
  paused --> running: play()
  running --> finished: vaqt tugadi yoki finish()
  finished --> running: play() yoki reverse()
  running --> idle: cancel()
  paused --> idle: cancel()
  finished --> idle: cancel()

Nimaga qarang: finished oxirgi holat emas — play() yoki reverse() uni qaytadan ishga tushiradi. idle esa "animatsiya yo'q": element boshlang'ich uslubida.

4.2 O'zingiz boshqaring

Osh tayyorlanish chizig'i 4 soniyada to'ladi. Tugmalar va surgich bilan boshqaring:

html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  .track { height: 1rem; background: #eef3ee; border-radius: 1rem;
    overflow: hidden; }
  #bar { height: 100%; background: #2e7d32; border-radius: 1rem;
    transform-origin: left; transform: scaleX(0); }
  .controls { display: flex; flex-wrap: wrap; gap: 0.5rem;
    margin: 0.75rem 0; }
  button { font: inherit; padding: 0.4rem 0.8rem; }
  input { width: 100%; }
</style>
<p>Osh tayyorlanmoqda… <output id="state">running</output></p>
<div class="track"><div id="bar"></div></div>
<div class="controls">
  <button id="play" type="button">Davom</button>
  <button id="pause" type="button">Pauza</button>
  <button id="reverse" type="button">Orqaga</button>
  <button id="fast" type="button">×2 tezlik</button>
  <button id="finish" type="button">Tugat</button>
</div>
<label for="time">Vaqt (ms):</label>
<input id="time" type="range" min="0" max="4000" value="0">
<script>
  const stateOut = document.getElementById("state");
  const timeInput = document.getElementById("time");
  const animation = document.getElementById("bar").animate(
    { transform: ["scaleX(0)", "scaleX(1)"] },
    { duration: 4000, fill: "forwards" },
  );

  function show() {
    stateOut.textContent = animation.playState;
    timeInput.value = animation.currentTime;
    requestAnimationFrame(show);
  }
  requestAnimationFrame(show);

  const actions = {
    play: () => animation.play(),
    pause: () => animation.pause(),
    reverse: () => animation.reverse(),
    fast: () => { animation.playbackRate *= 2; },
    finish: () => animation.finish(),
  };
  for (const [id, action] of Object.entries(actions)) {
    document.getElementById(id).addEventListener("click", () => {
      action();
      // tezlik keyingi kadrda o'zgaradi — ready kutiladi
      animation.ready.then(() => {
        const { playState, playbackRate } = animation;
        console.log(`${id}: ${playState}, tezlik ${playbackRate}`);
      });
    });
  }

  timeInput.addEventListener("input", () => {
    animation.pause();
    animation.currentTime = Number(timeInput.value);
  });
</script>

Nimani ko'rasiz:

  • «Pauza» — chiziq to'xtaydi, holat paused. «Davom» — davom etadi.
  • «Orqaga» — chiziq shu joyidan bo'shay boshlaydi. Ichkarida playbackRate ishorasi almashadi: konsolda tezlik -1.
  • «×2 tezlik» — ikki baravar tez. Ikki marta bossangiz — to'rt baravar.
  • Surgichni sursangiz, animatsiya pauzaga o'tadi va siz uni qo'lda "aylantirasiz". Video pleyerdagi kabi — bu currentTime ga yozish.

Bu yerda fill: "forwards" o'rinli: chiziq tugagach to'la qolishi kerak va biz unga boshqa uslub yozmaymiz.

Biz bosib sinadik: «Pauza» → pause: paused, tezlik 1, «Orqaga» → reverse: running, tezlik -1, «Tugat» → finish: finished, tezlik -1. Oxirgisiga e'tibor bering: orqaga ketayotgan animatsiyada finish() uni boshiga — bo'sh chiziqqa — olib boradi. "Oxir" — yo'nalishga qarab.

show funksiyasi rAF bilan har kadrda holatni ko'rsatadi. Animatsiyaning o'zi esa rAF'siz ishlaydi — uni brauzer chizadi.

Konsolga yozishdan oldin nega animation.ready kutiladi? reverse() va play() tezlikni darhol emas, keyingi kadrda almashtiradi. Biz avval ready siz yozib ko'rdik: «Orqaga» dan keyin konsolda hali eski tezlik 1 chiqdi. ready — "kutayotgan o'zgarishlar kuchga kirdi" degan promise.

Tekshirib ko'ring: Animatsiya 4 000 ms li, hozir currentTime 1 000 da. reverse() chaqirildi. Chiziq to'liq bo'shashiga qancha vaqt kerak?

Javob

1 000 ms. reverse() animatsiyani boshidan emas, turgan joyidan orqaga yuritadi. Chiziq to'rtdan bir qismi to'lgan — shuncha vaqtda bo'shaydi.

5. finished: tugashini kutish

5.1 Promise bilan ketma-ketlik

Ko'p hollarda animatsiyadan keyin nimadir qilish kerak: xabarni o'chirish, keyingi animatsiyani boshlash, tugmani yoqish. Buning uchun Animation da finished xususiyati bor. U — Promise: animatsiya tugaganda bajariladi.

«Bahor»da buyurtma tayyor bo'lganda pastdan xabar (toast) chiqadi, 1,5 soniya turadi va yo'qoladi:

html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem;
    min-height: 9rem; }
  .toast { position: fixed; left: 1rem; bottom: 1rem; margin: 0;
    padding: 0.6rem 1rem; border-radius: 0.5rem;
    background: #1b3d1f; color: #fff; }
  button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<button id="ready" type="button">Buyurtma tayyor</button>
<script>
  async function showToast(text) {
    const toast = document.createElement("p");
    toast.className = "toast";
    toast.setAttribute("role", "status");
    toast.textContent = text;
    document.body.append(toast);

    const slide = [
      { transform: "translateY(150%)", opacity: 0 },
      { transform: "translateY(0)", opacity: 1 },
    ];
    await toast.animate(slide, { duration: 250, easing: "ease-out" })
      .finished;
    await toast.animate(slide, {
      duration: 250, delay: 1500, direction: "reverse",
      fill: "forwards",
    }).finished;
    toast.remove();
    console.log(`Toast olib tashlandi: ${text}`);
  }

  document.getElementById("ready").addEventListener("click", () => {
    showToast("№ 17 buyurtma tayyor — osh, 2 porsiya");
  });
</script>

Kod yuqoridan pastga o'qiladi: chiq → kut → 1,5 soniyadan keyin orqaga qayt → kut → o'chir. Callback'lar yo'q, xuddi async / await darsidagidek. Ikkinchi animatsiyada direction: "reverse" — o'sha keyframe'lar teskari tartibda. fill: "forwards" esa toast o'chirilguncha ko'rinmay tursin uchun: oradagi bir lahzada u qayta "miltillab" ketmasin.

Tugmani bosgach, taxminan 2 soniyada konsolda:

text
Toast olib tashlandi: № 17 buyurtma tayyor — osh, 2 porsiya

finished bilan bir qatorda eski uslubdagi onfinish xususiyati ham bor: anim.onfinish = () => {...}. U ham ishlaydi, lekin await bilan ketma-ketlik yozish ancha qulay.

5.2 cancel() va AbortError

Animatsiya bekor qilinsa, finished bajarilmaydi — u xato bilan rad etiladi (reject). Chrome 154 dagi xato:

text
AbortError: The user aborted a request.

Tarjimasi: "Foydalanuvchi so'rovni to'xtatdi". Matn biroz g'alati — u AbortController darsidagi fetch xatosi bilan bir xil. Brauzer ikkalasiga bitta DOMException turini ishlatadi. Muhimi — nomi: AbortError.

Agar await anim.finished ni try/catch siz yozsangiz va animatsiya bekor qilinsa, natija oynasining konsolida shunday chiqadi:

text
Uncaught (in promise) AbortError: The user aborted a request.

Bu ushlanmagan xato. Bekor qilinishi mumkin bo'lgan animatsiyani shunday kuting:

js
try {
  await anim.finished;
  toast.remove();
} catch (error) {
  if (error.name !== "AbortError") throw error;
  // bekor qilindi — hech narsa qilmaymiz
}

AbortError dan boshqa xatolarni yutib yubormaymiz — ular qayta otiladi.

Tekshirib ko'ring: Toast chiqib turganda foydalanuvchi uni yopish tugmasini bosdi va kod anim.finish() chaqirdi. await anim.finished dan keyingi toast.remove() bajariladimi? anim.cancel() bo'lsa-chi?

Javob

finish() — ha: animatsiya darhol oxiriga yetadi va finished bajariladi. cancel() — yo'q: finished AbortError bilan rad etiladi, remove() ga navbat kelmaydi (catch'ga o'tadi). Toastni yopish uchun finish() to'g'riroq.

6. getAnimations(): CSS animatsiyalarini ham boshqarish

6.1 Bitta dvigatel

WAAPI CSS animatsiyalari bilan bitta dvigatelda ishlaydi. Shuning uchun CSS'da yozilgan @keyframes animatsiyasi va transition ham JavaScript'dan Animation obyekti bo'lib ko'rinadi.

  • element.getAnimations() — shu elementdagi barcha faol animatsiyalar.
  • document.getAnimations() — butun sahifadagilar.

CSS animatsiyasi CSSAnimation turida keladi va unda animationName bor, transition esa CSSTransition turida, transitionProperty bilan. Ikkalasida ham pause(), playbackRate, finished — hammasi bor.

6.2 «Bahor»: hamma narsani to'xtatish

Sahifada CSS bilan aylanuvchi yuklanish belgisi va JS bilan "nafas oluvchi" bug' bor. Bitta tugma hammasini to'xtatadi:

html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  .row { display: flex; align-items: center; gap: 1.5rem;
    margin-bottom: 1rem; }
  .spinner { width: 2rem; height: 2rem; border-radius: 50%;
    border: 4px solid #cfe3cf; border-top-color: #2e7d32;
    animation: spin 0.8s linear infinite; }
  @keyframes spin { to { transform: rotate(1turn); } }
  #steam { font-size: 2rem; }
  button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<div class="row">
  <div class="spinner" aria-hidden="true"></div>
  <span id="steam" aria-hidden="true">♨️</span>
</div>
<button id="toggle" type="button" aria-pressed="false">
  Harakatni to'xtatish
</button>
<script>
  document.getElementById("steam").animate(
    { opacity: [0.3, 1] },
    { duration: 900, iterations: Infinity, direction: "alternate",
      id: "steam" },
  );

  const toggle = document.getElementById("toggle");
  let paused = false;

  toggle.addEventListener("click", () => {
    paused = !paused;
    toggle.setAttribute("aria-pressed", String(paused));
    for (const animation of document.getAnimations()) {
      if (paused) animation.pause();
      else animation.play();
      const name = animation.animationName ?? animation.id;
      console.log(`${animation.constructor.name} «${name}»:`
        + ` ${animation.playState}`);
    }
  });
</script>

Tugmani bosganingizda konsolga shu ikki qator chiqadi (bosib sinalgan):

text
CSSAnimation «spin»: paused
Animation «steam»: paused

animation.animationName ?? animation.id — CSS animatsiyasida nomi @keyframes dan keladi, JS animatsiyasida esa biz bergan id dan. Bu tugma — harakatdan bezovta bo'ladigan odamlar uchun "pauza" tugmasining eng sodda ko'rinishi.

6.3 CSS animatsiyasi tugashini kutish

getAnimations() ning eng ko'p ishlatiladigan joyi — elementni CSS animatsiyasidan keyin o'chirish. Animatsiya CSS'da yozilgan bo'lsa ham, uni JavaScript'da kutish mumkin:

js
card.classList.add("leaving"); // CSS'da chiqish animatsiyasi
await Promise.all(
  card.getAnimations().map((animation) => animation.finished),
);
card.remove();

Promise.all (Promise kombinatorlari) — hamma animatsiyalar tugashini kutadi. Klassda animatsiya bo'lmasa (masalan, foydalanuvchi harakatni o'chirgan va CSS uni none qilgan), ro'yxat bo'sh bo'ladi va Promise.all([]) darhol bajariladi. Element baribir o'chadi — animationend kutib qolib ketish xavfi yo'q.

7. View Transitions — JavaScript tomoni

7.1 startViewTransition qaytaradigan obyekt

View Transitions darsida document.startViewTransition(yangila) ni ishlatgan edik: brauzer eski va yangi ko'rinishni suratga olib, ular orasida animatsiya qiladi. O'shanda "o'tish tugashini kutish va uni o'tkazib yuborish — JavaScript tomonida" degan edik.

startViewTransition ViewTransition obyektini qaytaradi. Unda uchta promise va bitta metod bor:

  • updateCallbackDone — siz bergan yangilash funksiyasi bajarildi (DOM o'zgardi).
  • ready — suratlar olindi, ::view-transition-… pseudo-elementlari yaratildi, animatsiya boshlanmoqda.
  • finished — animatsiya tugadi, pseudo-elementlar o'chirildi.
  • skipTransition() — animatsiyani o'tkazib yuborish: DOM baribir yangilanadi, faqat chiroyli o'tish bo'lmaydi.
sequenceDiagram
  participant K as Kodingiz
  participant B as Brauzer
  K->>B: startViewTransition(update)
  B->>B: eski ko'rinish surati
  B->>K: update() chaqiriladi
  Note over K,B: updateCallbackDone
  B->>B: yangi surat, pseudo-elementlar
  Note over K,B: ready — animate() shu yerda
  B->>B: animatsiya
  Note over K,B: finished

7.2 ready + animate(): bosilgan joydan tarqaladigan doira

ready dan keyin pseudo-elementlar mavjud. Ularni WAAPI bilan animatsiya qilish mumkin. Buning uchun options da pseudoElement beriladi. Klassik misol — tema almashganda yangi rang bosilgan nuqtadan doira bo'lib tarqaladi:

html
<style>
  :root { color-scheme: light; }
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 0;
    padding: 1rem; min-height: 12rem; background: #fdfbf6;
    color: #1d2a1f; }
  body.dark { background: #16201a; color: #e9f1ea; }
  button { font: inherit; padding: 0.4rem 0.8rem; }
  ::view-transition-old(root), ::view-transition-new(root) {
    animation: none; mix-blend-mode: normal;
  }
</style>
<h3>«Bahor» menyusi</h3>
<p>Osh — 35 000 so'm</p>
<button id="theme" type="button">Temani almashtirish</button>
<script>
  const button = document.getElementById("theme");

  button.addEventListener("click", async (event) => {
    const update = () => document.body.classList.toggle("dark");
    if (!document.startViewTransition) {
      update();
      return;
    }
    const transition = document.startViewTransition(update);
    await transition.updateCallbackDone;
    console.log("1) DOM yangilandi");
    await transition.ready;
    console.log("2) suratlar tayyor");

    const radius = Math.hypot(innerWidth, innerHeight);
    const at = `at ${event.clientX}px ${event.clientY}px`;
    document.documentElement.animate(
      { clipPath: [`circle(0 ${at})`, `circle(${radius}px ${at})`] },
      { duration: 500, easing: "ease-in",
        pseudoElement: "::view-transition-new(root)" },
    );
    await transition.finished;
    console.log("3) o'tish tugadi");
  });
</script>

Qatorma-qator:

  • CSS'dagi animation: none — brauzerning standart "eriydigan" animatsiyasini o'chiramiz, o'rniga o'zimiznikini qo'yamiz.
  • await transition.ready — shundan keyin ::view-transition-new(root) (yangi ko'rinish surati) bor.
  • Math.hypot(innerWidth, innerHeight) — oyna diagonali: doira butun oynani yopishi uchun yetarli radius.
  • clipPath: circle(...) — yangi surat avval bosilgan nuqtadagi nol radiusli doira ichida ko'rinadi, keyin doira kattalashadi.
  • pseudoElement — animatsiya elementning o'ziga emas, uning pseudo-elementiga qo'llanadi.

Bosganda konsolda:

text
1) DOM yangilandi
2) suratlar tayyor
3) o'tish tugadi

Tartib doim shunday. Biz Chrome 154 da (natija oynasida) sinadik: ready dan keyin document.getAnimations() da ::view-transition-group(root), ::view-transition-old(root) va ::view-transition-new(root) animatsiyalari bor edi.

7.3 skipTransition() va harakatni kamaytirish

prefers-reduced-motion darsida aytgandik: harakat ba'zi odamlarda bosh aylanishini qo'zg'atadi. matchMedia darsida bu sozlamani JavaScript'dan o'qishni o'rgandik. Endi uni ishlatamiz:

js
const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)");

const transition = document.startViewTransition(update);
if (reduceMotion.matches) {
  transition.skipTransition(); // DOM yangilanadi, animatsiyasiz
}

skipTransition() dan keyin updateCallbackDone va finished odatdagidek bajariladi. ready esa rad etiladi — animatsiya bo'lmaydi. Chrome 154 dagi xabar:

text
AbortError: Transition was skipped. skipTransition() called

Shuning uchun await transition.ready ni skipTransition() bilan birga ishlatsangiz — try/catch kerak. Yoki ready o'rniga finished ni kuting: u o'tkazib yuborilganda ham bajariladi.

Oddiy element.animate() uchun ham shu qoida: harakatni kamaytirish yoqilgan bo'lsa, duration: 0 bering yoki animatsiyani umuman chaqirmang. Ma'lumot (hisoblagich, tema) baribir yangilanadi.

Maslahat: Chrome 154 da ViewTransition obyektida types (o'tish turlari, View Transitions darsida ko'rganmiz) va waitUntil() ham bor. Ular yangi va hamma brauzerda emas. Ishlatishdan oldin Baseline holatini tekshiring.

8. WAAPI, CSS va rAF: qaysi biri qachon

Vazifa Vosita
Hover, fokus, holat o'zgarishi CSS transition
Doimiy bezak (yuklanish belgisi) CSS @keyframes
Bosishga javob, qiymatlar JS'da hisoblanadi, "tugagach — keyingisi" WAAPI
Har kadrda JS hisoblaydigan qiymat (hisoblagich, fizika, <canvas>) requestAnimationFrame

WAAPI'ning rAF'dan katta afzalligi — u brauzer ichida ishlaydi. transform va opacity animatsiyasini Chrome kompozitor thread'iga beradi. Kompozitor — tayyor chizilgan qatlamlarni surib, ustma-ust qo'yib ekranga chiqaradigan alohida thread (Rendering pipeline darsi). U JavaScript'ni kutmaydi. JavaScript 200 ms band bo'lib qolsa ham, savat silliq sakraydi. rAF animatsiyasi esa o'sha 200 ms da to'xtab turardi.

Bu afzallik faqat "arzon" xususiyatlarga tegishli. width, top, margin ni WAAPI bilan animatsiya qilsangiz ham, har kadrda layout qayta hisoblanadi — CSS'dagi kabi.

9. Ko'p uchraydigan xatolar

9.1 Kebab-case xususiyat nomi

js
// ❌ "background-color" — e'tiborsiz qoladi
box.animate(
  [{ "background-color": "blue" }, { "background-color": "green" }],
  1000,
);

Xato ham, ogohlantirish ham chiqmaydi — rang shunchaki o'zgarmaydi. Chrome 154 da getKeyframes() bu kadrlarda hech qanday xususiyat qaytarmadi. Tuzatish: backgroundColor. (CSS o'zgaruvchilari esa aksincha, chiziqcha bilan yoziladi: "--angle".)

9.2 Davomiylik soniyada

animate(kadrlar, 0.3) — 0,3 ms. Tuzatish: 300.

9.3 fill: "forwards" ni "yakuniy holat" deb ishlatish

Animatsiya uslubni ushlab turadi va keyin yozilgan style ko'rinmaydi. Tuzatish: yakuniy uslubni o'zingiz yozing yoki commitStyles() + cancel().

9.4 finished ni try/catch siz kutish

Animatsiya bekor qilinsa — Uncaught (in promise) AbortError. Tuzatish: AbortError ni ushlang yoki bekor qilinishi mumkin bo'lmagan joyda kuting.

9.5 Harakatni kamaytirishni unutish

Har bosishda katta harakat — ba'zi foydalanuvchilar uchun og'riqli. Tuzatish: matchMedia("(prefers-reduced-motion: reduce)") ni tekshiring.

10. Mashqlar

1-mashq (oson): «Yoqdi» tugmasi

«Bahor» sharhlari ostida tugmasi bor. Bosilganda u 250 ms da scale(1) → scale(1.3) → scale(1) bo'lib "urib" qo'ysin va yonidagi son bittaga oshsin. «Savat sakraydi» misolidagi naqshni ishlating.

Yechim
html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  #like { font-size: 1.5rem; padding: 0.3rem 0.7rem;
    display: inline-block; }
</style>
<button id="like" type="button" aria-label="Yoqdi">❤</button>
<span id="likes">12</span>
<script>
  const like = document.getElementById("like");
  const likes = document.getElementById("likes");

  like.addEventListener("click", () => {
    likes.textContent = Number(likes.textContent) + 1;
    like.animate(
      { transform: ["scale(1)", "scale(1.3)", "scale(1)"] },
      { duration: 250, easing: "ease-out" },
    );
  });
</script>

Keyframe'lar ikkinchi shaklda: transform ga uchta qiymat. Son animatsiyadan oldin yangilanadi — animatsiya faqat bezak.

2-mashq (o'rta): Ketma-ket paydo bo'lish

Uchta taom kartasi (.dish) sahifa ochilganda birin-ketin paydo bo'lsin: har biri 300 ms da opacity 0 → 1 va translateY(10px) → 0. Keyingisi oldingisi tugagach boshlansin. Hammasi tugagach konsolga "Menyu tayyor" chiqsin.

Ishora: for...of ichida await card.animate(...).finished. Boshlanishdan oldin kartalar ko'rinmay tursin uchun ularga CSS'da opacity: 0 bering va animatsiyada fill: "forwards" ishlating.

Yechim
html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
  .dish { opacity: 0; margin: 0.4rem 0; padding: 0.5rem 0.75rem;
    border: 1px solid #cfe3cf; border-radius: 0.5rem; }
</style>
<p class="dish">Osh — 35 000 so'm</p>
<p class="dish">Lag'mon — 28 000 so'm</p>
<p class="dish">Manti — 30 000 so'm</p>
<script>
  async function revealMenu() {
    for (const card of document.querySelectorAll(".dish")) {
      await card.animate(
        { opacity: [0, 1],
          transform: ["translateY(10px)", "translateY(0)"] },
        { duration: 300, easing: "ease-out", fill: "forwards" },
      ).finished;
    }
    console.log("Menyu tayyor");
  }
  revealMenu();
</script>

Konsolda:

text
Menyu tayyor

Bu yerda fill: "forwards" kerak: CSS'dagi opacity: 0 animatsiya tugagach qaytib kelardi. Yana bir yo'l — har biriga delay: index * 300 berib, hammasini bir vaqtda boshlash. Unda Promise.all bilan oxirgisini kutasiz.

3-mashq (qiyin): Toastni yopish tugmasi

«finished: tugashini kutish» bo'limidagi toastga «×» tugmasi qo'shing. Bosilganda toast darhol (150 ms da) yo'qolsin. Kutayotgan 1,5 soniyalik yopilish animatsiyasi xato bermasin. Konsolga har holatda bitta Toast olib tashlandi: … chiqsin.

Ishora: yopilish animatsiyasini o'zgaruvchida saqlang. «×» bosilganda unga playbackRate ni oshirib, currentTime ni delay ga (1 500) suring yoki shunchaki finish() qiling. cancel() emas — u AbortError beradi.

Yechim
html
<style>
  body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem;
    min-height: 9rem; }
  .toast { position: fixed; left: 1rem; bottom: 1rem; margin: 0;
    display: flex; gap: 0.75rem; align-items: center;
    padding: 0.6rem 1rem; border-radius: 0.5rem;
    background: #1b3d1f; color: #fff; }
  .toast button { font: inherit; background: none; color: inherit;
    border: 0; cursor: pointer; }
  #ready { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<button id="ready" type="button">Buyurtma tayyor</button>
<script>
  async function showToast(text) {
    const toast = document.createElement("p");
    toast.className = "toast";
    toast.setAttribute("role", "status");
    const label = document.createElement("span");
    label.textContent = text;
    const close = document.createElement("button");
    close.type = "button";
    close.setAttribute("aria-label", "Yopish");
    close.textContent = "×";
    toast.append(label, close);
    document.body.append(toast);

    const slide = [
      { transform: "translateY(150%)", opacity: 0 },
      { transform: "translateY(0)", opacity: 1 },
    ];
    const show = toast.animate(slide, { duration: 250 });
    const hide = toast.animate(slide, {
      duration: 250, delay: 1500, direction: "reverse",
      fill: "forwards",
    });
    hide.pause(); // ko'rinish tugaguncha kutib tursin
    await show.finished;
    hide.play();

    close.addEventListener("click", () => {
      hide.currentTime = 1500; // kutishni o'tkazib yuboramiz
      hide.playbackRate = 1.7; // 250 ms → ~150 ms
    }, { once: true });

    await hide.finished;
    toast.remove();
    console.log(`Toast olib tashlandi: ${text}`);
  }

  document.getElementById("ready").addEventListener("click", () => {
    showToast("№ 18 buyurtma tayyor");
  });
</script>

Ikkala animatsiya boshida yaratiladi, yopilishi pauzada turadi. «×» bosilganda biz cancel() qilmaymiz — currentTime ni kutish oxiriga suramiz va tezlikni oshiramiz. hide.finished odatdagidek bajariladi, shuning uchun remove() va konsol qatori bitta joyda qoladi. Biz sinadik: «×» bosilgandan taxminan 0,2 soniyada konsolda Toast olib tashlandi: № 18 buyurtma tayyor chiqdi.

4-mashq: Amaliy tajriba — yangi vazifa "yonib" chiqsin

vazifalar kanon kodiga tegmasdan tajriba qiling: kurs/mashqlar/13/10-waapi/index.html — kichik forma va <ul>. Yangi qo'shilgan <li> ning foni 600 ms davomida sariq rangdan (#fff3b0) shaffofga o'tsin — "yangi qo'shildi" belgisi. Harakatni kamaytirish yoqilgan bo'lsa, animatsiya bo'lmasin. Live Server bilan oching.

Yechim
html
<form id="form">
  <label for="text">Yangi vazifa</label>
  <input id="text" required>
  <button>Qo'shish</button>
</form>
<ul id="list"></ul>
<script>
  const form = document.getElementById("form");
  const input = document.getElementById("text");
  const list = document.getElementById("list");
  const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)");

  form.addEventListener("submit", (event) => {
    event.preventDefault();
    const item = document.createElement("li");
    item.textContent = input.value;
    list.append(item);
    form.reset();
    if (reduceMotion.matches) return;
    item.animate(
      { backgroundColor: ["#fff3b0", "transparent"] },
      { duration: 600, easing: "ease-out" },
    );
  });
</script>

backgroundColor — camelCase. fill kerak emas: animatsiya tugagach <li> o'zining odatiy (shaffof) foniga qaytadi — aynan kerakli holat. DevTools'da Rendering paneli → "Emulate CSS media feature prefers-reduced-motion" → reduce bilan ikkinchi holatni sinang.

bash
git add 13/10-waapi/index.html
git commit -m "13/10: yangi vazifa WAAPI bilan ajratib ko'rsatiladi"

11. Real ishda

  • Mikro-interaksiyalar. "Savatga qo'shildi", "Yoqdi", nusxalandi belgisi — bosishga javob beradigan qisqa animatsiyalar WAAPI bilan bir necha qatorda yoziladi.
  • Kutubxonalar. Motion (oldingi nomi Framer Motion) — React va oddiy JavaScript uchun animatsiya kutubxonasi. U imkon bo'lsa animatsiyani WAAPI'ga topshiradi — shuning uchun asosiy thread band bo'lsa ham silliq. React'ni 17-qismdan o'rganamiz, hozir bilish shart emas.
  • View Transitions. Tema almashtirish, ro'yxatdan batafsil sahifaga o'tish — startViewTransition + ready + animate(). Freymvorklar (Next.js, Astro) shu API'ni ichkarida ishlatadi.
  • Testlar. Animatsiyali interfeysni testlashda getAnimations() bilan barcha animatsiyalarni finish() qilib, natijani darhol tekshirishadi.
  • Intervyu. "CSS animatsiyasi, WAAPI va rAF farqi?", "Animatsiya tugashini qanday kutasiz?" — frontend intervyularida uchraydi.

Xulosa

  • element.animate(keyframes, options) animatsiyani darhol boshlaydi va Animation pultini qaytaradi.
  • Keyframe xususiyatlari camelCase'da, davomiylik — millisekundda.
  • fill: "forwards" uslubni "qulflaydi" — yakuniy holatni o'zingiz yozing yoki commitStyles() qiling.
  • pause, play, reverse, finish, cancel, playbackRate, currentTime — to'liq boshqaruv.
  • await animation.finished — "tugagach, keyingisi"; cancel() bo'lsa AbortError.
  • getAnimations() CSS animatsiyalarini ham qaytaradi; startViewTransition ning ready va finished i bilan o'tishni JS'dan boshqarasiz.

Keyingi dars: File va Blob — brauzerda fayl bilan ishlash boshlanadi: foydalanuvchi tanlagan faylni o'qimasdan ko'rish, o'zingiz fayl yaratish va uni yuklab berish.

Manbalar

  • W3C: Web Animations Level 1 — w3.org/TR/web-animations-1
  • MDN: Element.animate(), Animation, Document.getAnimations() — developer.mozilla.org
  • MDN: ViewTransition (ready, finished, skipTransition) — developer.mozilla.org
  • Chrome for Developers: "View Transitions" (bosilgan joydan doira misoli) — developer.chrome.com
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript Web Animations API: element.animate() bilan animatsiyani boshqarish — IlmHamroh