Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. Performance API: brauzerning "qora qutisi"
- 2.1 Eslab olamiz va bir qadam oldinga
- 2.2 Sahifa yuklanishi: navigation yozuvi
- 2.3 PerformanceObserver: yozuv paydo bo'lishini kutish
- 3. Core Web Vitals: uchta asosiy ko'rsatkich
- 3.1 Uch savol — uch son
- 3.2 Yordamchi ko'rsatkichlar: TTFB va FCP
- 3.3 LCP: eng katta element qachon chizildi
- 3.4 CLS: sahifa qancha "sakradi"
- 3.5 INP: bosishga qancha tez javob berildi
- 4. web-vitals kutubxonasi
- 4.1 Nega kuzatuvchining o'zi yetmaydi?
- 4.2 Birinchi ulanish
- 4.3 Natijani serverga yuborish
- 4.4 Qaysi brauzerda ishlaydi?
- 5. Laboratoriya va maydon
- 5.1 Ikki xil o'lchov
- 5.2 Google qayerdan biladi?
- 6. Ko'p uchraydigan xatolar
- 6.1 Faqat o'z kompyuteringizda o'lchash
- 6.2 buffered: true ni unutish
- 6.3 CLS'ni ms deb o'qish
- 6.4 Kutubxona qiymat bermayapti deb o'ylash
- 6.5 O'rtacha qiymatga qarash
- 7. Mashqlar
- 1-mashq (oson): Bahoni hisoblang
- 2-mashq (o'rta): Banner uchun joy ajrating
- 3-mashq (qiyin): Menyu yuklanishini o'lchang
- 4-mashq: Vazifalar qadami — Core Web Vitals konsolga yoziladi
- 8. Real ishda
- Xulosa
- Manbalar
Performance API va Core Web Vitals: saytning haqiqiy tezligini o'lchash
Qisqacha: Brauzer sahifadagi har bir muhim voqeani o'zi yozib boradi: sahifa qachon kelgani, eng katta element qachon chizilgani, nima siljigani, bosishga qancha vaqtda javob berilgani. Bu yozuvlarni
PerformanceObserverbilan o'qiysiz. Google ulardan uchtasini asosiy deb biladi — Core Web Vitals: LCP (yuklanish, ≤ 2,5 s), INP (javob berish, ≤ 200 ms) va CLS (barqarorlik, ≤ 0,1). Amalda ularniweb-vitalskutubxonasi bilan o'lchashadi.
Bu darsda
performance.getEntriesByTypevaPerformanceObserverbilan brauzerning vaqt yozuvlarini o'qiy olasiz.- LCP, INP va CLS nimani o'lchashini, chegaralarini va qachon "yomon" bo'lishini tushuntira olasiz.
- Sahifani "sakratadigan" va sekin javob beradigan kodni o'lchab, isbot bilan ko'rsata olasiz.
web-vitalskutubxonasini ulab, haqiqiy foydalanuvchilar tezligini yig'ishni boshlaysiz.- Laboratoriya o'lchovi (Lighthouse) va maydon ma'lumoti (CrUX, Search Console) farqini bilasiz.
Oldin bilishingiz kerak: Performansni o'lchash va benchmarking, Rendering pipeline va layout thrashing, Microtask va macrotask chuqur, Chrome DevTools II: Network, Application va Lighthouse.
1. Nega bu kerak?
Rendering pipeline va layout thrashing darsida sahifani tezlashtirishni o'rgandik. Lekin bitta savol ochiq qoldi: "tez" degani nima va uni kim o'lchaydi?
Jasur aka «Bahor» saytini telefonidan ochdi. "Sayt sekin, menyu kech chiqyapti. Tugmani bossam, bir lahza hech narsa bo'lmaydi" — dedi. Sardor esa o'z kompyuterida ochib ko'rdi: hammasi bir zumda. "Menda tez-ku" — dedi u.
Ikkalasi ham haq: Sardorning kompyuteri kuchli, Jasur akaning telefoni oddiy va internet mobil. Saytning haqiqiy tezligi — foydalanuvchining qurilmasidagi tezlik.
"Tez" so'zi ham juda keng. Mehmon uch narsani sezadi:
- Ko'rindimi? — asosiy mazmun qachon ekranga chiqdi.
- Javob berdimi? — tugmani bosganda sahifa qancha tez o'zgardi.
- Joyida turdimi? — o'qiyotgan matn birdan pastga "sakrab" ketmadimi.
Brauzerda shu uch savolga son bilan javob beradigan asboblar bor. Sonni esa haqiqiy foydalanuvchilardan — Jasur akaning telefonidan ham — yig'ish mumkin.
2. Performance API: brauzerning "qora qutisi"
2.1 Eslab olamiz va bir qadam oldinga
Performansni o'lchash darsida uch asbobni ko'rgan edik: performance.now(), performance.mark va performance.measure. Ular o'zingiz qo'ygan nuqtalar orasidagi vaqtni o'lchaydi.
Yangi g'oya: brauzer sizdan so'ramasdan ham ko'p narsani yozib boradi — sahifa qachon keldi, har bir fayl qancha yuklandi, birinchi piksel qachon chizildi. Xuddi samolyotdagi qora quti kabi.
Bu yozuvlar to'plami performance timeline (vaqt chizig'i) deyiladi. Har bir yozuv — PerformanceEntry obyekti, umumiy maydonlari to'rtta:
| Maydon | Ma'nosi |
|---|---|
entryType |
Yozuv turi: "mark", "navigation", "paint"… |
name |
Nomi: belgi nomi, fayl manzili yoki hodisa nomi |
startTime |
Boshlangan payt (sahifa ochilganidan beri, ms) |
duration |
Davomiyligi (ms); bir lahzalik yozuvda 0 |
mark va measure ham shunday yozuvlar. Siz ularni o'zingiz qo'shasiz, qolganlarini brauzer qo'shadi.
2.2 Sahifa yuklanishi: navigation yozuvi
Eng foydali yozuvlardan biri — sahifa yuklanishining o'zi. Uni istalgan saytda, DevTools konsolida ko'ring:
const [nav] = performance.getEntriesByType("navigation");
console.log("TTFB:", Math.round(nav.responseStart), "ms");
console.log("DOM tayyor:", Math.round(nav.domContentLoadedEventEnd));
console.log("To'liq yuklandi:", Math.round(nav.loadEventEnd));
console.log("Hajmi:", nav.transferSize, "bayt");getEntriesByType("navigation") bitta yozuvli massiv qaytaradi — uni massiv destructuring bilan oldik.
responseStart— server javobining birinchi bayti kelgan payt: TTFB (Time to First Byte). Manzilni yozganda nima bo'ladi darsidagi DNS, ulanish va server o'ylashi — hammasi shu songa kiradi.domContentLoadedEventEnd— HTML o'qib bo'lindi,DOMContentLoadedtinglovchilari ishladi (Sahifa hayot sikli).loadEventEnd— rasm va shriftlar bilan birga hammasi tugadi.transferSize— tarmoqdan kelgan baytlar. Keshdan olinsa —0.
Sonlar har safar boshqacha, shuning uchun bu yerda yozmadik. Yuklanish bosqichlari (yuqoridan pastga):
flowchart TD
A["Havola bosildi<br/>(startTime = 0)"] --> B["DNS, ulanish,<br/>so'rov"]
B --> C["Birinchi bayt<br/>TTFB"]
C --> D["HTML o'qilmoqda"]
D --> E["Birinchi chizish<br/>FCP"]
E --> F["Eng katta element<br/>LCP"]
F --> G["load hodisasi"]FCP va LCP — navigation yozuvida emas, ular alohida turlar. resource turi esa har bir fayl (CSS, skript, rasm, fetch) uchun yozuv beradi — Network paneli kabi.
2.3 PerformanceObserver: yozuv paydo bo'lishini kutish
getEntriesByType — "hozirgacha nima yozildi?" degan savol. Lekin ko'p voqealar keyin bo'ladi: siljish 3 soniyadan keyin, sekin bosish bir daqiqadan keyin. Ular uchun kuzatuvchi (observer) kerak.
PerformanceObserver — MutationObserver ning qarindoshi. Unga "shu turdagi yozuv paydo bo'lsa, menga ayt" deysiz. Avval Node'da ko'ramiz — u yerda ham mark va measure bor:
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.entryType}: ${entry.name}`);
}
observer.disconnect();
});
observer.observe({ type: "measure" });
performance.mark("menu-start");
const menu = Array.from({ length: 1000 }, (_, i) => i);
performance.mark("menu-end");
performance.measure("menu-build", "menu-start", "menu-end");
console.log("Sinxron kod tugadi:", menu.length);Konsolda:
Sinxron kod tugadi: 1000
measure: menu-buildE'tibor bering: "Sinxron kod tugadi" birinchi chiqdi. Kuzatuvchi yozuvlarni yig'ib, keyinroq — alohida task'da beradi (Microtask va macrotask). Bu ataylab qilingan: o'lchov o'lchanayotgan kodni sekinlatmasin.
observe ga ikki muhim parametr beriladi:
type— qaysi yozuv turi kerak.buffered: true— "kuzatuvchi yaratilishidan oldin yozilganlarini ham ber". Skript kech yuklansa ham, erta voqealar yo'qolmaydi.
Brauzer qaysi turlarni bilishini PerformanceObserver.supportedEntryTypes aytadi. Chrome 154 da natija oynasida 15 ta tur chiqdi, jumladan largest-contentful-paint, layout-shift, event, longtask, long-animation-frame. Node 24 da ro'yxat boshqacha (dns, http, gc).
Tekshirib ko'ring: Skriptingiz sahifa oxirida,
deferbilan yuklanadi. Sizpaintturinibufferedsiz kuzatsangiz, birinchi chizish yozuvini olasizmi?
Javob
Ehtimol yo'q. Birinchi chizish skriptingizdan oldin bo'lib o'tgan bo'lishi mumkin, buffered siz kuzatuvchi esa faqat o'zidan keyingi yozuvlarni ko'radi. Shuning uchun yuklanishni o'lchashda buffered: true deyarli doim yoziladi.
3. Core Web Vitals: uchta asosiy ko'rsatkich
3.1 Uch savol — uch son
Google 2020 yilda foydalanuvchi sezadigan tezlik uchun ko'rsatkichlar to'plamini e'lon qildi — Web Vitals ("vebning hayotiy belgilari"). Ulardan uchtasi asosiy — Core Web Vitals. Har biri «Nega bu kerak?» bo'limidagi bitta savolga javob beradi:
| Savol | Ko'rsatkich | Yaxshi | Yomon |
|---|---|---|---|
| Ko'rindimi? | LCP — eng katta mazmun chizilishi | ≤ 2,5 s | > 4 s |
| Javob berdimi? | INP — bosishdan keyingi chizishgacha | ≤ 200 ms | > 500 ms |
| Joyida turdimi? | CLS — sahifa siljishlari yig'indisi | ≤ 0,1 | > 0,25 |
Ikki chegara orasidagi qiymat — "yaxshilash kerak" (needs improvement). Chegaralar — web.dev'dagi "Web Vitals" maqolasidan (2026-oktabr holati); web-vitals 6.2.3 kodida ham aynan shu sonlar.
Muhim qoida: sahifa "yaxshi" bo'lishi uchun foydalanuvchilarning 75 foizida qiymat yaxshi chegarada bo'lishi kerak. Bu 75-persentil deyiladi. Misol: 100 ta mehmonning LCP'sini eng tezidan eng sekiniga qarab qatorga tizasiz. 75-o'rindagi son — 75-persentil. Undan oldingi 74 kishi tezroq, keyingi 25 kishi sekinroq ko'rgan.
Nega o'rtacha emas? O'rtacha qiymat sekin telefonlarni "yashiradi": o'nta tez kompyuter bitta sekin telefonni bosib ketadi. 75-persentil esa "har to'rt mehmondan uchtasi yaxshi ko'rdimi?" deb so'raydi.
3.2 Yordamchi ko'rsatkichlar: TTFB va FCP
Ikki yordamchi ko'rsatkich sababni topishga yordam beradi:
- TTFB — server javobining birinchi bayti; yaxshi ≤ 0,8 s. U yomon bo'lsa, LCP ham yaxshi bo'lmaydi: hamma narsa shu baytdan keyin boshlanadi.
- FCP (First Contentful Paint) — ekranga birinchi matn yoki rasm chiqqan payt; yaxshi ≤ 1,8 s. Mehmon "sahifa ochilyapti" deb ko'radigan birinchi lahza.
Maslahat: Eski maqolalarda FID (First Input Delay) uchraydi. 2024 yil 12 martda uning o'rnini INP egalladi: FID faqat birinchi bosishning kutishini o'lchardi, INP esa butun tashrif davomidagi bosishlarni.
3.3 LCP: eng katta element qachon chizildi
LCP (Largest Contentful Paint) — ekrandagi eng katta matn bloki yoki rasm chizilgan vaqt. Mehmon uchun bu "sahifa keldi" degan lahza: bosh rasm, katta sarlavha yoki birinchi paragraf.
Brauzer LCP'ni bir necha marta yozadi. Avval kichik paragraf — "nomzod". Kattaroq element paydo bo'lsa — yangi nomzod. Mehmon birinchi marta bosganda hisob to'xtaydi, oxirgi nomzod — LCP. Sahifada avval kichik matn bor, 600 ms dan keyin katta banner qo'shiladi:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
.hero { height: 160px; margin: 0; display: grid;
place-items: center; font: 700 2rem/1.2 system-ui;
color: #fff; background: #2e7d32; border-radius: 1rem; }
</style>
<p>Bosh sahifa yuklanmoqda…</p>
<div id="slot"></div>
<script>
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const name = entry.element?.id || entry.element?.tagName;
console.log(`LCP nomzodi: ${name}`);
}
}).observe({ type: "largest-contentful-paint", buffered: true });
setTimeout(() => {
const hero = document.createElement("p");
hero.className = "hero";
hero.id = "hero";
hero.textContent = "«Bahor» — 07:00 dan 23:00 gacha";
document.getElementById("slot").append(hero);
}, 600);
</script>Konsolda:
LCP nomzodi: P
LCP nomzodi: heroBirinchi nomzod — kichik p, keyin kattaroq hero. LCP shu sahifada kamida 600 ms. Yozuvning element maydoni — DOM elementning o'zi; yana size (px²), startTime va rasm bo'lsa url bor.
LCP'ni nima yomonlashtiradi?
- Sekin server — TTFB katta bo'lsa, hamma narsa kechikadi.
- Kech topilgan bosh rasm (CSS ichida yoki JS bilan qo'shilgan) — yechim Resurs maslahatlari dagi
preloadvafetchpriority="high". Bosh rasmgaloading="lazy"qo'yish esa uni ataylab kechiktiradi. - Chizishni to'suvchi resurslar — katta CSS va skriptlar (Renderni nima to'xtatadi).
- JavaScript bilan chiziladigan mazmun — yuqoridagi misoldagidek.
3.4 CLS: sahifa qancha "sakradi"
Kech kelgan rasm yoki banner o'qiyotgan matningizni pastga suradi. Eng yomoni — "Bekor qilish" ni bosmoqchi bo'lasiz, u siljiydi va barmog'ingiz "To'lash" ga tushadi.
CLS (Cumulative Layout Shift) — kutilmagan siljishlarning yig'indisi. Har bir siljishning bahosi = qancha maydon siljidi × qancha masofaga (ekran balandligiga nisbatan). Ekranning yarmi to'rtdan bir balandlikka surilsa — taxminan 0.5 × 0.25 = 0.125. Shuning uchun CLS'ning birligi yo'q — bu ms emas.
Menyu chizildi, 800 ms dan keyin tepaga chegirma banneri qo'shiladi:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 0;
padding: 1rem; }
.banner { margin: 0 0 1rem; padding: 1rem; height: 5rem;
background: #fff3cd; border-radius: 0.5rem; }
.card { margin: 0 0 0.5rem; padding: 1rem;
background: #eef3ee; border-radius: 0.5rem; }
</style>
<div id="top"></div>
<h2>Bugungi menyu</h2>
<p class="card">Osh — 35 000 so'm</p>
<p class="card">Manti — 30 000 so'm</p>
<p class="card">Lag'mon — 28 000 so'm</p>
<script>
let cls = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.hadRecentInput) continue;
cls += entry.value;
console.log(`Siljish: ${entry.value.toFixed(3)},`
+ ` jami CLS: ${cls.toFixed(3)}`);
}
}).observe({ type: "layout-shift", buffered: true });
setTimeout(() => {
const banner = document.createElement("p");
banner.className = "banner";
banner.textContent = "Bugun 20% chegirma!";
document.getElementById("top").append(banner);
}, 800);
</script>Banner chiqqanda menyu sakraydi va konsolda bitta siljish yoziladi. Biz 800×600 px oynada sinadik: Siljish: 0.073, jami CLS: 0.073 (son oyna o'lchamiga bog'liq). Bitta banner "yaxshi" chegarasining (0,1) yarmidan ko'pini yedi.
Kodda ikki nozik joy bor:
entry.hadRecentInput— foydalanuvchi oxirgi 500 ms ichida bosgan yoki yozgan bo'lsa,true. Bunday siljish kutilgan ("Batafsil" ni bosdingiz va matn ochildi), CLS uni hisoblamaydi.entry.sources— qaysi elementlar siljigani. Debug uchun foydali.
Haqiqiy CLS'da siljishlar seanslarga guruhlanadi (oralig'i 1 soniyadan kam, seans ko'pi bilan 5 soniya), CLS — eng "yomon" seans. Soatlab ochiq turadigan sahifa shu tufayli adolatsiz jazolanmaydi. Bu hisobni keyingi bo'limdagi kutubxona qiladi.
Tuzatish g'oyasi bitta: joyni oldindan ajrating. Rasmga width va height (yoki aspect-ratio), banner joyiga min-height, kech keladigan ro'yxatga skelet (Loaderlar va skeletonlar). Shrift almashganda ham matn kengayadi — Web shriftlar darsidagi size-adjust shuning uchun.
Tekshirib ko'ring: Mehmon «Yana ko'rsatish» tugmasini bosdi, 100 ms dan keyin pastga 10 ta yangi taom qo'shildi va pastdagi footer surildi. Bu siljish CLS'ga qo'shiladimi?
Javob
Yo'q. Siljish bosishdan keyingi 500 ms ichida bo'ldi — hadRecentInput true, mehmon uni kutgan edi. Lekin javob 600 ms dan keyin kelsa, siljish "kutilmagan" hisoblanadi.
3.5 INP: bosishga qancha tez javob berildi
INP (Interaction to Next Paint) — foydalanuvchi sichqoncha bilan bosgan, ekranga tekkan yoki klaviatura tugmasini bosgan paytdan keyingi kadr chizilgunicha o'tgan vaqt. Mehmon savatga taom qo'shdi — tugma qachon "Qo'shildi" bo'ldi? Shu vaqt.
Bu vaqt uch bo'lakdan iborat:
- Kutish (input delay) — asosiy thread band edi, hodisa navbatda turdi.
- Ishlov (processing) — sizning tinglovchilaringiz ishladi.
- Chizish (presentation delay) — style, layout, paint va kadr ekranga chiqdi (Rendering pipeline).
Brauzer har bir sekin hodisa uchun event turidagi yozuv qoldiradi. Uni kuzatamiz. Ikki tugma: biri darhol javob beradi, ikkinchisi 300 ms "og'ir hisob" qiladi:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<button id="fast" type="button">Tez tugma</button>
<button id="slow" type="button">Sekin tugma</button>
<p id="out">Bosing…</p>
<script>
function busyWait(ms) {
const end = performance.now() + ms;
while (performance.now() < end) {}
}
const out = document.getElementById("out");
document.getElementById("fast").addEventListener("click", () => {
out.textContent = "Tez javob";
});
document.getElementById("slow").addEventListener("click", () => {
busyWait(300); // og'ir hisobni taqlid qiladi
out.textContent = "Sekin javob";
});
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name !== "click") continue;
const work = entry.processingEnd - entry.processingStart;
console.log(`${entry.target?.id}: ${Math.round(entry.duration)}`
+ ` ms, shundan ishlov ${Math.round(work)} ms`);
}
}).observe({ type: "event", durationThreshold: 16 });
</script>Ikkala tugmani bosing. Biz Chrome 154 da bosib ko'rdik: fast: 16 ms, shundan ishlov 0 ms va slow: 304 ms, shundan ishlov 300 ms. Tez tugma keyingi kadrda javob berdi — 16 ms, ya'ni bitta kadr. Sekinida mehmon 0,3 soniya tugmani "o'lik" deb o'yladi. Kuzatuvchidagi durationThreshold: 16 16 ms dan qisqa hodisalarni yozmaydi (bu eng kichik ruxsat etilgan qiymat). Kodimizdagi busyWait esa Performansni o'lchash darsidagi "bo'sh aylanish". Haqiqiy saytda uning o'rnida katta ro'yxatni saralash yoki ming qatorli jadvalni chizish bo'ladi.
Yozuvning foydali maydonlari:
duration— hodisadan keyingi kadrgacha to'liq vaqt (8 ms ga yaxlitlangan).processingStart − startTime— kutish,processingEnd − processingStart— ishlov.interactionId— bitta harakatning hodisalari (pointerdown,pointerup,click) bir xil raqam oladi va bitta "o'zaro ta'sir" sanaladi.
INP — sahifadagi eng sekin o'zaro ta'sir. Bosishlar 50 dan ko'p bo'lsa, har 50 tadan eng yomon bittasi tashlab yuboriladi — tasodifiy bitta "qotish" butun sahifani yomon qilmasin. Uni tuzatish — keyingi dars, Asosiy thread'ni bo'shatish.
Tekshirib ko'ring: Sahifa yuklanayotganda 400 ms li skript ishlayapti. Shu paytda mehmon tugmani bosdi, tinglovchi esa atigi 5 ms ishladi. Bu bosishning INP'ga qo'shadigan vaqti qancha atrofida va u qaysi bo'lakka tushadi?
Javob
Taxminan skriptning qolgan vaqti + 5 ms + chizish — masalan, 300–400 ms. Tinglovchi tez, lekin hodisa kutish bo'lagida turib qoldi. INP faqat tinglovchilarga bog'liq emas — sahifadagi boshqa uzun ishlar ham uni buzadi.
4. web-vitals kutubxonasi
4.1 Nega kuzatuvchining o'zi yetmaydi?
Aniq Core Web Vitals'ni hisoblashda nozik qoidalar ko'p:
- CLS seanslari va INP'da 50 tadan bittasini tashlash;
- fonda ochilgan sahifada LCP'ni hisoblamaslik;
- "Orqaga" tugmasi bilan qaytganda hisobni qaytadan boshlash. Brauzer yaqinda yopilgan sahifani xotirada butunligicha saqlab turadi va "Orqaga" bosilganda bir zumda qaytaradi. Bu xotira bfcache (back/forward cache — "orqaga-oldinga keshi") deyiladi.
Yakuniy CLS va INP esa faqat mehmon sahifani tark etganda tayyor.
Bularni har loyihada qayta yozish xatoga olib boradi. Google jamoasi web-vitals kutubxonasini yozgan — Chrome qanday o'lchasa, aynan shunday o'lchaydi. Biz 6.2.3 versiyani ishlatamiz (2026-oktabr); README'ga ko'ra siqilgan hajmi taxminan 3 KB.
4.2 Birinchi ulanish
Kutubxonada beshta funksiya bor: onLCP, onINP, onCLS, onFCP, onTTFB. Har biriga callback berasiz — qiymat tayyor bo'lganda chaqiriladi. Natija oynasida uni jsDelivr CDN'idan ulaymiz (saytimiz CSP'si shu CDN'ga ruxsat beradi):
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 0;
padding: 1rem; }
button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<h2>«Bahor» menyusi</h2>
<button id="add" type="button">Savatga qo'shish</button>
<p id="out"></p>
<script type="module">
import { onCLS, onFCP, onINP, onLCP, onTTFB }
from "https://cdn.jsdelivr.net/npm/web-vitals@6.2.3/+esm";
function report(metric) {
const value = metric.name === "CLS"
? metric.value.toFixed(3)
: `${Math.round(metric.value)} ms`;
console.log(`${metric.name}: ${value} (${metric.rating})`);
}
onTTFB(report);
onFCP(report);
onLCP(report, { reportAllChanges: true });
onCLS(report, { reportAllChanges: true });
onINP(report, { reportAllChanges: true });
document.getElementById("add").addEventListener("click", () => {
const end = performance.now() + 300;
while (performance.now() < end) {} // og'ir ish
document.getElementById("out").textContent = "Qo'shildi";
});
</script>Ochilishi bilan konsolda uch qator chiqadi: FCP, LCP va CLS — hammasi good. «Savatga qo'shish» ni bosing — INP chiqadi, needs-improvement bilan (biz sinaganda 300 ms dan oshdi). Sonlar kompyuterga bog'liq, shuning uchun yozmadik.
Callback'ga keladigan metrika obyektining asosiy maydonlari:
| Maydon | Ma'nosi |
|---|---|
name |
"LCP", "INP", "CLS", "FCP", "TTFB" |
value |
Qiymat (ms yoki CLS soni) |
rating |
"good", "needs-improvement", "poor" |
delta |
Oldingi xabardan beri o'zgarish |
id |
Shu o'lchovning noyob belgisi (serverda guruhlash uchun) |
entries |
Hisobga kirgan performance yozuvlari |
reportAllChanges: true — "har o'zgarganda xabar ber", faqat o'rganish va debug uchun. Oddiy holatda kutubxona yakuniy qiymatni bir marta beradi: LCP — birinchi bosishda yoki tab yashirilganda, CLS va INP — tab yashirilganda (boshqa tabga o'tish, ilovani yopish).
TTFB chiqmadi: natija oynasi tarmoqdan yuklanmaydi, sayt uni HTML matnidan yasaydi (about:srcdoc). Server javobi yo'q — TTFB ham yo'q. O'z saytingizda (Live Server bilan ham) u chiqadi.
4.3 Natijani serverga yuborish
Konsoldagi son faqat sizga ko'rinadi. Haqiqiy mehmonlar natijasini serverga yuborib yig'ish RUM (Real User Monitoring — haqiqiy foydalanuvchini kuzatish) deyiladi:
import { onCLS, onINP, onLCP } from "./vendor/web-vitals.js";
function sendToServer(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating,
id: metric.id,
page: location.pathname,
});
navigator.sendBeacon("/api/vitals", body);
}
onCLS(sendToServer);
onINP(sendToServer);
onLCP(sendToServer);navigator.sendBeacon(url, matn) — fetch ning kichik ukasi: so'rovni brauzerga topshiradi, javobni kutmaydi va sahifa yopilayotganda ham yo'qotmaydi. CLS va INP aynan shu paytda tayyor bo'lgani uchun u ishlatiladi. Serverni (/api/vitals) keyingi qismlarda yozamiz — hozir bilish shart emas.
Ikkinchi yig'ma — web-vitals/attribution — har bir qiymat bilan sababini ham beradi: LCP elementi, eng ko'p siljigan element, sekin bosishdagi skript.
4.4 Qaysi brauzerda ishlaydi?
API'lar hamma brauzerda bir xil emas (web-features, 2026-oktabr):
| Ko'rsatkich | Qayerda o'lchanadi |
|---|---|
| LCP, INP | Chrome, Edge, Firefox; Safari 26.2 dan — Baseline 2025-dekabrdan |
| CLS | Faqat Chromium (Chrome, Edge) |
| FCP, TTFB | Hammasida |
Ya'ni iPhone'dagi Safari'dan CLS kelmaydi: API yo'q, kutubxona jim turadi. Google ham CLS'ni faqat Chrome foydalanuvchilaridan yig'adi.
Tekshirib ko'ring: Siz
onINP(report)ni (reportAllChangessiz) ulab, sahifada 5 marta sekin tugmani bosdingiz. Konsolda hech narsa yo'q. Kutubxona buzilganmi?
Javob
Yo'q. Sukut bo'yicha INP yakuniy qiymat sifatida beriladi — mehmon tabni yashirganda yoki sahifadan chiqqanda. Boshqa tabga o'tib qayting — qator paydo bo'ladi. Debug paytida har o'zgarishni ko'rish uchun { reportAllChanges: true } qo'yiladi.
5. Laboratoriya va maydon
5.1 Ikki xil o'lchov
Chrome DevTools II darsidagi Lighthouse — laboratoriya (lab) o'lchovi: sahifani bitta "sun'iy" qurilmada ochadi. Lighthouse 13.5.0 ning mobil rejimi sekin tarmoq va 4 baravar sekin protsessorni taqlid qiladi. Tarmoq: RTT 150 ms (RTT — so'rov serverga borib, javob qaytguncha ketadigan vaqt) va taxminan 1,6 Mbit/s tezlik. Sonlar uning hisobot faylidan. Maydon (field) ma'lumoti — haqiqiy mehmonlardan yig'ilgan sonlar. Ikkalasi bir-birini to'ldiradi:
flowchart LR
L["Lab: Lighthouse,<br/>DevTools"] -->|"tezkor sinov,<br/>sababni topish"| D["Dasturchi"]
F["Field: web-vitals,<br/>CrUX"] -->|"haqiqiy holat,<br/>75-persentil"| D
D -->|"tuzatish"| S["Sayt"]
S --> L
S --> F| Lab (Lighthouse) | Field (CrUX, RUM) | |
|---|---|---|
| Kim ochadi | Bitta sun'iy qurilma | Haqiqiy mehmonlar |
| Qachon | Siz bosganda, bir zumda | Doimiy, kunlar davomida |
| INP | Yo'q — bosuvchi odam yo'q | Bor |
| Nima uchun | O'zgarishni sinash, sababni topish | "Haqiqatan yaxshimi?" |
Lighthouse hech narsani bosmaydi, shuning uchun INP'ni o'lchay olmaydi. Uning o'rniga TBT (Total Blocking Time — yuklanishdagi uzun task'larning "ortiqcha" qismi yig'indisi) ni ko'rsatadi. TBT yuqori bo'lsa, INP ham yomon bo'lishi ehtimoli katta.
5.2 Google qayerdan biladi?
Statistika yuborishga rozi bo'lgan Chrome foydalanuvchilari Core Web Vitals'ni Google'ga yuboradi. Bu baza — CrUX (Chrome User Experience Report): oxirgi 28 kun, 75-persentil. Uni ko'rishning uch yo'li:
- PageSpeed Insights (pagespeed.web.dev) — tepada CrUX maydon ma'lumoti, pastda Lighthouse o'lchovi.
- Google Search Console — sayt egasi uchun bepul xizmat, "Core Web Vitals" hisoboti bilan. Google bu ko'rsatkichlarni qidiruvda tartiblash signallaridan biri sifatida ishlatadi.
- DevTools Performance paneli — "Live metrics" sizning brauzeringizdagi LCP, CLS va INP'ni jonli ko'rsatadi, CrUX bor sahifada yonida maydon qiymatini ham.
Kichik saytda CrUX ma'lumoti bo'lmasligi mumkin — mehmonlar yetarli emas. Shunda yagona maydon ma'lumoti — o'zingiz web-vitals bilan yig'gan RUM.
Diqqat: Lighthouse'da 100 ball — "foydalanuvchilar uchun tez" degani emas. Sinovda hech kim bosmadi, haqiqiy mehmonlar esa sekin filtrni har kuni bosadi — CrUX'da INP "yomon" bo'lishi mumkin. Qarorni maydon ma'lumotiga qarab qiling.
Tekshirib ko'ring: Sardor kodga tuzatish kiritdi va natijani bugunoq bilmoqchi. Lighthouse'ga qaraydimi yoki Search Console'dagi CrUX hisobotiga? Nega?
Javob
Bugun — Lighthouse (lab). U bir zumda o'lchaydi va o'zgarish yordam berdimi, ko'rsatadi. CrUX esa oxirgi 28 kunni yig'adi: yangi kod natijasi unda asta-sekin, haftalar davomida ko'rinadi. Ikkalasi ham kerak: lab — tez tekshirish uchun, field — "haqiqiy mehmonlarga ham yaxshi bo'ldimi?" degan yakuniy javob uchun.
6. Ko'p uchraydigan xatolar
6.1 Faqat o'z kompyuteringizda o'lchash
Sardorning xatosi: kuchli kompyuterda hamma sayt tez. Tuzatish: DevTools'da protsessorni 4–6 baravar sekinlashtiring va sekin tarmoqni yoqing (DevTools: Performance, Memory va Network). Haqiqiy javob esa — maydon ma'lumotida.
6.2 buffered: true ni unutish
Kuzatuvchi kech yaratilsa, LCP va birinchi siljishlar "yo'q" bo'lib ko'rinadi — aslida ular bo'lib o'tgan. Tuzatish: yuklanish ko'rsatkichlarida doim buffered: true.
6.3 CLS'ni ms deb o'qish
"CLS 0.15 — atigi 0,15 ms!" Yo'q: CLS'ning birligi yo'q, 0,15 esa "yaxshilash kerak" zonasida.
6.4 Kutubxona qiymat bermayapti deb o'ylash
onCLS(fn) va onINP(fn) sahifa ochiq turganda jim — ular tab yashirilganda xabar beradi. Tuzatish: debug uchun reportAllChanges: true, ishda — sendBeacon.
6.5 O'rtacha qiymatga qarash
1 000 ta LCP'ning o'rtachasi 1,9 s — "yaxshi"? Tez kompyuterlar sekin telefonlarni yashiradi. Tuzatish: 75-persentilni hisoblang — mediana kabi, tartiblangan ro'yxatdan.
7. Mashqlar
1-mashq (oson): Bahoni hisoblang
Avval chegaralarni eslang. INP: yaxshi — [:200] ms gacha, yomon — [:500] ms dan ortiq. CLS: yaxshi — gacha.
Endi rate(name, value) funksiyasini yozing. U ko'rsatkich nomi va qiymatiga qarab "good", "needs-improvement" yoki "poor" qaytarsin. Chegaraning o'zi yaxshi hisoblanadi (≤). Tekshiruv: rate("LCP", 2500) → good, rate("INP", 350) → needs-improvement, rate("CLS", 0.3) → poor.
Ishora: chegaralarni bitta obyektda [yaxshi, yomon] juftligi qilib saqlang.
Yechim
const THRESHOLDS = {
LCP: [2500, 4000],
INP: [200, 500],
CLS: [0.1, 0.25],
};
function rate(name, value) {
const [good, poor] = THRESHOLDS[name];
if (value <= good) return "good";
if (value <= poor) return "needs-improvement";
return "poor";
}
console.log(rate("LCP", 2500)); // good
console.log(rate("INP", 350)); // needs-improvement
console.log(rate("CLS", 0.3)); // poorJuftlikni massiv destructuring bilan oldik. 2500 aynan chegarada — <= tufayli good. web-vitals ichida ham xuddi shunday massivlar bor: LCPThresholds = [2500, 4000]. rate("TTFB", 900) esa TypeError beradi — obyektga TTFB: [800, 1800] ni qo'shing.
2-mashq (o'rta): Banner uchun joy ajrating
«Sahifa qancha "sakradi"» bo'limidagi menyu misolini oching («Tahrirlab ko'rish»). Banner kodiga tegmasdan, faqat CSS bilan siljishni yo'qoting: konsolga birorta ham Siljish qatori chiqmasin.
Ishora: banner #top ichiga tushadi. Banner balandligi 5rem + ikki tomondan 1rem padding + 1rem pastki margin.
Yechim
#top ga banner sig'adigan balandlikni oldindan bering:
#top { min-height: 8rem; }Endi menyu birinchi kadrdan boshlab 8rem pastda turadi. Banner kelganda bo'sh joyga tushadi — hech narsa surilmaydi. Biz tekshirdik: konsol bo'sh qoldi, ya'ni CLS = 0.
Real saytda bo'sh oq joy chiroyli emas. Shuning uchun u yerga kulrang skelet yoki "Aksiyalar yuklanmoqda…" yozuvi qo'yiladi — Loaderlar va skeletonlar darsidagidek.
3-mashq (qiyin): Menyu yuklanishini o'lchang
Mashq API'sidan menyuni yuklab, ikki nuqta orasini performance.measure bilan o'lchang va natijani PerformanceObserver bilan oling. Talablar:
fetchdan oldinmenu-fetch-start, javob kelgachmenu-fetch-endbelgisi.measurenomi —menu-fetch, uni kuzatuvchi konsolga chiqarsin: nomi va taomlar soni.- Vaqtni chiqarmang (har safar boshqacha) — faqat
duration > 0ekanini.
Ishora: fetch — fetch asoslari. GET /menyu taomlar massivini qaytaradi — sonini length beradi.
Yechim
<p id="status">Yuklanmoqda…</p>
<script type="module">
let count = 0;
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${count} ta taom,`
+ ` vaqt bor: ${entry.duration > 0}`);
}
});
observer.observe({ type: "measure" });
performance.mark("menu-fetch-start");
const res = await fetch("https://ilmhamroh.uz/api/mashq/menyu");
const data = await res.json();
performance.mark("menu-fetch-end");
count = data.length;
performance.measure("menu-fetch", "menu-fetch-start",
"menu-fetch-end");
document.getElementById("status").textContent = "Tayyor";
</script>Konsolda:
menu-fetch: 4 ta taom, vaqt bor: truecount measure dan oldin yangilandi — kuzatuvchi callback'i keyinroq ishlaydi va tayyor sonni ko'radi. Bu o'lchov DevTools Performance panelining "Timings" qatorida ham ko'rinadi: menu-fetch nomli chiziq. Real loyihada aynan shunday "biznes" o'lchovlar qo'yiladi: "savat ochilishi", "qidiruv natijasi".
4-mashq: Vazifalar qadami — Core Web Vitals konsolga yoziladi
vazifalar 13-qismda Lighthouse ≥ 90 maqsadiga boradi (qism rejasi). Yaxshilashdan oldin — o'lchash: ilova o'z Core Web Vitals'ini konsolga yozadi.
Qiyinchilik: vazifalar da build vositasi (bundler — paketlarni brauzer uchun bitta faylga yig'adigan dastur, 16-qismda o'rganamiz) yo'q. Brauzer esa node_modules papkasini o'zi ko'rmaydi. CDN ham bo'lmaydi: qism oxirida CSP faqat o'z fayllarimizga ruxsat beradi. Yechim — paketning tayyor faylini o'z papkamizga ko'chirib qo'yish (vendor nusxa).
1. Branch va paket:
git switch -c feature/web-vitals
npm install --save-dev --save-exact web-vitals@6.2.3--save-exact — package.json ga ^6.2.3 emas, aynan 6.2.3 yoziladi: nusxa qo'lda yangilanadi, versiya aniq bo'lsin.
2. Ko'chiruvchi skript skriptlar/vendor.js va package.json da "vendor": "node skriptlar/vendor.js":
// skriptlar/vendor.js (asosiy qismi)
const kod = readFileSync(
new URL(
"../node_modules/web-vitals/dist/web-vitals.js",
import.meta.url,
),
"utf8",
)
// .map fayl ko'chirilmaydi — unga havola ham kerak emas
.replace(/\n?\/\/# sourceMappingURL=.*\s*$/, "\n");
const sarlavha =
`/*! web-vitals ${paket.version} | ${paket.license} | ` +
"https://github.com/GoogleChrome/web-vitals | npm run vendor */\n";
writeFileSync(
new URL("../assets/js/vendor/web-vitals.js", import.meta.url),
sarlavha + kod,
);npm run vendor faylni assets/js/vendor/web-vitals.js ga yozadi, boshiga paket, versiya, litsenziya (Apache-2.0) va yangilash yo'lini qo'yadi — begona kodda bu ma'lumot shart.
3. Yangi modul assets/js/olchov.js:
// olchov.js — Core Web Vitals: foydalanuvchi his qiladigan tezlik.
// Hozircha faqat konsolga yoziladi (serverga yuborilmaydi)
import {
onCLS,
onFCP,
onINP,
onLCP,
onTTFB,
} from "./vendor/web-vitals.js";
const BAHOLAR = Object.freeze({
good: "yaxshi",
"needs-improvement": "yaxshilash kerak",
poor: "yomon",
});
// CLS — o'lchovsiz son (siljish ulushi), qolganlari — millisekund
export function olchovMatni({ name, value, rating }) {
const qiymat =
name === "CLS" ? value.toFixed(3) : `${Math.round(value)} ms`;
return `${name}: ${qiymat} (${BAHOLAR[rating] ?? rating})`;
}
// LCP birinchi bosish yoki tab yashirilganda, CLS va INP — tab
// yashirilganda yakunlanadi: shunda konsolga chiqadi
export function olchashniBoshla(yoz = console.info) {
const xabar = (metrika) =>
yoz(`[Web Vitals] ${olchovMatni(metrika)}`);
for (const kuzat of [onTTFB, onFCP, onLCP, onCLS, onINP]) {
kuzat(xabar);
}
}olchovMatni — sof funksiya, Node'da testlanadi. olchashniBoshla brauzerga faqat chaqirilganda tegadi; yoz parametri — tikuv (seam), testda soxta funksiya beriladi.
4. asosiy.js oxirida — alohida yuklash:
// O'lchov kutubxonasi alohida yuklanadi:
// birinchi chizishni kutdirmasin
// (kanonda izoh ham, import ham bittadan qator — telefon
// ekraniga sig'ishi uchun bo'lindi)
import("./olchov.js").then(({ olchashniBoshla }) =>
olchashniBoshla(),
);Nega oddiy import emas? U modulni ilova ishga tushishidan oldin yuklab, birinchi chizishni kechiktirardi — o'lchovning o'zi sahifani sekinlashtirardi. Dinamik import() uni keyinroq yuklaydi; modulepreload ro'yxatiga ham qo'shilmadi.
5. Qolgan joylar. Begona kodni tekshirmaymiz va formatlamaymiz: eslint.config.js ga globalIgnores(["assets/js/vendor/"]), .prettierignore ga assets/js/vendor/. Oflaynda ham ishlashi uchun sw.js da VERSIYA = "v4-3" va QOBIQ ga ikki yangi fayl (25 ta).
6. Testlar. tekshiruv/olchov.test.js — 2 ta test: LCP 1234.56 → "LCP: 1235 ms (yaxshi)", CLS 0.30001 → "CLS: 0.300 (yomon)". Jami 90/90 (22 suite); lint, format:check, tip — toza.
7. Brauzerda. Kanon tekshiruvidagi haqiqiy chiqish (Chrome 154, lokal server):
[Web Vitals] TTFB: 4 ms (yaxshi)
[Web Vitals] FCP: 44 ms (yaxshi)
[Web Vitals] LCP: 44 ms (yaxshi)
[Web Vitals] INP: 0 ms (yaxshi)
[Web Vitals] CLS: 0.139 (yaxshilash kerak)To'rttasi yashil, CLS esa — 0,139, "yaxshilash kerak": ilovada nimadir sakrayapti. Ko'z bilan qaraganda sahifa "normal" ochiladi — o'lchovsiz buni bilmas edik. Sababini DevTools: Performance, Memory va Network darsida topib, bitta atribut bilan tuzatamiz. O'zingiz Live Server bilan sinang: LCP birinchi bosishda, CLS va INP boshqa tabga o'tib qaytganda chiqadi.
8. Commit va PR:
git add assets/js/olchov.js assets/js/vendor/web-vitals.js \
assets/js/asosiy.js skriptlar/vendor.js package.json \
package-lock.json eslint.config.js .prettierignore sw.js \
tekshiruv/olchov.test.js tekshiruv/sw.test.js
git commit -m \
"feat: Core Web Vitals konsolga yoziladi (web-vitals 6.2.3)"
gh pr create --fill
gh pr merge --mergeDiff: 11 fayl, +123 −8. Bu qadam ikki qarz qoldiradi: natijalar hech qayerga yig'ilmaydi va vendor nusxa o'zi yangilanmaydi. Ular Brauzer xavfsizlik modeli darsidagi qadamda TEXNIK-QARZ.md ga yoziladi (15 va 16-qatorlar).
8. Real ishda
- Google qidiruvi. Core Web Vitals — Google'ning "sahifa tajribasi" signallaridan biri; Search Console hisobotini SEO mutaxassislari muntazam ko'radi.
- RUM xizmatlari. Katta saytlar natijani o'z serveriga yoki tayyor xizmatlarga yuboradi (Sentry, Datadog, Vercel Speed Insights). Next.js'da tayyor
useReportWebVitalshook bor (Next.js — keyingi qismlarda). - Intervyu. "LCP, INP, CLS nima va chegaralari qanday?", "Lab va field farqi?", "CLS'ni qanday kamaytirasiz?" — frontend intervyularida ko'p so'raladi.
Xulosa
- Brauzer muhim voqealarni performance timeline'ga yozadi; ularni
getEntriesByTypevaPerformanceObserver(buffered: true) bilan o'qiysiz. - Core Web Vitals: LCP ≤ 2,5 s (ko'rindimi), INP ≤ 200 ms (javob berdimi), CLS ≤ 0,1 (joyida turdimi) — 75-persentil bo'yicha.
- LCP — eng katta element; CLS — kutilmagan siljishlar, tuzatish — joyni oldindan ajratish; INP — kutish + ishlov + chizish.
web-vitals(6.2.3) — Chrome qanday o'lchasa, shunday o'lchaydi; CLS va INP tab yashirilganda yakunlanadi,sendBeaconbilan yuboriladi.- Lab (Lighthouse) — sinash va sabab topish uchun; field (CrUX, Search Console, o'z RUM'ingiz) — haqiqiy holat uchun.
Keyingi dars: Asosiy thread'ni bo'shatish — INP'ni yaxshilashning asosiy usuli: uzun task'ni bo'laklab, brauzerga bosishga javob berish va kadr chizish imkonini qoldirish (scheduler.yield).
Manbalar
- web.dev: "Web Vitals", "Largest Contentful Paint (LCP)", "Interaction to Next Paint (INP)", "Cumulative Layout Shift (CLS)" — web.dev/articles/vitals
- MDN:
PerformanceObserver,PerformanceNavigationTiming,LargestContentfulPaint,LayoutShift,PerformanceEventTiming— developer.mozilla.org - GoogleChrome/web-vitals README (6.2.3) — github.com/GoogleChrome/web-vitals
- Chrome for Developers: "Chrome UX Report (CrUX)" — developer.chrome.com/docs/crux
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!