IlmHamroh
JavaScript Full-stack/9-qism. JavaScript brauzerda va debugging10/29-dars22 daqiqa
Mundarija (34)

Element o'lchamlari va scroll: getBoundingClientRect, offsetWidth, scrollTop va scrollIntoView

Qisqacha: Element qancha joy egallashini uch oila xususiyat aytadi: offsetWidth — chegarasi bilan, clientWidth — chegarasiz ichki qismi, scrollWidth — ko'rinmay turgan kontent bilan birga. Ekranning qayerida turganini getBoundingClientRect() beradi. Aylantirish uchun scrollTop ga qiymat yozasiz yoki scrollTo, scrollBy, scrollIntoView metodlarini chaqirasiz — behavior: "smooth" bilan silliq.

Bu darsda

  • offset*, client* va scroll* xususiyatlari nimani o'lchashini va qaysi biri qachon kerakligini bilasiz.
  • getBoundingClientRect() bilan elementning ekrandagi joyini topib, uning yoniga boshqa element qo'ya olasiz.
  • Ro'yxatni JavaScript bilan aylantira olasiz va "oxiriga yetdimi?" savoliga javob bera olasiz.
  • Oyna o'lchamlarini (innerWidth, clientWidth, scrollY) farqlay olasiz.
  • O'lcham o'qish nega qimmat ekanini va qaysi tuzoqlar borligini tushuntira olasiz.

Oldin bilishingiz kerak: Ko'p elementni samarali chizish, Box model, position va containing block, Skroll xulqi va scroll-snap.

1. Nega bu kerak?

Hozirgacha DOM'dan ma'no o'qidik: matn, atribut, class. Lekin ba'zi vazifalarda boshqa savol turadi: element qancha joy egallaydi va qayerda turibdi?

«Bahor» saytidagi vaziyatlar:

  • "Bron qilish" tugmasi ustiga ishora (tooltip) chiqishi kerak — aynan tugmaning ostida, tugma qayerda bo'lmasin.
  • Bron formasida xato bo'lsa, sahifa o'sha maydonga o'zi aylanib borishi kerak.
  • Menyu ro'yxati uzun. "Boshiga qaytish" tugmasi va "ro'yxat oxiriga yetdingiz" yozuvi kerak.
  • Sahifa qisqa bo'lsa, "Yuqoriga" havolasining keragi yo'q.

CSS'da o'lchamni berasiz: width: 200px. JavaScript'da esa brauzer hisoblab bo'lgan haqiqiy o'lchamni o'qiysiz. Box model darsidagi qutilarni eslang — bugun o'sha qutilarni raqam bilan ko'ramiz.

2. Uch oila: offset, client, scroll

2.1 Bitta karta, uch xil o'lcham

Kartaga aniq o'lchamlar beramiz va JavaScript'dan so'raymiz:

html
<style>
  #karta {
    width: 200px;
    height: 100px;
    padding: 10px;
    border: 2px solid #1f6f4a;
    margin: 20px;
  }
</style>
<div id="karta">Osh — 35 000 so'm</div>
<script>
  const karta = document.getElementById("karta");
  console.log(karta.offsetWidth, karta.offsetHeight);
  console.log(karta.clientWidth, karta.clientHeight);
  console.log(karta.scrollWidth, karta.scrollHeight);
</script>

Konsolda:

text
224 124
220 120
220 120

Hisoblab chiqamiz (eni bo'yicha):

  • offsetWidth = kontent 200 + padding 10 × 2 + chegara 2 × 2 = 224. Ko'zga ko'rinadigan butun quti.
  • clientWidth = kontent 200 + padding 20 = 220. Chegaraning ichi.
  • scrollWidth — ichidagi kontent to'liq sig'ib turibdi, shuning uchun clientWidth bilan teng: 220.
  • margin (20px) hech birida yo'q. Tashqi bo'shliq — elementning o'lchami emas, qo'shnilar bilan masofa.

Hamma qiymatlar — piksel, px siz oddiy son.

Diagrammada uch oila ichma-ich qutilar sifatida — tashqisidan ichkarisiga:

flowchart TB
  subgraph O["offsetWidth = 224: chegara bilan"]
    subgraph C["clientWidth = 220: padding bilan"]
      K["kontent: 200"]
    end
  end
  M["margin — hech biriga kirmaydi"]

Hayotdan o'xshatish: choynak qutisi. offset — qutining tashqi o'lchami, karton devori bilan. client — ichki bo'shliq, devorsiz. scroll — qutiga solingan hamma narsa, hatto qopqoqdan chiqib turgan qismi bilan. Qutilar orasidagi masofa esa (margin) — qutining o'lchami emas.

2.2 scroll* — ko'rinmay turgan kontent

scroll* oilasining ma'nosi kontent sig'may qolganda ochiladi. Menyuda 6 ta taom, har biri 40px, lekin ro'yxat balandligi — 120px. Ortiqchasi aylantirish (scroll) ortida:

html
<style>
  #menyu {
    height: 120px;
    overflow: auto;
    margin: 0;
    padding: 0;
    list-style: none;
    border: 1px solid #c9d6cf;
  }
  #menyu li { height: 40px; }
</style>
<ul id="menyu">
  <li>Osh</li>
  <li>Manti</li>
  <li>Lag'mon</li>
  <li>Ko'k choy</li>
  <li>Somsa</li>
  <li>Shashlik</li>
</ul>
<script>
  const menyu = document.getElementById("menyu");
  console.log(menyu.offsetHeight, menyu.clientHeight);
  console.log(menyu.scrollHeight);
</script>

Konsolda:

text
122 120
240
  • offsetHeight — 122: 120 + yuqori va pastki chegara.
  • clientHeight — 120: ko'rinadigan "deraza".
  • scrollHeight — 240: 6 × 40, ya'ni kontentning to'liq balandligi, ko'rinmaydigan qismi bilan.

Demak, scrollHeight > clientHeight — "kontent sig'madi, aylantirish bor" degani. Somsa va shashlik shu misol uchun qo'shilgan, «Bahor» menyusiga kirmaydi.

Tekshirib ko'ring: Kartaga box-sizing: border-box qo'shilsa (Box model), offsetWidth va clientWidth nechaga teng bo'ladi?

Javob

offsetWidth — 200, clientWidth — 196. border-box da width: 200px chegara bilan birga hisoblanadi: tashqi quti 200 bo'ladi. Ichkarisi — 200 dan ikki chegarani (4px) ayirganda 196. Kontentning o'zi esa 176 ga torayadi: 196 dan padding (20px) chiqadi. "Tahrirlab ko'rish" bilan tekshiring.

3. Aylantirish: scrollTop va uning chegaralari

3.1 scrollTop — o'qish va yozish

scrollTop — kontent qancha piksel yuqoriga surilganini bildiradi. Uni o'qish ham, unga yozish ham mumkin. Yozsangiz — element o'sha joyga aylanadi:

html
<style>
  #menyu {
    height: 120px;
    overflow: auto;
    margin: 0;
    padding: 0;
    list-style: none;
    border: 1px solid #c9d6cf;
  }
  #menyu li { height: 40px; }
</style>
<ul id="menyu">
  <li>Osh</li>
  <li>Manti</li>
  <li>Lag'mon</li>
  <li>Ko'k choy</li>
  <li>Somsa</li>
  <li>Shashlik</li>
</ul>
<script>
  const menyu = document.getElementById("menyu");
  console.log(menyu.scrollTop);

  menyu.scrollTop = 80;
  console.log(menyu.scrollTop);

  menyu.scrollTop = 1000;
  const eng = menyu.scrollHeight - menyu.clientHeight;
  console.log(menyu.scrollTop, eng);

  const oxirida =
    Math.ceil(menyu.scrollTop + menyu.clientHeight) >=
    menyu.scrollHeight;
  console.log(`Oxiriga yetdi: ${oxirida}`);
</script>

Konsolda:

text
0
80
120 120
Oxiriga yetdi: true
  • Boshida 0 — hech narsa surilmagan.
  • 80 yozdik — ro'yxat ikki taomga suriladi.
  • 1000 yozdik, lekin 120 bo'ldi. Brauzer qiymatni chegaraga "qisib" qo'ydi: kontentdan ortiq surib bo'lmaydi. Eng katta qiymat — scrollHeight - clientHeight.
  • "Oxiriga yetdimi?" formulasi: surilgan + ko'rinadigan ≥ to'liq balandlik.

Natija tabiga qarang: ro'yxat oxirgi uchta taomni ko'rsatib turibdi.

Formuladagi Math.ceil nega kerak? Telefonlarda scrollTop kasr bo'lishi mumkin (masalan 119.5) — ekran piksellari zichligi tufayli. Math.ceil yuqoriga yaxlitlab, "deyarli oxirida"ni ham "oxirida" deb hisoblaydi (Math obyekti).

3.2 Foizda: "qancha o'qildi?"

Uzun maqolalarda tepada "o'qish chizig'i" bo'ladi — qancha o'qilganini ko'rsatadi. Uning hisobi bitta formula:

html
<style>
  #matn { height: 120px; overflow: auto; margin: 0; }
  #matn p { height: 60px; margin: 0; }
</style>
<div id="matn">
  <p>Osh — o'zbek dasturxonining toji.</p>
  <p>Guruch, go'sht, sabzi va piyoz.</p>
  <p>Qozonda, olov ustida pishiriladi.</p>
  <p>«Bahor»da har kuni tayyorlanadi.</p>
</div>
<script>
  const matn = document.getElementById("matn");
  matn.scrollTop = 60;
  const jami = matn.scrollHeight - matn.clientHeight;
  const foiz = Math.round((matn.scrollTop / jami) * 100);
  console.log(`O'qildi: ${foiz}%`);
</script>

Konsolda:

text
O'qildi: 50%

To'liq balandlik 240, deraza 120 — surish mumkin bo'lgan masofa 120. 60 surilgan — demak yarmi. Haqiqiy o'qish chizig'ida bu hisob har aylantirishda qayta bajariladi. Buning uchun scroll hodisasi kerak — uni Fokus, scroll va resize hodisalari darsida qo'shamiz.

Tekshirib ko'ring: Kontent derazaga to'liq sig'sa (scrollHeight === clientHeight), formuladagi jami nechaga teng va natija nima bo'ladi?

Javob

jami — 0, va 0 / 0 — NaN (Number turi). Konsolda O'qildi: NaN% chiqadi. Aylantirish yo'q bo'lsa, hisobni umuman qilmaslik kerak: avval if (jami === 0) bilan tekshiring va, masalan, 100% deb oling.

4. getBoundingClientRect — ekrandagi joy

4.1 To'rtburchak

offset* va client* o'lchamni beradi, lekin "ekranning qayerida?" degan savolga to'liq javob bermaydi. Buning uchun getBoundingClientRect() bor. U elementni o'rab turgan to'rtburchakni qaytaradi — ko'rinadigan oyna (viewport) chap-yuqori burchagiga nisbatan:

html
<style>
  body { margin: 0; }
  #tugma {
    display: block;
    width: 120px;
    height: 40px;
    margin: 30px 0 0 50px;
  }
</style>
<button id="tugma" type="button">Bron qilish</button>
<script>
  const tugma = document.getElementById("tugma");
  const joy = tugma.getBoundingClientRect();
  console.log(joy.top, joy.left);
  console.log(joy.width, joy.height);
  console.log(joy.bottom, joy.right);
  console.log(joy.x === joy.left, joy.y === joy.top);
</script>

Konsolda:

text
30 50
120 40
70 170
true true

Natijadagi obyekt — DOMRect. Uning xususiyatlari:

Xususiyat Ma'nosi Bu yerda
top, left Yuqori va chap chetgacha masofa 30, 50
bottom, right Pastki va o'ng chetgacha (yuqori/chapdan) 70, 170
width, height O'lcham — offset* ga o'xshash 120, 40
x, y left va top ning sinonimi 50, 30

E'tibor bering: bottom — pastdan emas, tepadan pastki chetgacha masofa: 30 + 40 = 70. CSS'dagi bottom bilan adashtirmang.

Konsolga console.log(joy) ni to'g'ridan-to'g'ri yozsangiz, haqiqiy Chrome'da DOMRect {x: 50, y: 30, width: 120, …} chiqadi. Jonli oyna konsoli bu obyektni ochib ko'rsatmaydi — shuning uchun xususiyatlarni alohida chiqardik.

4.2 Viewport'ga nisbatan — aylantirsa o'zgaradi

getBoundingClientRect doim hozirgi ko'rinishga nisbatan. Sahifa yoki ota element aylansa, qiymat ham o'zgaradi. offsetTop esa o'zgarmaydi:

html
<style>
  body { margin: 0; }
  #menyu {
    position: relative;
    height: 120px;
    overflow: auto;
    margin: 0;
    padding: 0;
    list-style: none;
  }
  #menyu li { height: 40px; }
</style>
<ul id="menyu">
  <li>Osh</li>
  <li>Manti</li>
  <li>Lag'mon</li>
  <li>Ko'k choy</li>
  <li>Somsa</li>
</ul>
<script>
  const menyu = document.getElementById("menyu");
  const choy = menyu.children[3];
  console.log(choy.offsetTop, choy.offsetParent === menyu);
  console.log(choy.getBoundingClientRect().top);

  menyu.scrollTop = 80;
  console.log(choy.offsetTop);
  console.log(choy.getBoundingClientRect().top);
</script>

Konsolda:

text
120 true
120
120
40
  • offsetTop — 120: ko'k choy ro'yxat boshidan 3 × 40 piksel pastda. U joylashuv (layout) bo'yicha hisoblanadi va aylantirishga bog'liq emas.
  • offsetParent — offsetTop qaysi elementga nisbatan hisoblanganini aytadi. Bu eng yaqin position berilgan ota. Biz #menyu ga position: relative berdik (position va containing block). Bunday ota bo'lmasa — body.
  • getBoundingClientRect().top — avval 120, 80 piksel surilgach 40. Ko'k choy ko'rinishda yuqoriga ko'tarildi.

Qoida: "hozir ekranning qayerida?" — getBoundingClientRect. "Hujjat ichida qayerda, aylantirishdan qat'i nazar?" — offsetTop yoki getBoundingClientRect().top + window.scrollY (sahifa darajasida, pastda ko'ramiz).

4.3 Layout va ko'rinish: transform farqi

2D transformlar darsida transform layout'ga ta'sir qilmasligini ko'rgandik: element joyida "turadi", faqat ko'rinishi o'zgaradi. O'lchamlar ham shuni tasdiqlaydi:

html
<style>
  .karta {
    width: 200px;
    height: 40px;
    border: 2px solid #1f6f4a;
  }
  #kichik { transform: scale(0.5); }
  #kasr { width: 100.5px; }
</style>
<div class="karta" id="kichik">Manti</div>
<div class="karta" id="kasr">Lag'mon</div>
<script>
  const kichik = document.getElementById("kichik");
  console.log(kichik.offsetWidth);
  console.log(kichik.getBoundingClientRect().width);

  const kasr = document.getElementById("kasr");
  console.log(kasr.offsetWidth);
  console.log(kasr.getBoundingClientRect().width);
</script>

Konsolda:

text
204
102
105
104.5
  • scale(0.5) dan keyin offsetWidth — hali ham 204 (layout o'lchami). getBoundingClientRect — 102: ekranda ko'rinadigan o'lcham.
  • Kasr kenglikda offsetWidth butun songa yaxlitlanadi (105), getBoundingClientRect esa aniq qiymatni beradi 104.5-bob.

Xulosa: ekrandagi haqiqiy joy kerak bo'lsa (tooltip, sudrab olib borish) — getBoundingClientRect. Layout hisoblari uchun — offset*.

5. Amaliy holat: tugma ostiga ishora

Bilganlarimizni birlashtiramiz. "Bron qilish" tugmasi bosilganda uning ostida ishora chiqsin: "Bron 10:00 dan 22:00 gacha". Ishora position: fixed — u viewport'ga nisbatan joylashadi, xuddi getBoundingClientRect qiymatlari kabi. Ikkalasi bir xil "koordinatalar tizimi"da — shuning uchun qiymatni to'g'ridan-to'g'ri berish mumkin.

Bosishga javob uchun yana DOM bo'ylab harakatlanish darsidagi qolipdan foydalanamiz: addEventListener("click", ...) (addEventListener asoslari da to'liq):

html
<style>
  body { margin: 0; }
  #tugma {
    display: block;
    width: 120px;
    height: 40px;
    margin: 30px 0 80px 50px;
  }
  #ishora {
    position: fixed;
    margin: 0;
    padding: 4px 8px;
    background: #1f2a24;
    color: #fff;
  }
</style>
<button id="tugma" type="button">Bron qilish</button>
<p id="ishora" hidden>Bron 10:00 dan 22:00 gacha</p>
<script>
  const tugma = document.getElementById("tugma");
  const ishora = document.getElementById("ishora");

  function ishoraniKorsat() {
    const joy = tugma.getBoundingClientRect();
    ishora.style.top = `${joy.bottom + 8}px`;
    ishora.style.left = `${joy.left}px`;
    ishora.hidden = false;
    console.log(`Ishora: top ${joy.bottom + 8}, left ${joy.left}`);
  }

  tugma.addEventListener("click", ishoraniKorsat);
</script>

Tugmani bosing: uning ostida, 8 piksel pastda, qora ishora paydo bo'ladi, konsolda — Ishora: top 78, left 50. "Tahrirlab ko'rish" bilan tugmaning margin ini o'zgartiring — ishora tugmaga "ergashadi", chunki joy har bosishda qaytadan o'lchanadi.

Ikki muhim eslatma:

  • O'lchashni bosish paytida qiling, oldindan emas. Sahifa aylanadi, oyna o'lchami o'zgaradi — oldindan saqlangan raqam eskiradi.
  • Bugun CSS buni JavaScript'siz ham qila oladi. Anchor positioning darsida ishorani tugmaga CSS bilan "bog'lagan" edingiz. Brauzer qo'llasa — CSS yo'li yaxshiroq: aylantirishda ham, o'lcham o'zgarganda ham o'zi yangilanadi. JavaScript hisobi — eski brauzerlar uchun zaxira va murakkab holatlar uchun.

6. Oyna va sahifa darajasida

6.1 Oyna o'lchamlari

Element o'rniga butun oyna kerak bo'lsa:

Xususiyat Nima
window.innerWidth, innerHeight Oyna o'lchami, aylantirish chizig'i (scrollbar) bilan
document.documentElement.clientWidth Oyna eni, scrollbar'siz — kontent uchun haqiqiy joy
window.scrollY, scrollX Sahifa qancha aylantirilgan
document.documentElement.scrollHeight Butun sahifaning balandligi

Bu qiymatlar har kimda har xil — ekranga, brauzerga, sahifa qayergacha aylantirilganiga bog'liq. Jonli oyna esa sahifa ichidagi kichik oyna, uning o'lchami haqiqiy ekranniki emas. Shuning uchun ularni haqiqiy sahifada sinang: F12 → Console va birma-bir yozing:

js
window.innerWidth;     // masalan 1280
window.scrollY;        // hozir qancha aylantirilgan

const sahifa = document.documentElement;
sahifa.clientWidth;    // masalan 1265 — scrollbar ayirildi
sahifa.scrollHeight;   // butun sahifa balandligi

// Elementning sahifa boshidan masofasi (aylantirishdan qat'i nazar):
const joy = $0.getBoundingClientRect();
joy.top + window.scrollY;

innerWidth va clientWidth farqi — scrollbar'ning eni. Windows'da u odatda 15–17 piksel, telefonda va macOS'da ko'pincha 0 (scrollbar kontent ustida suzadi). Media query'lar (Media queries) innerWidth ga mos keladi — scrollbar bilan.

Oxirgi ikki qatorga qarang. getBoundingClientRect().top — viewport'ga nisbatan; unga scrollY qo'shsak, sahifa boshidan masofa chiqadi. Bu raqam aylantirsangiz ham o'zgarmaydi.

6.2 Sahifani aylantirish

Butun sahifa uchun window ning metodlari:

js
// boshiga, silliq
window.scrollTo({ top: 0, behavior: "smooth" });
// yana 300px pastga
window.scrollBy({ top: 300, behavior: "smooth" });
// eski shakl, darhol
window.scrollTo(0, 500);
  • scrollTo — aniq joyga ("500-pikselga bor").
  • scrollBy — joriy joydan qancha ("yana 300 piksel").
  • behavior: "smooth" — silliq, "instant" — darhol, "auto" — CSS'dagi scroll-behavior qiymatiga qarab.

Xuddi shu metodlar istalgan aylanadigan elementda ham bor: menyu.scrollTo({ top: 0 }). Baseline ma'lumotlariga ko'ra, behavior parametri hamma asosiy brauzerlarda 2020-yildan beri ishlaydi (keng tarqalgan — 2023-yildan).

Maslahat: behavior ni yozmasangiz, u "auto" bo'ladi va CSS'ga bo'ysunadi. Skroll xulqi darsida scroll-behavior: smooth ni prefers-reduced-motion: no-preference sharti ichiga yozgan edingiz (prefers-reduced-motion). Shunda harakatni kamaytirishni so'ragan foydalanuvchiga JavaScript ham silliq emas, darhol aylantiradi. JavaScript'da "smooth" ni qattiq yozib qo'yish bu sozlamani buzadi.

7. scrollIntoView — elementni ko'rinishga olib kelish

Ko'pincha "nechta pikselga?" emas, "shu element ko'rinsin" degan vazifa turadi. Buning uchun element.scrollIntoView() bor. U kerakli ota elementlarni o'zi aylantiradi:

html
<style>
  #menyu {
    height: 120px;
    overflow: auto;
    margin: 0 0 8px;
    padding: 0;
    list-style: none;
    border: 1px solid #c9d6cf;
  }
  #menyu li { height: 40px; }
  @media (prefers-reduced-motion: no-preference) {
    #menyu { scroll-behavior: smooth; }
  }
</style>
<ul id="menyu">
  <li>Osh</li>
  <li>Manti</li>
  <li>Lag'mon</li>
  <li>Ko'k choy</li>
  <li>Somsa</li>
  <li id="shashlik">Shashlik</li>
</ul>
<button type="button" id="korsat">Shashlikni ko'rsat</button>
<button type="button" id="boshiga">Boshiga</button>
<script>
  const menyu = document.getElementById("menyu");
  const shashlik = document.getElementById("shashlik");

  function shashlikniKorsat() {
    shashlik.scrollIntoView({ block: "nearest" });
  }
  function boshigaQayt() {
    menyu.scrollTo({ top: 0 });
  }

  const korsat = document.getElementById("korsat");
  korsat.addEventListener("click", shashlikniKorsat);
  const boshiga = document.getElementById("boshiga");
  boshiga.addEventListener("click", boshigaQayt);
</script>

"Shashlikni ko'rsat" ni bosing — ro'yxat silliq aylanib, shashlik pastki chetda to'xtaydi. "Boshiga" — osh'ga qaytadi. Silliqlik JavaScript'dan emas, CSS'dan keldi: tizimda harakat kamaytirilgan bo'lsa, aylanish darhol bo'ladi.

scrollIntoView ning block parametri element derazaning qayerida to'xtashini aytadi:

block Element qayerda to'xtaydi
"start" (sukut bo'yicha) Yuqori chetda
"center" O'rtada
"end" Pastki chetda
"nearest" Eng kam aylantirish bilan; allaqachon ko'rinsa — umuman aylanmaydi

Gorizontal yo'nalish uchun xuddi shunday inline parametri bor.

7.1 Nega "nearest"?

scrollIntoView faqat eng yaqin otani emas, hamma aylanadigan ota-bobolarni aylantiradi — butun sahifani ham. Sukut bo'yicha ("start") u elementni ekran tepasiga olib chiqishga harakat qiladi va sahifa kutilmaganda "sakraydi".

Biz buni o'zimiz sinab ko'rdik: jonli oyna ichida "start" bilan chaqirilgan scrollIntoView dars sahifasining o'zini ham aylantirib yubordi. "nearest" esa element allaqachon ko'rinib turgan bo'lsa, ortiqcha harakat qilmaydi.

Yangi imkoniyat ham bor: container: "nearest" parametri faqat eng yaqin aylanadigan otani aylantiradi, sahifaga tegmaydi. Baseline ma'lumotlariga ko'ra, 2026-yil oktabr holatida u faqat Chrome va Edge 140+ da bor — hali Baseline emas. Tushunmagan brauzer uni shunchaki e'tiborsiz qoldiradi.

7.2 Forma xatosiga aylantirish

scrollIntoView ning eng ko'p uchraydigan ishi — bron formasida birinchi xato maydonini ko'rsatish. U ko'pincha fokus bilan birga yoziladi:

js
const xatoMaydon = document.querySelector("[aria-invalid='true']");
xatoMaydon?.scrollIntoView({ block: "center" });
xatoMaydon?.focus();

Forma tekshiruvini Constraint Validation API darsida to'liq yozamiz. Bir nozik joy: focus() ning o'zi ham elementni ko'rinishga olib keladi, lekin qayerda to'xtashini siz boshqarmaysiz. block: "center" maydonni ekran o'rtasiga qo'yadi — yopishqoq header ostida qolib ketmaydi.

Tekshirib ko'ring: Sahifada yopishqoq header bor. scrollIntoView({ block: "start" }) dan keyin sarlavha header ostida qolib ketdi. JavaScript'ni o'zgartirmasdan qanday tuzatasiz?

Javob

CSS bilan: scroll-margin-top (elementga) yoki scroll-padding-top (aylanadigan otaga — sahifa uchun html). Skroll xulqi darsida portfolio'da aynan scroll-padding-block-start ni qo'ygan edingiz. scrollIntoView bu qiymatlarni hurmat qiladi va elementni chetdan shuncha pastda to'xtatadi.

8. O'lcham o'qish — bepul emas

Ko'p elementni samarali chizish darsida offsetHeight ni sikl ichida o'qish sahifani 12 baravar sekinlashtirganini o'lchagan edik. Bu darsdagi hamma xususiyat o'sha guruhdan: offset*, client*, scroll*, getBoundingClientRect(), scrollIntoView(). DOM o'zgargandan keyin ularni o'qisangiz, brauzer shu zahoti layout qiladi.

Qoidalar o'sha:

  • Bir xil qiymatni sikl ichida qayta-qayta o'qimang — bir marta o'qib, o'zgaruvchiga saqlang.
  • Bir nechta elementni o'lchash kerak bo'lsa — avval hammasini o'lchang, keyin hammasini o'zgartiring.
  • O'lchamni doimiy kuzatish kerak bo'lsa (element kattalashdimi?) — har safar so'rash o'rniga, brauzerning maxsus "kuzatuvchisi"dan foydalaniladi: ResizeObserver va IntersectionObserver. Ularni keyinroq o'rganamiz.

Qaysi o'lchamni tanlash — diagrammada. Yuqoridan o'qing:

flowchart TD
  S{"Nima kerak?"}
  S -->|"ekrandagi joy"| R["getBoundingClientRect"]
  S -->|"o'lcham"| O{"Chegara kerakmi?"}
  O -->|ha| OW["offsetWidth / offsetHeight"]
  O -->|"yo'q, ichi"| CW["clientWidth / clientHeight"]
  S -->|"kontent sig'dimi?"| SH["scrollHeight va clientHeight"]
  S -->|"qancha aylangan?"| ST["scrollTop / scrollY"]

9. Ko'p uchraydigan xatolar

9.1 style.width dan o'lcham kutish

html
<style>
  #karta { width: 200px; padding: 10px; }
</style>
<div id="karta">Osh</div>
<script>
  const karta = document.getElementById("karta");
  console.log(`"${karta.style.width}"`);
  console.log(karta.offsetWidth);
</script>

Konsolda:

text
""
220

style.width — bo'sh satr. classList va stillar darsidan eslang: element.style faqat style="..." atributida yozilgan qiymatni ko'radi, CSS faylidagini emas. Haqiqiy o'lcham — offsetWidth yoki getBoundingClientRect(). Hisoblangan CSS qiymati kerak bo'lsa — getComputedStyle(karta).width ("200px", satr).

9.2 Yashirin elementni o'lchash

display: none yoki hidden element joy egallamaydi — uning hamma o'lchami 0, offsetParent esa null. Masalan, ishorani ko'rsatishdan oldin uning balandligini o'lchab, joyini hisoblamoqchi bo'lsangiz — 0 olasiz. Tuzatish: avval ko'rsating (hidden = false), keyin o'lchang. Ikkalasi bitta skript ichida bo'lsa, foydalanuvchi oraliq holatni ko'rmaydi — brauzer skript tugagach chizadi (Ko'p elementni samarali chizish).

9.3 Silliq aylantirishdan keyin darhol o'qish

html
<style>
  #menyu { height: 80px; overflow: auto; margin: 0; }
  #menyu li { height: 40px; }
</style>
<ul id="menyu">
  <li>Osh</li>
  <li>Manti</li>
  <li>Lag'mon</li>
  <li>Ko'k choy</li>
</ul>
<script>
  const menyu = document.getElementById("menyu");
  menyu.scrollTo({ top: 80, behavior: "smooth" });
  console.log(menyu.scrollTop);
</script>

Konsolda:

text
0

80 kutgan edik. Silliq aylanish bir zumda emas, bir necha yuz millisekund davom etadi — scrollTo esa harakatni boshlab, darhol qaytadi. Keyingi qatorda element hali joyidan qo'zg'almagan. Tuzatish: natija darhol kerak bo'lsa — behavior siz (yoki "instant"). Aylanish tugashini kutish kerak bo'lsa — scrollend hodisasi bor (Baseline: 2025-yil dekabrdan, yangi), hodisalarni esa Fokus, scroll va resize hodisalari darsida o'rganamiz.

9.4 Rasm yuklanmasdan o'lchash

width va height atributisiz <img> yuklanmaguncha 0 balandlikka ega. defer skript rasmni kutmaydi (Brauzer muhiti), shuning uchun u o'lchagan sahifa balandligi rasm kelgach o'zgaradi. Tuzatish: rasmlarga doim width va height yozing (Rasm qo'yish: img asoslari darsidagi qoida). Butun sahifa o'lchami kerak bo'lsa — load hodisasidan keyin o'lchang.

9.5 margin ni o'lchamda qidirish

offsetWidth ham, getBoundingClientRect().width ham margin ni o'z ichiga olmaydi. Qo'shni bilan masofa kerak bo'lsa — getComputedStyle(element).marginTop yoki ikki elementning getBoundingClientRect qiymatlarini ayiring.

10. Mashqlar

1-mashq (oson): O'lchamlarni oldindan ayting

Kodni ishga tushirmasdan, konsolda nima chiqishini hisoblang. Keyin Natija tabida tekshiring.

html
<style>
  #bron {
    width: 300px;
    height: 50px;
    padding: 15px 20px;
    border: 5px solid #1f6f4a;
    margin: 10px;
  }
</style>
<div id="bron">Stol band qilish</div>
<script>
  const bron = document.getElementById("bron");
  console.log(bron.offsetWidth, bron.offsetHeight);
  console.log(bron.clientWidth, bron.clientHeight);
</script>
Yechim

Konsolda:

text
350 90
340 80
  • offsetWidth = 300 + 20 × 2 + 5 × 2 = 350; offsetHeight = 50 + 15 × 2 + 5 × 2 = 90.
  • clientWidth = 300 + 40 = 340; clientHeight = 50 + 30 = 80.
  • margin: 10px hech qayerda yo'q.

2-mashq (o'rta): Kontent sig'dimi?

sigdimi(element) funksiyasini yozing: element ichidagi kontent to'liq ko'rinsa true, aylantirish kerak bo'lsa false qaytarsin. Ikki izoh blokida tekshiring: Qisqa: true, Uzun: false.

Ishora: scrollHeight va clientHeight ni solishtiring («scroll* — ko'rinmay turgan kontent» bo'limi).

html
<style>
  .izoh { height: 60px; overflow: auto; margin: 0 0 8px; }
  .izoh p { height: 40px; margin: 0; }
</style>
<div class="izoh" id="qisqa"><p>Juda mazali!</p></div>
<div class="izoh" id="uzun">
  <p>Osh zo'r edi.</p>
  <p>Choy issiq keldi.</p>
  <p>Yana kelamiz.</p>
</div>
<script>
  // shu yerga yozing
</script>
Yechim
html
<style>
  .izoh { height: 60px; overflow: auto; margin: 0 0 8px; }
  .izoh p { height: 40px; margin: 0; }
</style>
<div class="izoh" id="qisqa"><p>Juda mazali!</p></div>
<div class="izoh" id="uzun">
  <p>Osh zo'r edi.</p>
  <p>Choy issiq keldi.</p>
  <p>Yana kelamiz.</p>
</div>
<script>
  function sigdimi(element) {
    return element.scrollHeight <= element.clientHeight;
  }
  const qisqa = document.getElementById("qisqa");
  const uzun = document.getElementById("uzun");
  console.log(`Qisqa: ${sigdimi(qisqa)}`);
  console.log(`Uzun: ${sigdimi(uzun)}`);
</script>

Konsolda:

text
Qisqa: true
Uzun: false

Uzun izohda kontent 3 × 40 = 120, deraza esa 60. Bu funksiya real saytlarda "Batafsil o'qish" tugmasini faqat kerak bo'lganda ko'rsatish uchun ishlatiladi: matn sig'sa, tugma keraksiz.

3-mashq (qiyin): Ishora ekrandan chiqmasin

«Amaliy holat: tugma ostiga ishora» bo'limidagi kodni o'zgartiring: agar ishora tugma ostiga sig'masa (pastda joy yetmasa), u tugmaning ustida chiqsin. Sinash uchun tugma oynaning pastki chetiga yaqin turibdi.

Ishora: oyna balandligi — document.documentElement.clientHeight. Ishoraning balandligi — ishora.offsetHeight, lekin uni faqat ko'rsatilgandan keyin o'lchash mumkin («Yashirin elementni o'lchash» xatosi). Ustida chiqish uchun top = tugmaning top i − ishora balandligi − 8.

html
<style>
  body { margin: 0; min-height: 200px; }
  #tugma {
    position: fixed;
    bottom: 10px;
    left: 50px;
    width: 120px;
    height: 40px;
  }
  #ishora {
    position: fixed;
    margin: 0;
    padding: 4px 8px;
    background: #1f2a24;
    color: #fff;
  }
</style>
<p>Sahifa pastida — bron tugmasi.</p>
<button id="tugma" type="button">Bron qilish</button>
<p id="ishora" hidden>Bron 10:00 dan 22:00 gacha</p>
<script>
  const tugma = document.getElementById("tugma");
  const ishora = document.getElementById("ishora");
  // shu yerga ishoraniKorsat funksiyasini yozing
  // va uni tugma bosilganda chaqiring
</script>
Yechim
html
<style>
  body { margin: 0; min-height: 200px; }
  #tugma {
    position: fixed;
    bottom: 10px;
    left: 50px;
    width: 120px;
    height: 40px;
  }
  #ishora {
    position: fixed;
    margin: 0;
    padding: 4px 8px;
    background: #1f2a24;
    color: #fff;
  }
</style>
<p>Sahifa pastida — bron tugmasi.</p>
<button id="tugma" type="button">Bron qilish</button>
<p id="ishora" hidden>Bron 10:00 dan 22:00 gacha</p>
<script>
  const tugma = document.getElementById("tugma");
  const ishora = document.getElementById("ishora");

  function ishoraniKorsat() {
    ishora.hidden = false;
    const joy = tugma.getBoundingClientRect();
    const balandlik = ishora.offsetHeight;
    const oyna = document.documentElement.clientHeight;

    let tepa = joy.bottom + 8;
    if (tepa + balandlik > oyna) {
      tepa = joy.top - balandlik - 8;
    }
    ishora.style.top = `${tepa}px`;
    ishora.style.left = `${joy.left}px`;
    console.log(tepa < joy.top ? "Ishora ustida" : "Ishora ostida");
  }

  tugma.addEventListener("click", ishoraniKorsat);
</script>

Tugmani bosing: ishora tugmaning ustida chiqadi, konsolda Ishora ustida. "Tahrirlab ko'rish" bilan #tugma dagi bottom: 10px ni top: 10px ga almashtiring — endi pastda joy yetarli va Ishora ostida chiqadi.

Tartibga e'tibor bering: avval hidden = false, keyin o'lchash. Hammasi bitta funksiyada — foydalanuvchi ishoraning "noto'g'ri joydagi" oraliq holatini ko'rmaydi. Real kutubxonalar (masalan, Floating UI) aynan shu hisobni chap-o'ng chetlar uchun ham qiladi.

4-mashq: Portfolio qadami — "Yuqoriga" faqat kerak bo'lganda

Portfolio bosh sahifasi footer'ida "Yuqoriga" havolasi bor (Skroll xulqi darsidan): <p><a href="#asosiy">Yuqoriga</a></p>. Katta ekranda sahifa butunlay sig'ib qolsa, bu havola ma'nosiz — aylantiradigan joyning o'zi yo'q.

asosiy.js ga yuqoriganiTekshir funksiyasini qo'shing:

  1. Footer'dagi a[href="#asosiy"] ni toping. Yo'q bo'lsa (boshqa sahifalar) — tugasin.
  2. Sahifaning to'liq balandligi oyna balandligidan katta emasmi — tekshiring.
  3. Sahifa sig'sa, havolaning otasi (p) ni yashiring (hidden), sig'masa — ko'rsating.
  4. Funksiyani load hodisasida chaqiring: rasmlar va shriftlar yuklangach sahifa balandligi aniq bo'ladi (Brauzer muhiti darsidagi qolip).
Yechim

asosiy.js ga:

js
function yuqoriganiTekshir() {
  const havola = document.querySelector(
    'footer a[href="#asosiy"]',
  );
  if (havola === null) {
    return;
  }
  const sahifa = document.documentElement;
  const sigadi = sahifa.scrollHeight <= sahifa.clientHeight;
  havola.parentElement.hidden = sigadi;
}

window.addEventListener("load", yuqoriganiTekshir);

Tekshirish: DevTools'da qurilma rejimini yoqing (Ctrl + Shift + M) va balandligi katta ekranni tanlang yoki oynani DevTools bilan pastga cho'zing. Sahifani yangilang — sahifa sig'sa, "Yuqoriga" yo'qoladi. Telefon o'lchamida (360 × 740) esa qaytadi. Elements panelida p ga hidden atributi qo'shilganini ko'rasiz.

Uch nozik joy:

  • Nega parentElement? Yolg'iz havolani yashirsak, bo'sh p qoladi va o'z margin i bilan joy egallaydi. Butun qatorni yashiramiz (DOM bo'ylab harakatlanish).
  • Nega clientHeight, innerHeight emas? Ikkalasi ham ishlaydi. clientHeight gorizontal scrollbar'ni chiqarib tashlaydi — "kontent uchun haqiqiy joy" bilan solishtirish aniqroq.
  • Oyna o'lchami o'zgarsa-chi? Hozircha tekshiruv faqat yuklanganda bo'ladi. Oynani kattalashtirsangiz, sahifani yangilaguncha natija eski qoladi. Oyna o'zgarishiga javob berishni Fokus, scroll va resize hodisalari darsida qo'shasiz.

Commit: JS: qisqa sahifada Yuqoriga havolasini yashir.

11. Real ishda

  • Tooltip, dropdown, kontekst menyu — hammasi tugma yoki matn atrofida chiqadi va getBoundingClientRect bilan joylashadi. Floating UI kabi kutubxonalar shu hisobni ekran chetlari, aylantirish va o'lcham o'zgarishini hisobga olib qiladi. Yangi loyihalarda esa CSS Anchor positioning ga o'tish boshlangan.
  • Cheksiz lenta (Instagram, Telegram kanal): "oxiriga yaqinlashdikmi?" — scrollTop + clientHeight va scrollHeight solishtiruvi. Zamonaviy kodda bu ish IntersectionObserver ga topshiriladi.
  • Chat oynasi — yangi xabar kelganda pastga aylantirish (scrollTop = scrollHeight), lekin foydalanuvchi o'zi tepaga chiqqan bo'lsa — aylantirmaslik. Bu "oxiriga yetdimi?" formulasi bilan aniqlanadi.
  • Formalar — xato maydonga scrollIntoView + focus (Constraint Validation API).
  • Intervyuda: "offsetWidth, clientWidth va scrollWidth farqi?", "getBoundingClientRect nimaga nisbatan?", "Nega o'lcham o'qish qimmat?" — o'rta darajadagi tez-tez beriladigan savollar.

Xulosa

  • offset* — chegara bilan, client* — chegarasiz ichki qism, scroll* — ko'rinmaydigan kontent bilan; margin hech qaysida yo'q.
  • getBoundingClientRect() — viewport'ga nisbatan joy va ko'rinadigan o'lcham; aylantirsa o'zgaradi, transform ni hisobga oladi.
  • scrollTop ni o'qish va yozish mumkin; eng katta qiymat — scrollHeight - clientHeight.
  • scrollTo, scrollBy, scrollIntoView — aylantirish; behavior ni CSS'ga qoldirsangiz, harakat sozlamasi hurmat qilinadi; scrollIntoView da block: "nearest" sahifani bekorga "sakratmaydi".
  • Oyna: innerWidth (scrollbar bilan), documentElement.clientWidth (scrollbar'siz), scrollY.
  • Har o'lcham o'qish — majburiy layout: o'qishlarni guruhlang.

Keyingi dars: Holatdan UI chizish: state → render — ma'lumotni bitta joyda saqlab, butun ro'yxatni undan qayta chizish: freymvorklar g'oyasining ildizi.

Manbalar

  • MDN: "Element: getBoundingClientRect()", "Determining the dimensions of elements", "HTMLElement: offsetParent" — developer.mozilla.org
  • MDN: "Element: scrollIntoView()", "Element: scrollTo()", "Window: innerWidth" — developer.mozilla.org
  • CSSOM View Module — drafts.csswg.org/cssom-view
  • web-features (Baseline): scroll-elements, scroll-into-view, scroll-into-view-container, scrollend — github.com/web-platform-dx/web-features
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Element o'lchamlari va scroll: getBoundingClientRect, offsetWidth, scrollTop va scrollIntoView — IlmHamroh