Mundarija (37)
- Bu darsda
- 1. Nega bu kerak?
- 2. focus va blur
- 2.1 Maydonga kirish va chiqish
- 2.2 focus bubble qilmaydi
- 3. To'rt hodisa va ularning tartibi
- 3.1 Jadval
- 3.2 Fokus bir elementdan ikkinchisiga o'tganda
- 3.3 relatedTarget: fokus qayerdan, qayerga?
- 4. document.activeElement va focus()
- 4.1 Fokus hozir qayerda?
- 4.2 Fokusni ko'chirish: focus() va blur()
- 4.3 CSS ham fokusni sezadi: :focus-within
- 4.4 Oynaning o'zi fokusni yo'qotganda
- 4.5 DevTools fokusni "o'g'irlaydi"
- 5. scroll hodisasi
- 5.1 Qaysi element aylantirilyapti?
- 5.2 O'qish ko'rsatkichi
- 5.3 scrollend — aylantirish tugadi
- 5.4 scroll ni og'irlashtirmang
- 6. resize hodisasi
- 6.1 Oyna o'lchami o'zgarganda
- 6.2 Debounce: "jimlik bo'lganda bir marta"
- 7. Ko'p uchraydigan xatolar
- 7.1 Formaga focus tinglovchisi
- 7.2 relatedTarget ning null bo'lishini unutish
- 7.3 Fokus olmaydigan elementga focus()
- 7.4 Element scroll ini window da tinglash
- 7.5 scroll va resize da og'ir ish
- 8. Mashqlar
- 1-mashq (oson): Maydonga necha marta kirildi?
- 2-mashq (o'rta): Faol qatorni belgilash
- 3-mashq (qiyin): "Yuqoriga" tugmasi
- 4-mashq: Portfolio qadami — "Yuqoriga" havolasi
- 9. Real ishda
- Xulosa
- Manbalar
Fokus, scroll va resize hodisalari JavaScript'da: focusin, activeElement, scrollend
Qisqacha: Element fokus olganda
focus, yo'qotgandablurkeladi, lekin ular ota elementga ko'tarilmaydi (bubble qilmaydi). Butun forma yoki menyuni kuzatish uchun ko'tariladigan juftlik —focusin/focusout; fokusni o'qish va ko'chirish —document.activeElementvafocus(). Aylantirishgascrollvascrollend, oyna o'lchamigaresizejavob beradi — ular juda tez-tez keladi, shuning uchun tinglovchini yengil yozing.
Bu darsda
focus/blurvafocusin/focusoutfarqini bilasiz va forma yoki menyu darajasida fokusni kuzata olasiz.relatedTargetbilan "fokus guruhdan chiqdimi?" degan savolga javob berasiz.document.activeElementvafocus()bilan fokusni o'qiysiz va ko'chirasiz (xatolar sarlavhasiga).scroll,scrollendvaresizehodisalarini ishlatasiz va ularni "tinchlantirish" (debounce) usulini bilasiz.- Portfolio'ga sahifa aylantirilganda chiqadigan "Yuqoriga" havolasini qo'shasiz.
Oldin bilishingiz kerak: Listener opsiyalari: once, passive, signal, Klaviatura hodisalari va tezkor tugmalar, Element o'lchamlari va scroll, Klaviatura va fokus boshqaruvi.
1. Nega bu kerak?
«Bahor» bron formasida uchta muammo bor.
Birinchisi. Mehmon telefon raqamini yozmay, keyingi maydonga o'tib ketdi. Xatoni qachon aytish kerak? Har harf bosilganda emas — u hali yozyapti. Forma yuborilganda esa kech. Eng qulay payt — mehmon maydondan chiqqanda.
Ikkinchisi. Forma yuborildi va ikkita xato topildi. Ko'zi ko'radigan mehmon qizil yozuvlarni ko'radi. Malika esa hech narsani eshitmaydi: fokus "Yuborish" tugmasida qoldi. Fokus va tahrir atributlari darsida tabindex="-1" li xatolar sarlavhasini yasab, "JavaScript fokusni shu sarlavhaga ko'chiradi" degan edik. Bu dars — o'sha va'da.
Uchinchisi. Menyu sahifasi uzun, telefonda pastga ko'p aylantiriladi. Mehmonga "Yuqoriga" tugmasi kerak — lekin faqat pastga tushgandan keyin. Buni sahifa aylantirilganini sezadigan hodisa hal qiladi.
Uchala muammoning ham kaliti — bu darsdagi hodisalar: fokus, aylantirish (scroll) va oyna o'lchami (resize).
2. focus va blur
2.1 Maydonga kirish va chiqish
Fokus (focus) — klaviatura hozir qaysi element bilan ishlayotgani (Klaviatura va fokus darsida ko'rgansiz). Element fokus olganda focus, yo'qotganda blur hodisasi keladi. Fokus sichqoncha bilan bosish, Tab yoki kod orqali o'zgarishi mumkin — hodisa har uch holatda ham keladi.
<label for="telefon">Telefon</label>
<input id="telefon" type="tel">
<p id="telefon-xato" hidden>Telefon raqamini yozing</p>
<label for="ism">Ism</label>
<input id="ism">
<script>
const telefon = document.getElementById("telefon");
const xato = document.getElementById("telefon-xato");
telefon.addEventListener("focus", () => {
console.log("focus: telefon");
});
telefon.addEventListener("blur", () => {
const bosh = telefon.value.trim() === "";
xato.hidden = !bosh;
console.log(`blur: telefon, bo'sh: ${bosh}`);
});
</script>Telefon maydoniga bosing, hech narsa yozmay Tab bilan "Ism" ga o'ting. Konsolda focus: telefon va blur: telefon, bo'sh: true chiqadi, maydon ostida esa "Telefon raqamini yozing" paydo bo'ladi. Qaytib raqam yozib chiqsangiz — xabar yo'qoladi.
Hayotdan o'xshatish: ofitsiant Otabek stol yonidan ketayotganda buyurtmani bir ko'zdan kechiradi: "choy yozilmay qolibdi-ku". Mehmon gapirib turganda bo'lib qo'ymaydi, oshxonaga borganda ham emas — aynan stoldan ketish paytida.
Tekshirib ko'ring: Nega bu tekshiruvni
focusga emas,blurga yozdik?
Javob
focus paytida mehmon hali hech narsa yozmagan — maydon doim bo'sh. Unda "raqamni yozing" desak, mehmon yozishni boshlamasdan xato ko'radi. blur esa mehmon "tayyorman" deb maydondan chiqqan payt: endi tekshirish adolatli.
Maydondan chiqqanda tekshirish real formalarda ko'p ishlatiladi. Uning to'liq usullarini input va change va Constraint Validation API darslarida ko'ramiz.
2.2 focus bubble qilmaydi
Formada o'nta maydon bo'lsa-chi? Har biriga alohida tinglovchi qo'yish noqulay. Delegation darsidagi g'oya — formaga bitta tinglovchi qo'yish. Lekin focus va blur bilan bu ishlamaydi: ular bubble qilmaydi. Eslatma: bubble — hodisaning elementdan ota elementlarga, pufakchadek, yuqoriga ko'tarilishi. Bunday ko'tarilmaydigan hodisalar borligini Hodisa tarqalishi darsida aytgandik.
<form id="bron">
<label for="ism">Ism</label> <input id="ism">
<label for="tel">Telefon</label> <input id="tel" type="tel">
</form>
<script>
const bron = document.getElementById("bron");
bron.addEventListener("focus", (event) => {
console.log(`forma — focus: ${event.target.id}`);
});
bron.addEventListener("focusin", (event) => {
console.log(`forma — focusin: ${event.target.id}`);
});
bron.addEventListener("focusout", (event) => {
console.log(`forma — focusout: ${event.target.id}`);
});
</script>"Ism" ga bosing, keyin Tab bilan "Telefon" ga o'ting. Konsolda faqat focusin va focusout qatorlari chiqadi (tekshirdik):
forma — focusin: ism
forma — focusout: ism
forma — focusin: telforma — focus umuman chiqmadi: focus maydonda qoldi, formaga ko'tarilmadi. focusin va focusout esa xuddi focus va blur kabi, faqat bubble qiladi.
3. To'rt hodisa va ularning tartibi
3.1 Jadval
| Hodisa | Qachon | Bubble |
|---|---|---|
focus |
Element fokus oldi | yo'q |
blur |
Element fokusni yo'qotdi | yo'q |
focusin |
Element fokus oldi | ha |
focusout |
Element fokusni yo'qotdi | ha |
Qoida: bitta element — focus/blur; guruh (forma, menyu, karta) — focusin/focusout.
O'zingiz to'ldiring (hodisa nomini yozing):
- Formaga bitta tinglovchi: "istalgan maydondan chiqildi" —
- Faqat telefon maydonining o'ziga: "maydondan chiqildi" —
- Formaga bitta tinglovchi: "istalgan maydonga kirildi" —
3.2 Fokus bir elementdan ikkinchisiga o'tganda
Fokus A dan B ga o'tganda to'rtala hodisa ham keladi. Biz Chrome'da tekshirdik — tartib shunday:
flowchart LR
A["blur — A da"] --> B["focusout — A dan, bubble"]
B --> C["focus — B da"]
C --> D["focusin — B dan, bubble"]Nimaga qarang: avval eski element "xayrlashadi" (blur, focusout), keyin yangisi "salomlashadi" (focus, focusin). Ikki juftning har birida bubble qilmaydigani birinchi keladi.
3.3 relatedTarget: fokus qayerdan, qayerga?
Fokus hodisalarining maxsus xususiyati bor — relatedTarget. U "ikkinchi tomonni" ko'rsatadi:
focusoutvablurda — fokus qayerga ketyapti;focusinvafocusda — fokus qayerdan keldi.
Fokus hech qayerga ketmasa (masalan, sahifaning bo'sh joyiga bosilsa yoki brauzerning manzil satriga o'tilsa), relatedTarget — null.
Bu xususiyat guruhdan chiqishni aniqlashga yordam beradi. Bron formasida sana va vaqt bir guruhda. Mehmon ular orasida yurganda hech narsa qilmaymiz. Guruhdan butunlay chiqqandagina "sana va vaqt tanlandimi?" deb tekshiramiz:
<fieldset id="vaqt-guruh">
<legend>Bron vaqti</legend>
<label for="sana">Sana</label> <input id="sana" type="date">
<label for="vaqt">Vaqt</label> <input id="vaqt" type="time">
</fieldset>
<label for="odam">Odam soni</label>
<input id="odam" type="number" min="1" max="20">
<script>
const guruh = document.getElementById("vaqt-guruh");
guruh.addEventListener("focusout", (event) => {
if (guruh.contains(event.relatedTarget)) {
console.log("Guruh ichida yurdi");
return;
}
console.log("Guruhdan chiqdi — sana va vaqtni tekshiramiz");
});
</script>Sanaga bosing va Tab bilan yuring. Sana maydoni ichida Tab avval kun, oy, yil qismlari bo'ylab yuradi — bu paytda fokus maydondan chiqmaydi, hodisa ham yo'q. Sanadan vaqtga o'tganda konsolda Guruh ichida yurdi chiqadi. Vaqtdan "Odam soni" ga o'tganda esa — Guruhdan chiqdi — ... (tekshirdik).
Yangi metod: guruh.contains(element) — "element guruhning ichidami (yoki guruhning o'zimi)?" degan savolga true/false qaytaradi. null berilsa — false. Shuning uchun fokus hech qayerga ketmasa ham kod to'g'ri ishlaydi: "chiqdi".
Tekshirib ko'ring: Mehmon "Ism" maydonidan sichqoncha bilan sahifaning oq joyiga bosdi.
focusoutdagievent.relatedTargetnima bo'ladi?
Javob
null. Oq joy (oddiy p yoki div) fokus olmaydi, demak fokus hech qayerga "ketmadi" — u body ga qaytdi. Shuning uchun relatedTarget bilan ishlaganda doim null holatini hisobga oling.
4. document.activeElement va focus()
4.1 Fokus hozir qayerda?
document.activeElement — hozir fokusda turgan element. Hech narsa fokusda bo'lmasa — document.body:
<label for="ism">Ism</label> <input id="ism">
<script>
console.log(document.activeElement);
console.log(document.activeElement === document.body);
</script>Konsolda:
<body>…</body>
trueSahifa endi ochildi, mehmon hech narsaga bosmagan — fokus body da. Maydonga bosib, DevTools konsolida document.activeElement ni yozsangiz, endi input chiqadi.
Maslahat: DevTools'da fokus qayerda ekanini kuzatish uchun Console panelidagi "ko'z" belgisini (Create live expression) bosing va
document.activeElementni yozing. Endi fokus har o'zgarganda ifoda o'zi yangilanadi —Tabbilan yurib, natijani tepada ko'rasiz.
4.2 Fokusni ko'chirish: focus() va blur()
element.focus() — fokusni shu elementga o'tkazadi, element.blur() — undan olib tashlaydi. Klaviatura hodisalari darsida Ctrl+K bilan qidiruvga shunday o'tgandik.
Endi va'dani bajaramiz — forma yuborilganda fokus xatolar sarlavhasiga o'tadi:
<h2 id="xatolar" tabindex="-1" hidden>Formada 1 ta xato bor</h2>
<label for="tel">Telefon</label> <input id="tel" type="tel">
<button type="button" id="yuborish">Yuborish</button>
<h2 id="oddiy">Oddiy sarlavha</h2>
<button type="button" id="oddiyga">Oddiy sarlavhaga fokus</button>
<script>
const xatolar = document.getElementById("xatolar");
const tel = document.getElementById("tel");
const yuborish = document.getElementById("yuborish");
yuborish.addEventListener("click", () => {
xatolar.hidden = tel.value.trim() !== "";
if (!xatolar.hidden) {
xatolar.focus();
}
console.log(document.activeElement);
});
document.getElementById("oddiyga").addEventListener("click", () => {
document.getElementById("oddiy").focus();
console.log(document.activeElement);
});
</script>Telefonni bo'sh qoldirib "Yuborish" ni bosing: tepada xato sarlavhasi chiqadi va fokus unga o'tadi. Konsolda — <h2 id="xatolar" tabindex="-1">Formada 1 ta xato bor</h2>. Malika NVDA bilan shu sarlavhani darhol eshitadi.
Endi "Oddiy sarlavhaga fokus" ni bosing. Konsolda <button type="button" id="oddiyga">Oddiy sarlavhaga fokus</button> chiqadi — ya'ni fokus tugmada qoldi. Oddiy h2 fokus olmaydi va focus() jim e'tiborsiz qoladi: xato ham, ogohlantirish ham yo'q. Sarlavhaga fokus berish uchun unga tabindex="-1" kerak — xuddi #xatolar dagidek.
Yana uch nozik joy:
hiddenyokidisplay: nonedagi element ham fokus olmaydi. Shuning uchun kodda avvalhiddenolib tashlandi, keyinfocus()chaqirildi. Tartib muhim.focus()elementni ekranga ham aylantirib keladi. Aylantirish kerak bo'lmasa —focus({ preventScroll: true }).focus()bilan kelgan fokusda halqa ko'rinadimi — buni brauzer o'zi hal qiladi (:focus-visible). Majburan ko'rsatish uchunfocus({ focusVisible: true })opsiyasi bor. Baseline ma'lumotiga ko'ra u 2026-yil fevralidan barcha asosiy brauzerlarda (Chrome 145 oxirgi bo'ldi) — yangi imkoniyat.
Diqqat: Fokusni sababsiz ko'chirmang. Sahifa ochilishi bilan fokusni maydonga "otish" yoki har o'zgarishda fokusni sakratish Malikani chalg'itadi: u o'zi qayerda ekanini bilmay qoladi. Fokus faqat mehmon harakatiga javob sifatida ko'chsin: forma yuborildi, oyna ochildi, menyu yopildi. Fokusni boshqarishning to'liq qoidalari — Dinamik UI'da qulaylik darsida.
4.3 CSS ham fokusni sezadi: :focus-within
Ba'zan JavaScript umuman kerak emas. Masalan, maydon fokusda bo'lganda butun qatorni yoritish. CSS'da buning tayyor pseudo-klassi bor — :focus-within: "ichida fokus bor element" (Holat pseudo-klasslari). Faqat ko'rinish o'zgarsa — CSS. Mantiq kerak bo'lsa (tekshirish, saqlash, yopish) — focusin/focusout.
4.4 Oynaning o'zi fokusni yo'qotganda
window da ham focus va blur hodisalari bor. Mehmon boshqa tabga yoki boshqa dasturga o'tganda window da blur keladi, qaytganda — focus. O'yinlar shunday pauza qiladi:
window.addEventListener("blur", () => {
oyinniTuxtat();
});Lekin "mehmon sahifani ko'ryaptimi?" degan savolga bu ishonchli javob emas: oyna fokusda bo'lmasa ham ekranda ko'rinib turishi mumkin (ikki oyna yonma-yon). Buning aniq hodisasi — visibilitychange, uni Sahifa hayot sikli darsida ko'ramiz.
4.5 DevTools fokusni "o'g'irlaydi"
Fokus hodisalarini DevTools bilan tekshirganda bir tuzoq bor. Siz sahifadagi maydonga bosasiz, keyin Console'ga yozish uchun DevTools'ga bosasiz. Shu paytda sahifa fokusni yo'qotadi: maydonda blur keladi, window da ham blur. Sizning blur tinglovchingiz "mehmon maydondan chiqdi" deb ishlab ketadi. Natijada konsolda siz kutmagan qatorlar paydo bo'ladi va xatoni noto'g'ri joydan qidirasiz.
Yechim — DevTools'ning Rendering paneli. Ctrl+Shift+P bosib, "Rendering" deb yozing va panelni oching. Unda Emulate a focused page belgisini yoqing. Endi DevTools'ga bossangiz ham sahifa o'zini "fokusda" deb hisoblaydi va ortiqcha blur kelmaydi.
Xuddi shu muammo :focus uslublarida ham bor: DevTools'ga bosganingizda maydonning fokus halqasi yo'qoladi. Elements panelidagi :hov tugmasi elementga :focus holatini qo'lda "yoqib" qo'yadi. Fokus bilan bog'liq ko'rinishni shunday tekshirasiz.
5. scroll hodisasi
5.1 Qaysi element aylantirilyapti?
Aylantirish (scroll) ikki joyda bo'ladi: butun sahifa yoki overflow: auto li alohida element (Element o'lchamlari va scroll darsidagi scrollTop, scrollHeight, clientHeight). scroll hodisasi aynan aylantirilgan joyga keladi:
- sahifa aylantirilsa —
documentga (u yerdanwindowga ko'tariladi), shuning uchun odatdawindow.addEventListener("scroll", ...); - element aylantirilsa — elementning o'ziga. Bubble qilmaydi:
documentdagi tinglovchi uni sezmaydi.
5.2 O'qish ko'rsatkichi
Menyu ro'yxati alohida qutida aylanadi. Ustida — qancha o'qilganini ko'rsatadigan chiziq:
<style>
#royxat { height: 120px; overflow: auto; border: 1px solid; }
#royxat p { margin: 0; padding: 16px; }
progress { width: 100%; }
</style>
<progress id="korsatkich" max="1" value="0">0%</progress>
<div id="royxat" tabindex="0" aria-label="Menyu ro'yxati">
<p>Osh — 35 000</p><p>Lag'mon — 28 000</p>
<p>Manti — 30 000</p><p>Ko'k choy — 5 000</p>
<p>Somsa — 8 000</p><p>Non — 4 000</p>
</div>
<p>scroll hodisasi: <output id="soni">0</output> marta</p>
<script>
const royxat = document.getElementById("royxat");
const korsatkich = document.getElementById("korsatkich");
const soni = document.getElementById("soni");
let hodisalar = 0;
royxat.addEventListener("scroll", () => {
const eng = royxat.scrollHeight - royxat.clientHeight;
korsatkich.value = royxat.scrollTop / eng;
hodisalar++;
soni.textContent = hodisalar;
});
royxat.addEventListener("scrollend", () => {
console.log(`To'xtadi: ${Math.round(royxat.scrollTop)} px`);
});
</script>Ro'yxatni oxirigacha aylantiring: chiziq to'ladi. Pastdagi hisoblagichga qarang — bitta aylantirishda scroll o'nlab marta keladi. Biz silliq aylantirishni tekshirganimizda bitta harakat 12 tadan 28 tagacha scroll berdi — masofaga qarab. Aylantirish to'xtaganda esa konsolda faqat bitta qator chiqadi: To'xtadi: ... px.
Ikki narsaga e'tibor bering. Birinchi: <div> ga tabindex="0" va nom berildi — aylanadigan qutiga klaviatura bilan ham kirib, strelkalar bilan aylantirish mumkin bo'lsin (Accessible jadvallar darsidagi jadval-orami qoidasi). Ikkinchi: <progress> — tayyor HTML elementi, ekran o'quvchi uni o'zi o'qiydi (Guruhlash va natija elementlari).
5.3 scrollend — aylantirish tugadi
scrollend aylantirish to'liq to'xtaganda bir marta keladi: barmoq qo'yib yuborilgach, silliq aylantirish va scroll-snap tortishi tugagach. Skroll xulqi darsida "hodisalarni keyin o'rganamiz" degan edik — mana, u.
Qachon kerak? "Aylantirish tugagandan keyin bir marta" qilinadigan ish uchun: lenta ostidagi nuqtalardan qaysi biri faol ekanini belgilash, joyni eslab qolish, analitikaga "foydalanuvchi shu yergacha o'qidi" deb yozish.
Baseline ma'lumotiga ko'ra scrollend 2025-yil dekabridan hamma asosiy brauzerlarda bor — oxirgi bo'lib Safari 26.2 qo'shdi. Bu yangi Baseline: eski brauzerlarda hodisa umuman kelmaydi. Shuning uchun muhim ishni faqat unga bog'lamang.
5.4 scroll ni og'irlashtirmang
Brauzer ekranni sekundiga taxminan 60 marta qayta chizadi. Har bir chizilgan rasm — kadr (frame), xuddi multfilmdagi bitta surat kabi. scroll tinglovchisi har kadrda chaqirilishi mumkin. Ichida og'ir ish bo'lsa (ko'p elementni qayta chizish, o'lchamlarni qayta-qayta o'qish), aylantirish "qotadi". Uch qoida:
- Tinglovchida faqat eng kerakli narsa: bir-ikki xususiyatni o'qish va bitta qiymatni yozish.
scrollning o'zinipreventDefaultbilan to'xtatib bo'lmaydi — u aylantirish bo'lib bo'lgandan keyin keladi. Listener opsiyalari darsidagipassiveasosanwheelvatouchmoveuchun muhim edi.- "Element ekranga chiqdimi?" kabi savollar uchun
scrollemas, maxsus kuzatuvchi bor —IntersectionObserver. Animatsiyani kadr bilan sinxronlash —requestAnimationFrame. Ikkalasi ham keyingi qismda.
Tekshirib ko'ring: Aziz
document.addEventListener("scroll", ...)yozdi, lekin menyu qutisini aylantirganda hech narsa bo'lmadi. Nega?
Javob
Element ichidagi aylantirishning scroll hodisasi bubble qilmaydi — u qutining o'zida qoladi va document gacha ko'tarilmaydi. document dagi tinglovchi faqat sahifaning aylantirilishini sezadi. Tuzatish: tinglovchini aylanayotgan qutining o'ziga qo'yish.
6. resize hodisasi
6.1 Oyna o'lchami o'zgarganda
resize — brauzer oynasining o'lchami o'zgarganda window ga keladi. Telefonni yonboshiga burish ham oyna o'lchamini o'zgartiradi. Sinab ko'ring: bu oynaning tepasidagi ekran tugmalari (telefon, planshet, kompyuter) natija oynasining enini o'zgartiradi:
<p>Tepadagi ekran tugmalarini bosing.</p>
<p>Oyna eni: <output id="en"></output></p>
<script>
const en = document.getElementById("en");
en.textContent = `${window.innerWidth} px`;
window.addEventListener("resize", () => {
en.textContent = `${window.innerWidth} px`;
console.log(`resize: ${window.innerWidth} px`);
});
</script>"Telefon" tugmasini bossangiz, konsolda resize: 360 px, "Planshet" da — resize: 768 px chiqadi. Sahifa ochilganda raqam sizning ekraningizga bog'liq, shuning uchun uni bu yerda yozmadik.
resize faqat window da keladi. Alohida elementning o'lchami o'zgarganini (masalan, yon panel kengaydi) bilish uchun boshqa vosita bor — ResizeObserver. "Ekran 40rem dan kengmi?" degan savolga esa resize siz, CSS media query'ning JavaScript'dagi egizagi javob beradi — matchMedia.
6.2 Debounce: "jimlik bo'lganda bir marta"
Kompyuterda brauzer oynasining chetini sichqoncha bilan sudrasangiz, resize o'nlab marta keladi. Har birida og'ir ish (masalan, jadvalni qayta chizish) qilish — isrof. Bizga faqat oxirgi o'lcham kerak.
Yechim — debounce ("tinchlantirish"): har hodisada taymerni qaytadan boshlash. Hodisalar to'xtab, 200 ms jimlik bo'lgandagina ish bir marta bajariladi.
Hayotdan o'xshatish: lift eshigi. Odamlar ketma-ket kirib turibdi — eshik har safar qayta ochiladi va taymeri yangidan boshlanadi. Hech kim kirmay, bir necha soniya o'tgandagina eshik yopiladi va lift jo'naydi. Bitta odam uchun ham, o'n kishi uchun ham — bitta yurish.
Buning uchun setTimeout ning juftini bilish kerak. setTimeout (Callback darsida ko'rgansiz) taymer raqamini qaytaradi. clearTimeout(raqam) esa hali ishlamagan taymerni bekor qiladi. Taymerlarni setTimeout va setInterval darsida to'liq o'rganamiz.
<p>Ekran tugmalarini bosing yoki kompyuterda oynani sudrang.</p>
<script>
let taymer;
let hodisalar = 0;
window.addEventListener("resize", () => {
hodisalar++;
clearTimeout(taymer);
taymer = setTimeout(() => {
const en = window.innerWidth;
console.log(`Yakuniy eni: ${en} px (${hodisalar} ta resize)`);
hodisalar = 0;
}, 200);
});
</script>Ekran tugmasini bossangiz, eni bir zumda o'zgaradi: konsolda Yakuniy eni: 360 px (1 ta resize). Haqiqiy foyda esa oynani qo'lda sudraganda ko'rinadi: qavs ichidagi son o'nlab bo'ladi, qator esa baribir bitta.
Debounce'ning juftligi ham bor — throttle ("sekinlatish"): "ko'pi bilan har 100 ms da bir marta". Ikkalasini va ular qachon kerakligini Debounce va throttle darsida chuqur ko'ramiz.
Tekshirib ko'ring: Debounce kodida
clearTimeout(taymer)qatorini o'chirsak, oynani sudraganda nima bo'ladi?
Javob
Har resize o'z taymerini boshlaydi va hech biri bekor qilinmaydi. Hodisa 30 ta bo'lsa, 200 ms dan keyin konsolga 30 ta qator chiqadi — debounce'ning butun ma'nosi yo'qoladi. clearTimeout — "oldingi rejani bekor qil, yangidan kut" degani.
7. Ko'p uchraydigan xatolar
7.1 Formaga focus tinglovchisi
form.addEventListener("focus", ...) hech qachon ishlamaydi — focus bubble qilmaydi. Tuzatish: guruh uchun focusin/focusout.
7.2 relatedTarget ning null bo'lishini unutish
event.relatedTarget.closest(...) → oq joyga bosilganda Uncaught TypeError: Cannot read properties of null (reading 'closest'). Tarjimasi: "null ning xususiyatini o'qib bo'lmaydi ('closest' ni o'qiyotganda)". Tuzatish: guruh.contains(event.relatedTarget) — u null ni o'zi to'g'ri hal qiladi, yoki ?..
7.3 Fokus olmaydigan elementga focus()
h2, div, p ga focus() — jim hech narsa bo'lmaydi. Tuzatish: tabindex="-1". Yashirin (hidden) elementga ham fokus berib bo'lmaydi — avval ko'rsating.
7.4 Element scroll ini window da tinglash
Qutining aylantirilishi window ga yetib bormaydi. Tuzatish: tinglovchini aylanayotgan elementga qo'ying.
7.5 scroll va resize da og'ir ish
Har hodisada butun ro'yxatni qayta chizish — aylantirish va o'lcham o'zgarishi "qotadi". Tuzatish: tinglovchini yengillashtiring, resize uchun debounce, ko'rinish uchun IntersectionObserver.
8. Mashqlar
1-mashq (oson): Maydonga necha marta kirildi?
"Izoh" maydoniga har kirilganda konsolga Izohga 1-marta kirildi, 2-marta... chiqsin. Maydondan chiqqanda — yozilgan belgilar soni: Chiqildi: 12 ta belgi.
<label for="izoh">Izoh</label>
<textarea id="izoh" rows="2"></textarea>
<button type="button">Saqlash</button>
<script>
// shu yerga yozing
</script>Yechim
<label for="izoh">Izoh</label>
<textarea id="izoh" rows="2"></textarea>
<button type="button">Saqlash</button>
<script>
const izoh = document.getElementById("izoh");
let kirish = 0;
izoh.addEventListener("focus", () => {
kirish++;
console.log(`Izohga ${kirish}-marta kirildi`);
});
izoh.addEventListener("blur", () => {
console.log(`Chiqildi: ${izoh.value.length} ta belgi`);
});
</script>Bitta maydon — shuning uchun focus/blur yetarli. Maydonga "Osh zo'r" yozib, Tab bilan tugmaga o'tsangiz: Izohga 1-marta kirildi, Chiqildi: 8 ta belgi.
2-mashq (o'rta): Faol qatorni belgilash
Bron formasida har maydon o'z <p> ichida. Qaysi maydon fokusda bo'lsa, uning <p> iga faol class'i qo'shilsin, fokus chiqqanda olib tashlansin. Faqat bitta tinglovchi juftligi formada bo'lsin. Konsolga Faol: ism kabi yozing.
Ishora: focusin/focusout, event.target.closest("p") (Elementlarni tanlash), classList.add/remove (classList).
<style>.faol { background: #e8f1ec; }</style>
<form id="bron">
<p><label for="ism">Ism</label> <input id="ism"></p>
<p><label for="tel">Telefon</label> <input id="tel" type="tel"></p>
</form>
<script>
// shu yerga yozing
</script>Yechim
<style>.faol { background: #e8f1ec; }</style>
<form id="bron">
<p><label for="ism">Ism</label> <input id="ism"></p>
<p><label for="tel">Telefon</label> <input id="tel" type="tel"></p>
</form>
<script>
const bron = document.getElementById("bron");
bron.addEventListener("focusin", (event) => {
event.target.closest("p").classList.add("faol");
console.log(`Faol: ${event.target.id}`);
});
bron.addEventListener("focusout", (event) => {
event.target.closest("p").classList.remove("faol");
});
</script>Ikki maydon uchun ham bitta juftlik ishladi — chunki focusin/focusout bubble qiladi. Formaga yana o'nta maydon qo'shsangiz ham kod o'zgarmaydi.
Bu mashq — hodisani o'rganish uchun. Real loyihada aynan shu ko'rinishni bitta CSS qatori beradi: form p:focus-within { background: #e8f1ec; }. JavaScript'ni ko'rinish uchun emas, mantiq uchun saqlang.
3-mashq (qiyin): "Yuqoriga" tugmasi
Uzun menyu sahifasida "Yuqoriga" tugmasi faqat sahifa 200 px dan ko'proq aylantirilganda ko'rinsin. Bosilganda sahifa silliq tepaga qaytsin va fokus sahifa sarlavhasiga o'tsin — aks holda klaviatura foydalanuvchisi pastda qolib ketadi. Aylantirish to'xtaganda konsolga Joy: 0 px kabi yozing.
Ishora: sahifa aylantirilishi — window.scrollY va window dagi scroll; tepaga — window.scrollTo({ top: 0, behavior: "smooth" }) (Element o'lchamlari va scroll). Sarlavhaga fokus — tabindex="-1" va focus({ preventScroll: true }): aylantirishni scrollTo qiladi, focus xalaqit bermasin. scrollend sahifada document ga keladi.
Yechim
<style>
body { margin: 0; padding: 16px; }
main { height: 1200px; }
#yuqoriga { position: fixed; right: 16px; bottom: 16px; }
</style>
<main>
<h1 id="sarlavha" tabindex="-1">«Bahor» menyusi</h1>
<p>Oynani pastga aylantiring.</p>
</main>
<button type="button" id="yuqoriga" hidden>Yuqoriga</button>
<script>
const tugma = document.getElementById("yuqoriga");
const sarlavha = document.getElementById("sarlavha");
window.addEventListener("scroll", () => {
tugma.hidden = window.scrollY < 200;
});
document.addEventListener("scrollend", () => {
console.log(`Joy: ${Math.round(window.scrollY)} px`);
});
tugma.addEventListener("click", () => {
window.scrollTo({ top: 0, behavior: "smooth" });
sarlavha.focus({ preventScroll: true });
});
</script>Oynani pastga aylantirsangiz, o'ng pastda tugma chiqadi va konsolga Joy: ... px yoziladi. Tugmani bosing: sahifa silliq tepaga qaytadi, konsolda Joy: 0 px, tugma esa yana yashirinadi. Sarlavha atrofida fokus halqasi ko'rinmasa ham fokus o'sha yerda: endi Tab bossangiz, sarlavhadan keyingi elementga o'tasiz.
preventScroll: true bo'lmasa nima bo'lardi? focus() sarlavhani ekranga o'zi bir zumda aylantirib kelardi va silliq aylantirish ko'rinmay qolardi. Oddiy havola ham shu ishni qiladi: <a href="#sarlavha">Yuqoriga</a>. Faqat ko'rinish kerak bo'lsa — havola soddaroq. Bu mashqda JavaScript tugmaning paydo bo'lishi uchun kerak.
4-mashq: Portfolio qadami — "Yuqoriga" havolasi
Portfolio'ning uzun sahifalarida (masalan, loyihalar ro'yxati) telefonda pastga ko'p aylantiriladi. Sahifa 400 px dan ko'proq aylantirilganda o'ng pastda "Yuqoriga" havolasi chiqsin.
- Havolani HTML'ga emas,
asosiy.jsda yarating (Element yaratish):<a href="#asosiy" id="yuqoriga">Yuqoriga</a>, boshidahidden. JavaScript ishlamasa, keraksiz havola ham ko'rinmaydi. - Havola
#asosiyga — skip link ham shumainga olib boradi. Havola bo'lgani uchun klaviaturaning keyingiTabi ham o'sha joydan davom etadi: Bobur pastda qolib ketmaydi. JavaScript faqat ko'rsatish uchun kerak — 3-mashqdagi tugmadan farqi shu. windowningscrollhodisasidahiddenniscrollYga qarab almashtiring. Tinglovchi{ passive: true }bilan bo'lsin (Listener opsiyalari).main#asosiyyo'q sahifada (404.html) hech narsa yaratilmasin.asosiy.cssga joylashuv:position: fixed, o'ng past burchak,z-index— kanonik--qatlam-yopishqoq(z-index va qatlamlar).- Va'dani ham bajaring: Element o'lchamlari va scroll darsidagi
yuqoriganiTekshir(bosh sahifa footer'idagi "Yuqoriga") hozircha faqatloadda ishlaydi. Uni oyna o'lchami o'zgarganda ham chaqiring — debounce bilan, «Debounce» bo'limidagidek.
Yechim
asosiy.js ga:
// "Yuqoriga" havolasi: sahifa pastga aylantirilganda chiqadi
if (document.getElementById("asosiy") !== null) {
const yuqoriga = document.createElement("a");
yuqoriga.href = "#asosiy";
yuqoriga.id = "yuqoriga";
yuqoriga.textContent = "Yuqoriga";
yuqoriga.hidden = true;
document.body.append(yuqoriga);
window.addEventListener("scroll", () => {
yuqoriga.hidden = window.scrollY < 400;
}, { passive: true });
}
// Footer'dagi "Yuqoriga": oyna o'lchami o'zgarganda ham tekshir
let yuqoriTaymer;
window.addEventListener("resize", () => {
clearTimeout(yuqoriTaymer);
yuqoriTaymer = setTimeout(yuqoriganiTekshir, 200);
});Ikki "Yuqoriga" bir-biriga xalaqit bermaydi: footer'dagisi — bosh sahifa oxirida, sahifa oynaga sig'sa yashirinadi; suzuvchi #yuqoriga esa har sahifada, faqat 400 px dan pastda chiqadi. Ikkalasi ham bitta manzilga — #asosiy ga olib boradi.
asosiy.css ga:
#yuqoriga {
position: fixed;
inset-inline-end: 1rem;
inset-block-end: 1rem;
z-index: var(--qatlam-yopishqoq);
}Kichik sahifada sinab ko'ring — oynani pastga aylantiring:
<style>
body { margin: 0; }
main { block-size: 1400px; padding: 1px 16px; }
#yuqoriga { position: fixed; inset-inline-end: 1rem;
inset-block-end: 1rem; }
</style>
<main id="asosiy">
<h1>Loyihalar</h1>
<p>Oynani pastga aylantiring.</p>
</main>
<script>
if (document.getElementById("asosiy") !== null) {
const yuqoriga = document.createElement("a");
yuqoriga.href = "#asosiy";
yuqoriga.id = "yuqoriga";
yuqoriga.textContent = "Yuqoriga";
yuqoriga.hidden = true;
document.body.append(yuqoriga);
window.addEventListener("scroll", () => {
yuqoriga.hidden = window.scrollY < 400;
}, { passive: true });
}
document.addEventListener("scrollend", () => {
console.log(`Joy: ${Math.round(window.scrollY)} px`);
});
</script>400 px dan pastda havola chiqadi. Uni bossangiz, sahifa tepaga qaytadi, konsolda Joy: 0 px, havola esa yashirinadi (tekshirdik). scrollend qatori faqat shu sinov uchun — portfolio'ga kerak emas.
Portfolio CSS'ida [hidden] qoidasi !important bilan turibdi (Kaskad) — shuning uchun #yuqoriga dagi position yashirin havolani ko'rsatib qo'ymaydi.
Commit: JS: aylantirilganda chiqadigan Yuqoriga havolasi.
9. Real ishda
- Formalar — "maydondan chiqqanda tekshirish" (
blur) deyarli har bir zamonaviy formada bor. React kabi freymvorklarda (kursda alohida o'rganamiz) uonBlurdeb ataladi, lekin ichida shu hodisa ishlaydi. - Dropdown, menyu, combobox — "fokus chiqdi → yopish" naqshi
focusout+relatedTargetbilan yoziladi. ARIA Authoring Practices darsidagi naqshlar shunga tayanadi. - Cheksiz lenta, "Yuqoriga" tugmasi, o'qish ko'rsatkichi —
scrollvascrollend. Katta loyihalarda ko'rinishni kuzatishIntersectionObserverga o'tkaziladi. - Grafiklar va xaritalar — konteyner o'lchami o'zgarganda qayta chiziladi:
resize+ debounce yokiResizeObserver. - Intervyuda: "
focusvafocusinfarqi?", "Debounce nima va qachon kerak?", "scrollhodisasini qanday optimallashtirasiz?" — tez-tez so'raladi.
Xulosa
focus/blur— bubble qilmaydi, bitta element uchun;focusin/focusout— bubble qiladi, forma va menyu uchun.- Tartib:
blur→focusout→focus→focusin;relatedTarget— fokusning ikkinchi tomoni (yokinull). document.activeElement— hozir fokusda turgan element;focus()— faqat fokus oladigan, ko'rinadigan elementga (tabindex="-1"),preventScrollbilan.scroll— aylantirilgan joyda keladi, elementdan bubble qilmaydi, juda tez-tez;scrollend— to'xtaganda bir marta (Baseline 2025-12).resize— faqatwindowda; ko'p keladi, shuning uchun debounce (setTimeout+clearTimeout).
Keyingi dars: Custom events va EventTarget — brauzer hodisalarini tinglashdan o'z hodisalaringizni yaratishga o'tamiz.
Manbalar
- MDN: "Element: focusin event", "Element: focusout event", "FocusEvent: relatedTarget property" — developer.mozilla.org
- MDN: "Document: activeElement property", "HTMLElement: focus() method" — developer.mozilla.org
- MDN: "Element: scroll event", "Element: scrollend event", "Window: resize event" — developer.mozilla.org
- web.dev Baseline: "scrollend" — web-features ma'lumotlari
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!