IlmHamroh
JavaScript Full-stack/6-qism. CSS: harakat, zamonaviy CSS va dizayn52/57-dars23 daqiqa
Mundarija (29)

CSS modal oyna: dialog, skroll bloklash va mobil "sheet"

Qisqacha: Modal oyna — <dialog>, uni tugma command="show-modal" bilan JavaScript'siz ochadi. Oyna ochiqligida orqa sahifa surilmasligi uchun: html:has(dialog:modal) { overflow: hidden; }, skrollbar yo'qolganda sahifa sakramasligi uchun — html { scrollbar-gutter: stable; }, oyna ichidagi skroll sahifaga "o'tib ketmasligi" uchun — overscroll-behavior: contain. Uzun kontentda sarlavha va tugmalar joyida qoladi, faqat o'rtadagi qism suriladi. Telefonda oyna ekran pastiga yopishgan "sheet" bo'ladi.

Bu darsda

  • dialog ni invoker commands bilan ochib-yopasiz va unga to'liq komponent tuzilishini berasiz: sarlavha, yopish tugmasi, kontent, harakatlar.
  • Orqa sahifa skrollini ikki "teshik"dan bloklaysiz va skrollbar sakrashini yo'q qilasiz.
  • Uzun kontentli oynada faqat o'rtadagi qismni surilishini ta'minlaysiz.
  • Telefonda oynani pastdan chiqadigan "sheet" ga aylantirasiz va harakatni kamaytirish rejimini hurmat qilasiz.
  • Modal va popover orasidagi farqni bilib, qaysi birini qachon tanlashni aytasiz.

Oldin bilishingiz kerak: dialog: native modal oyna, Invoker commands, Top layer, Overflow va skroll konteynerlar.

1. Nega bu kerak?

O'tgan darsda forma komponentlarini yasadik. Endi ularni oynaga joylaymiz.

«Bahor» menyu sahifasi uzun: o'nlab taom. Mehmon pastda lag'monni ko'rdi va "Tezkor bron" tugmasini bosdi. Oyna ochildi, u ism va telefonni yozmoqchi — lekin barmog'i oyna yonidagi qorong'i joyga tegdi va sahifa suriladi. Oyna yopilgach, mehmon menyuning boshqa joyida — lag'monni yana qidiradi. Telefonda yana bir muammo: oyna ichidagi formani pastga surganda, oxiriga yetgach, orqadagi sahifa surila boshlaydi.

Top layer darsida oynani bezagan edik: parda, soya, ochilish animatsiyasi. Bu dars — to'liq komponent. Unda oldingi darslarning va'dalari bajariladi:

  1. Overflow darsida: "modal oyna ochilganda orqadagi sahifani to'xtatish" va overscroll-behavior ning modal oynadagi o'rni.
  2. Top layer darsida: to'liq modal komponenti.
  3. Vizual ierarxiya darsida: "Bronni bekor qilish" uchun tasdiqlash oynasi.

2. Oyna qanday ochiladi

2.1 JavaScript'siz: invoker commands

Invoker commands darsidan eslaymiz: tugmaga commandfor="oyna-id" va command="show-modal" yoziladi — brauzer dialog ni modal qilib ochadi. JavaScript'da xuddi shu ish dialog.showModal() metodi bilan qilinadi (09-qismda ko'rasiz). Yopish uchun ikki yo'l:

  • command="close" li tugma — oynani yopadi;
  • <form method="dialog"> ichidagi tugma — formani yubormaydi, oynani yopadi (dialog).

Invoker commands — yangi Baseline: 2025-yil dekabridan hamma asosiy brauzerda (Chrome 135, Firefox 144, Safari 26.2). Eski brauzerda tugma hech narsa qilmaydi. Muhim sahifada zaxira kerak — bir qator JavaScript (09-qismda). «Bahor»da oyna qo'shimcha qulaylik: tugma ishlamasa, "Tezkor bron" o'rniga oddiy havola bron/ sahifasiga olib boradi.

2.2 Modal nima beradi

command="show-modal" bilan ochilgan oyna — modal: u top layer'da (Top layer) va orqadagi sahifa inert — bosib ham, Tab bilan ham unga yetib bo'lmaydi. Esc oynani yopadi. Brauzer ochilganda fokusni oyna ichiga olib kiradi, yopilganda — ochgan tugmaga qaytaradi.

Lekin bitta narsani brauzer bermaydi: orqa sahifa skrolli bloklanmaydi. Tekshiramiz.

3. Orqa sahifa skrolli

3.1 Muammo

html
<style>
  body {
    margin: 0;
    font-family: system-ui, sans-serif;
    background: #fffaf0;
  }
  .uzun {
    block-size: 150rem;
    background: repeating-linear-gradient(#fffaf0 0 4rem,
      #e8f1ec 4rem 8rem);
  }
  dialog {
    max-block-size: 12rem;
    border: 0;
    border-radius: 1rem;
  }
  dialog::backdrop { background: #1f2a2499; }
  .matn { block-size: 30rem; }
</style>
<p>
  <button type="button" commandfor="oyna" command="show-modal">
    ❌ Oynani ochish
  </button>
</p>
<div class="uzun"></div>
<dialog id="oyna">
  <button type="button" commandfor="oyna" command="close">
    Yopish
  </button>
  <p class="matn">Oyna ichidagi uzun matn. Uni pastga suring.</p>
</dialog>

Nimaga qarang: oynani oching. Endi sichqonchani oyna yonidagi qorong'i pardaga olib boring va g'ildirakni aylantiring — orqadagi yo'l-yo'l sahifa suriladi. Keyin oyna ichida aylantiring: matn oxirigacha surilgach, g'ildirak orqadagi sahifani sura boshlaydi.

Chrome'da o'lchadik (sahifa 800 × 600): pardada 400 piksel aylantirish sahifani 400 pikselga surdi. Oyna ichida esa oyna kontenti oxiriga yetgach, qolgan aylantirish sahifaga o'tdi — sahifa 1 200 pikselga surildi. Ikki "teshik":

  1. Parda ustidagi skroll — parda sahifaning bir qismi emas, lekin skroll hodisasi hujjatga boradi.
  2. Skroll zanjiri (scroll chaining) — ichki skroll konteyneri oxiriga yetgach, surish uning otasiga "uzatiladi" (Overflow).

3.2 Uch qator

Ikki teshik va ularni yopadigan qatorlar — bir rasmda:

flowchart TD
  A[Mehmon g'ildirakni aylantiradi] --> B{Qayerda?}
  B -->|Pardada| C[Hujjat suriladi]
  B -->|Oyna ichida| D[Oyna kontenti suriladi]
  D -->|Oxiriga yetdi| E[Zanjir: sahifa suriladi]
  C -.->|to'xtatadi| F["html:has(dialog:modal)<br>overflow: hidden"]
  E -.->|uzadi| G["overscroll-behavior:<br>contain"]
css
/* 1. Oyna ochiqligida sahifa surilmaydi */
html:has(dialog:modal) {
  overflow: hidden;
}

/* 2. Skrollbar yo'qolganda sahifa sakramasin */
html {
  scrollbar-gutter: stable;
}

/* 3. Oyna ichidagi skroll sahifaga o'tmasin */
dialog {
  overscroll-behavior: contain;
}

1. html:has(dialog:modal) — "ichida modal oyna bor hujjat" (Mantiqiy pseudo-klasslar). Oyna ochilishi bilan qoida ishlaydi, yopilishi bilan o'zi bekor bo'ladi — JavaScript kerak emas. Nega dialog[open] emas, :modal? [open] modal bo'lmagan oynada ham (masalan, sahifa ichidagi oddiy dialog open) mos keladi — o'shanda sahifani bloklash xato. :modal faqat show-modal bilan ochilganiga.

Ko'p maqolalarda body:has(dialog[open]) { overflow: hidden; } ni uchratasiz. body ga yozsa ham ishlaydi — html da overflow bo'lmasa, body niki butun oynaga o'tadi (Overflow). Chrome'da ikkalasini sinadik — natija bir xil. Biz html ni tanladik: skroll aynan unda.

2. scrollbar-gutter: stable — Overflow darsida ko'rgan xususiyat: skrollbar uchun joy doim ajratilgan. Busiz nima bo'ladi? Kompyuterda skrollbar ~15 piksel joy oladi. overflow: hidden uni olib tashlaydi — sahifa 15 pikselga kengayadi va markazdagi kontent o'ngga "sakraydi". Chrome'da o'lchadik: markazlangan blok 192.5 dan 200 pikselga siljidi. scrollbar-gutter: stable bilan — 192.5 da qoldi. Telefonda skrollbar kontent ustida chiziladi, u yerda bu muammo yo'q.

scrollbar-gutter — yangi Baseline: 2024-yil dekabridan. Eski brauzerda sakrash qoladi — noqulay, lekin hech narsa buzilmaydi. Narxi ham bor: qisqa, skrollsiz sahifada ham o'ngda bo'sh tasma turadi.

3. overscroll-behavior: contain — skroll zanjirini uzadi (Overflow). Savol tug'iladi: birinchi qator sahifani allaqachon to'xtatgan-ku, bu nega kerak? Telefonda oxiriga yetgan ro'yxat "cho'zilish" effektini va sahifani yangilashni (pull-to-refresh — ekranni pastga tortib yangilash) ham ishga tushiradi. contain ularni ham to'xtatadi. Chrome'da alohida tekshirdik: faqat contain bilan (qulfsiz) oyna ichidagi skroll sahifaga o'tmadi. Lekin parda ustidagi skroll sahifani surdi — shuning uchun ikkalasi ham kerak. Bu xususiyat rasman Baseline emas: Baseline ma'lumotiga ko'ra to'liq qo'llash Chrome, Edge va Firefox'da; Safari ro'yxatda yo'q (sababini Overflow darsida ko'rgan edik).

Tekshirib ko'ring: Sahifada ikkita oyna bor: biri modal ("Tezkor bron"), ikkinchisi sahifa ichidagi <dialog open> ("Bugungi aksiya", yopish tugmasi bilan). Stajyor html:has(dialog[open]) { overflow: hidden; } yozdi. Mehmon nimani sezadi?

Javob

Sahifa umuman surilmaydi — "Bugungi aksiya" oynasi sahifa ochilgandan beri open, demak shart doim bajariladi. Mehmon aksiyani yopmaguncha menyuni o'qiy olmaydi va sababini tushunmaydi. :modal bilan faqat haqiqiy modal oyna sahifani bloklaydi.

4. To'liq komponent

4.1 Tuzilish

Professional modal oyna to'rt qismdan iborat:

Qism Nima Qoida
Sarlavha Oyna nomi (h2) aria-labelledby bilan oynaga ulanadi
Yopish tugmasi "" belgisi Yuqori o'ng burchakda — mehmon uni shu yerda qidiradi
Kontent Forma, matn Uzun bo'lsa, faqat shu qism suriladi
Harakatlar "Bekor qilish", "Yuborish" Pastda, asosiy harakat oxirida (Tugmalar tizimi)

Uzun formada sarlavha va tugmalar doim ko'rinib tursin: mehmon formaning o'rtasida "Yuborish" ni qidirib pastga surmasin.

4.2 «Bahor»: tezkor bron oynasi

html
<style>
  :root {
    --rang-fon: #fffaf0;
    --rang-matn: #1f2a24;
    --rang-asosiy: #2f6b3a;
    --rang-asosiy-toq: #1a5d1a;
    --rang-ikkinchi: #5b6660;
    --rang-chegara: #c9d6cf;
  }
  html { scrollbar-gutter: stable; }
  html:has(dialog:modal) { overflow: hidden; }
  body {
    margin: 0;
    padding: 1rem;
    font-family: system-ui, sans-serif;
    background: var(--rang-fon);
    color: var(--rang-matn);
  }
  button {
    min-block-size: 2.75rem;
    padding-inline: 1.25em;
    border: 2px solid var(--rang-asosiy);
    border-radius: 0.5rem;
    background: var(--rang-asosiy);
    color: var(--rang-fon);
    font: inherit;
    font-weight: 600;
  }
  button[command="close"] {
    background: transparent;
    color: var(--rang-asosiy);
  }
  :focus-visible {
    outline: 3px solid var(--rang-asosiy-toq);
    outline-offset: 2px;
    box-shadow: 0 0 0 2px var(--rang-fon);
  }
  dialog :focus-visible { box-shadow: 0 0 0 2px white; }
  /* Oyna */
  dialog {
    --_kirish: 0 1.5rem;
    box-sizing: border-box;
    inline-size: min(100% - 2rem, 30rem);
    max-block-size: calc(100% - 2rem);
    padding: 0;
    border: 0;
    border-radius: 1rem;
    background: white;
    color: var(--rang-matn);
    box-shadow: 0 12px 32px #1f2a2440;
    overscroll-behavior: contain;
  }
  dialog[open] {
    display: flex;
    flex-direction: column;
  }
  dialog::backdrop {
    background: #1f2a2499;
    backdrop-filter: blur(3px);
  }
  dialog form {
    display: flex;
    flex-direction: column;
    min-block-size: 0;
  }
  dialog header, dialog footer {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
  }
  dialog header {
    justify-content: space-between;
    border-block-end: 1px solid var(--rang-chegara);
  }
  dialog h2 { margin: 0; font-size: 1.25rem; }
  dialog header button {
    inline-size: 2.75rem;
    padding: 0;
    border-color: transparent;
  }
  dialog form > div {
    display: grid;
    gap: 1rem;
    padding: 1rem;
    overflow-y: auto;
    overscroll-behavior: contain;
  }
  dialog label { display: grid; gap: 0.25rem; font-weight: 600; }
  dialog input {
    min-block-size: 2.75rem;
    padding-inline: 0.75em;
    border: 2px solid var(--rang-ikkinchi);
    border-radius: 0.5rem;
    font: inherit;
  }
  dialog footer {
    justify-content: end;
    border-block-start: 1px solid var(--rang-chegara);
  }
  /* Telefon: pastdan chiqadigan sheet */
  @media (width < 40rem) {
    dialog {
      --_kirish: 0 100%;
      inline-size: 100%;
      max-inline-size: 100%;
      margin-block-end: 0;
      border-end-start-radius: 0;
      border-end-end-radius: 0;
    }
  }
  /* Ochilish va yopilish */
  dialog, dialog::backdrop {
    transition:
      opacity 0.25s ease-out,
      display 0.25s allow-discrete,
      overlay 0.25s allow-discrete;
    opacity: 0;
  }
  dialog[open], dialog[open]::backdrop { opacity: 1; }
  @starting-style {
    dialog[open], dialog[open]::backdrop { opacity: 0; }
  }
  @media (prefers-reduced-motion: no-preference) {
    dialog {
      translate: var(--_kirish);
      transition:
        opacity 0.25s ease-out,
        translate 0.25s ease-out,
        display 0.25s allow-discrete,
        overlay 0.25s allow-discrete;
    }
    dialog[open] { translate: 0 0; }
    @starting-style {
      dialog[open] { translate: var(--_kirish); }
    }
  }
</style>
<h1>Menyu</h1>
<p>Osh — 35 000, lag'mon — 28 000, manti — 30 000 so'm.</p>
<button type="button" commandfor="bron-oyna" command="show-modal">
  Tezkor bron
</button>
<dialog id="bron-oyna" aria-labelledby="bron-sarlavha"
  closedby="any">
  <form action="https://example.com/bron" method="post">
    <header role="none">
      <h2 id="bron-sarlavha">Tezkor bron</h2>
      <button type="button" commandfor="bron-oyna" command="close"
        aria-label="Yopish">✕</button>
    </header>
    <div>
      <label>Ismingiz
        <input name="ism" autocomplete="name" required autofocus>
      </label>
      <label>Telefon raqamingiz
        <input name="telefon" type="tel" autocomplete="tel"
          required>
      </label>
      <label>Sana
        <input name="sana" type="date" required>
      </label>
      <label>Mehmonlar soni
        <input name="odam" type="number" min="1" max="20" value="2">
      </label>
      <label>Qo'shimcha istaklar (ixtiyoriy)
        <input name="izoh">
      </label>
    </div>
    <footer role="none">
      <button type="button" commandfor="bron-oyna" command="close">
        Bekor qilish
      </button>
      <button type="submit">Yuborish</button>
    </footer>
  </form>
</dialog>

Nimaga qarang: "Tezkor bron" ni bosing. Kompyuterda (768, 1280) oq oyna ekran o'rtasida, 24 piksel pastdan ko'tarilib paydo bo'ladi; orqa sahifa qoraygan va xira. Yuqorida sarlavha va "", pastda ikki tugma — ular joyida turadi. O'rtadagi forma natija oynasiga sig'maydi: uni suring — faqat maydonlar suriladi. Kursor birinchi maydonda ("Ismingiz") yonib turibdi. Telefonda (360) oyna ekranning pastiga yopishgan, butun enda, faqat tepa burchaklari yumaloq — u pastdan suzib chiqadi.

Uch yo'l bilan yoping: "", "Bekor qilish", Esc — va pardaga bosish. Telefon enida o'ngda ingichka bo'sh tasma ko'rinishi mumkin — bu kompyuter skrollbari uchun ajratilgan joy (scrollbar-gutter, pastda). Haqiqiy telefonda skrollbar kontent ustida chiziladi, tasma bo'lmaydi.

Chrome'da tekshirdik (natija oynasi 400 piksel balandlikda):

  • oyna balandligi 368 piksel (100% - 2rem); kontent qismi 230 piksel, ichidagi forma esa 469 piksel — faqat shu qism surildi, sarlavha va tugmalar joyida qoldi;
  • ochilganda fokus autofocus li "Ismingiz" maydonida;
  • pardaga bosish oynani yopdi (closedby="any"), fokus "Tezkor bron" tugmasiga qaytdi;
  • telefon enida oyna pastki cheti natija oynasining pastki chetiga to'liq tegdi.

4.3 Kodni o'qiymiz

Tuzilish va skroll:

  • dialog[open] { display: flex; flex-direction: column; } — faqat ochiq holatda (Top layer darsidagi display tuzog'i). Yopiq oynaga display bersangiz, u sahifada ko'rinib qoladi.
  • max-block-size: calc(100% - 2rem) — top layer'da "100%" — ekran balandligi. Oyna ekrandan hech qachon chiqmaydi. dvh ham ishlatsa bo'ladi, lekin % bu yerda yetarli va telefon manzil qatorining o'zgarishiga ham moslashadi.
  • form va ichki div da min-block-size: 0, overflow-y: auto — flex elementi kontentidan kichrayishi uchun min-block-size: 0 shart (min-width: 0). Busiz forma oynani cho'zib yuborardi va oyna o'zi surilardi — sarlavha va tugmalar bilan birga.
  • overscroll-behavior: contain ikki joyda: oynaning o'zida va surilayotgan qismda — zanjir qayerdan boshlanmasin, sahifaga yetmaydi.
  • header role="none" va footer role="none" — dialog HTML'da alohida bo'lim hisoblanmaydi. Shuning uchun ichidagi header sahifa header'i kabi ikkinchi banner landmark'iga, footer esa ikkinchi contentinfo ga aylanadi — Malika ekran o'quvchisida ikkita "banner" eshitadi (Sahifa landmarklari). role="none" landmark rolini o'chiradi; element ham, dialog :is(header, footer) selektori ham qoladi.

Fokus va yopish:

  • autofocus — oyna ochilganda fokus qayerga tushishini aytadi. Busiz brauzer birinchi fokuslanadigan elementni tanlaydi — Chrome'da tekshirdik: bu "" tugmasi bo'ldi. Forma oynasida mehmon darhol yozishni boshlasin; tasdiqlash oynasida esa — xavfsiz tugma (1-mashq).
  • "" — aria-label="Yopish" bilan (Tugmalar tizimi dagi faqat ikonkali tugma), 44 × 44 piksel.
  • closedby="any" — pardaga bosganda yopish. U Baseline emas: Baseline ma'lumotiga ko'ra Chrome, Edge va Firefox'da bor, Safari'da yo'q. Safari atributni e'tiborsiz qoldiradi — "", "Bekor qilish" va Esc baribir ishlaydi. Forma oynasida ehtiyot bo'ling: mehmon yarim yozilgan formani tasodifan pardaga bosib yo'qotishi mumkin. Uzun forma uchun closedby="closerequest" (faqat Esc va tugmalar) xavfsizroq.

Telefon va harakat:

  • Sheet — margin-block-end: 0: brauzer oynani margin: auto bilan markazlaydi (Top layer), pastki margin'ni nolga tushirsak, oyna pastga yopishadi. Pastki burchaklar to'g'ri — ekran cheti bilan qo'shiladi. Bosh barmoq yetadigan joy (UI/UX asoslari).
  • --_kirish — bitta ichki o'zgaruvchi harakat yo'nalishini beradi: kompyuterda 0 1.5rem (biroz pastdan), telefonda 0 100% (to'liq pastdan). Animatsiya qoidasi bitta.
  • Harakat kamaytirilganda faqat opacity qoladi: translate no-preference ichida (harakat etikasi).
  • @starting-style — yangi Baseline (2024-yil avgust); overlay — faqat Chrome va Edge'da (Top layer). Ishlamagan joyda oyna animatsiyasiz ochiladi. Pardadagi backdrop-filter ham yangi Baseline (2024-yil sentabr) — «Bahor» faylida u @supports ichida: xiralash bo'lmasa, rangli parda qoladi.

Maslahat: iPhone'da sheet'ning pastida "uy chizig'i" bor. Tugmalar unga yopishmasligi uchun: dialog footer { padding-block-end: max(0.75rem, env(safe-area-inset-bottom)); } (Mobil qurilma tuzoqlari).

Tekshirib ko'ring: Stajyor dialog form > div dan min-block-size: 0 ni o'chirdi (form dagisini ham). Uzun formali oynada nima o'zgaradi?

Javob

Endi butun oyna suriladi: sarlavha va "Yuborish" tugmasi forma bilan birga ko'rinmay ketadi. Flex elementi sukut bo'yicha kontentidan kichraymaydi — forma o'z balandligicha (469 piksel) cho'ziladi. min-block-size: 0 unga oyna balandligiga sig'ib, ortiqchasini ichida surishga ruxsat beradi.

5. Modal yoki popover?

Ikkalasi ham top layer'da, ikkalasi ham JavaScript'siz ochiladi. Farq — orqa sahifa bilan munosabatda:

Modal dialog Popover
Orqa sahifa Inert — bosib ham, Tab bilan ham bo'lmaydi Ishlayveradi
Tashqariga bosish Sukut bo'yicha yopmaydi (closedby="any" bilan — yopadi) Yopadi (auto turida)
Skroll Bloklash kerak (shu dars) Bloklanmaydi
Qachon Mehmon javob berishi kerak: forma, tasdiqlash Yordamchi ma'lumot: menyu, maslahat, "Ish vaqti"

Qoida: agar oyna ochiqligida mehmon sahifaning boshqa joyi bilan ishlasa ham mantiqiy bo'lsa — popover. Agar avval oynadagi savolga javob berish kerak bo'lsa — modal. Navbar darsidagi mobil menyu — popover: mehmon uni yopmasdan ham orqaga qaytishi mumkin.

Tekshirib ko'ring: "Ish vaqti" haqidagi qisqa ma'lumot oynasi va "Buyurtmani tasdiqlaysizmi?" oynasi. Qaysi biri modal, qaysi biri popover?

Javob

"Ish vaqti" — popover: mehmon uni o'qib turib ham sahifa bilan ishlashi mumkin, hech narsaga javob berishi shart emas. "Buyurtmani tasdiqlaysizmi?" — modal: mehmon avval "ha" yoki "yo'q" deyishi kerak, orqa sahifa shu paytda to'xtab turadi.

6. «Bahor»ning komponentlar.css iga

css
/* === Modal oyna (Modal oyna va skroll bloklash darsi) === */
dialog {
  --_kirish: 0 1.5rem;
  box-sizing: border-box;
  inline-size: min(100% - 2rem, 30rem);
  max-block-size: calc(100% - 2rem);
  padding: 0;
  border: 0;
  border-radius: var(--burchak-m);
  background: var(--rang-yuza);
  color: var(--rang-matn);
  box-shadow: var(--soya-3);
  overscroll-behavior: contain;
}

dialog[open] {
  display: flex;
  flex-direction: column;
}

dialog::backdrop {
  background: color-mix(in oklch, var(--rang-matn) 60%, transparent);
}

@supports (backdrop-filter: blur(1px)) {
  dialog::backdrop {
    backdrop-filter: blur(3px);
  }
}

dialog form {
  display: flex;
  flex-direction: column;
  min-block-size: 0;
}

dialog :is(header, footer) {
  display: flex;
  align-items: center;
  gap: var(--masofa-xs);
  padding: var(--masofa-s) var(--masofa-m);
}

dialog header {
  justify-content: space-between;
  border-block-end: 1px solid
    color-mix(in oklch, var(--rang-asosiy) 25%, var(--rang-yuza));
}

dialog footer {
  justify-content: end;
  padding-block-end:
    max(var(--masofa-s), env(safe-area-inset-bottom));
  border-block-start: 1px solid
    color-mix(in oklch, var(--rang-asosiy) 25%, var(--rang-yuza));
}

dialog form > div {
  display: grid;
  gap: var(--masofa-m);
  padding: var(--masofa-m);
  overflow-y: auto;
  overscroll-behavior: contain;
}

@media (width < 40rem) {
  dialog {
    --_kirish: 0 100%;
    inline-size: 100%;
    max-inline-size: 100%;
    margin-block-end: 0;
    border-end-start-radius: 0;
    border-end-end-radius: 0;
  }
}

/* Halqaning ichki chizig'i — oyna fonida */
dialog :focus-visible {
  box-shadow: 0 0 0 2px var(--rang-yuza);
}

/* Ochilish/yopilish: shaffoflik doim, siljish — ruxsat bo'lsa */
dialog,
dialog::backdrop {
  opacity: 0;
  transition:
    opacity 0.25s ease-out,
    display 0.25s allow-discrete,
    overlay 0.25s allow-discrete;
}

dialog[open],
dialog[open]::backdrop {
  opacity: 1;
}

@starting-style {
  dialog[open],
  dialog[open]::backdrop {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: no-preference) {
  dialog {
    translate: var(--_kirish);
    transition:
      opacity 0.25s ease-out,
      translate 0.25s ease-out,
      display 0.25s allow-discrete,
      overlay 0.25s allow-discrete;
  }

  dialog[open] {
    translate: 0 0;
  }

  @starting-style {
    dialog[open] {
      translate: var(--_kirish);
    }
  }
}

asos.css ga — sahifa darajasidagi ikki qoida:

css
html {
  scrollbar-gutter: stable;
}

html:has(dialog:modal) {
  overflow: hidden;
}
  • Tugmalar — Tugmalar tizimi dan: "" — class="tugma shaffof belgi" (ichida .ikonka bilan i-yopish belgisi, SVG va ikonka tizimi), "Bekor qilish" — tugma shaffof, "Yuborish" — tugma. Oyna ichidagi header va footer — role="none" bilan (ikkinchi landmark bo'lmasin). Maydonlar — Forma komponentlari dan. Fokus halqasi — asos.css dagi umumiy ikki rangli qoida (CSS'da accessibility).
  • Top layer darsidagi «Bahor» oynasi ("Bronni bekor qilasizmi?") shu qoidalarni oladi — uning eski alohida stillari endi keraksiz.
  • Shaffoflik qoidalarida dialog, dialog::backdrop — oddiy vergulli ro'yxat, :is() emas. Sababi — «Ko'p uchraydigan xatolar» dagi birinchi tuzoq.
  • dialog :focus-visible — umumiy halqaning ichki chizig'i --rang-fon (qaymoq), oyna foni esa --rang-yuza (oq). Oyna ichida chiziq oyna foniga mos bo'lsin.

7. Ko'p uchraydigan xatolar

7.1 :is() ichida psevdo-element

:is(dialog, dialog::backdrop) { opacity: 0; } — oyna shaffof bo'ladi, parda esa yo'q. :is() ichida psevdo-element yozilmaydi, lekin :is() "kechirimli" ro'yxat: yaroqsiz qismni jimgina tashlaydi, qolgani ishlaydi (Mantiqiy pseudo-klasslar). Chrome'da tekshirdik: dialog qoidani oldi, ::backdrop — yo'q. Xato xabari ham chiqmaydi. Tuzatish: oddiy vergulli ro'yxat — dialog, dialog::backdrop { ... }.

7.2 dialog { display: flex; }

Yopiq oyna sahifada ko'rinib qoladi. Tuzatish: dialog[open].

7.3 Skroll bloklandi, sahifa "sakradi"

overflow: hidden skrollbarni olib tashladi. Tuzatish: html { scrollbar-gutter: stable; }.

7.4 Butun oyna suriladi, tugmalar ko'rinmay qoladi

Oynada overflow: auto, ichki qism esa cho'ziladi. Tuzatish: oyna — flex ustun, ichki qism — min-block-size: 0; overflow-y: auto.

7.5 Sahifa doim bloklangan

html:has(dialog[open]) — sahifadagi oddiy (modal emas) ochiq oyna ham sahifani to'xtatadi. Tuzatish: :modal.

7.6 Oyna ichida tugmalarning fokusi ko'rinmaydi

Oq oyna foni ustida brauzerning ko'k halqasi yoki fon rangidagi ichki chiziq ko'rinmaydi. Tuzatish: halqaning ichki chizig'i oynaning foniga mos bo'lsin — «Bahor»da --rang-yuza: dialog :focus-visible { box-shadow: 0 0 0 2px var(--rang-yuza); }.

8. Mashqlar

Mashqlarni kurs/mashqlar/06/ papkasida bajaring.

1-mashq (oson): "Bronni bekor qilasizmi?"

Vizual ierarxiya darsidagi va'da: bron sahifasida "Bronni bekor qilish" — qizg'ish matnli havola ko'rinishidagi tugma. U tasdiqlash oynasini ochsin: sarlavha, bir gap, ikki tugma — "Yo'q, qolsin" (xavfsiz, fokus shunda) va "Ha, bekor qilish" (xavfli variant). Pardaga bosish oynani yopmasin.

Ishora: xavfli tugma — Tugmalar tizimi dagi .xavfli g'oyasi. Fokus — autofocus. Pardaga bosish — closedby atributi yo'q bo'lsa, modal oyna faqat Esc va tugmalar bilan yopiladi.

Yechim
html
<style>
  body { font-family: system-ui, sans-serif; background: #fffaf0; }
  button {
    min-block-size: 2.75rem;
    padding-inline: 1.25em;
    border: 2px solid transparent;
    border-radius: 0.5rem;
    font: inherit;
    font-weight: 600;
  }
  .bekor-havola {
    background: transparent;
    color: oklch(52% 0.19 27);
    text-decoration: underline;
  }
  dialog {
    inline-size: min(100% - 2rem, 24rem);
    padding: 1.5rem;
    border: 0;
    border-radius: 1rem;
    background: white;
  }
  dialog::backdrop { background: #1f2a2499; }
  dialog h2 { margin-block-start: 0; }
  dialog form {
    display: flex;
    flex-wrap: wrap;
    justify-content: end;
    gap: 0.5rem;
  }
  button[value="yoq"] {
    border-color: #2f6b3a;
    background: transparent;
    color: #2f6b3a;
  }
  button[value="ha"] {
    background: oklch(52% 0.19 27);
    color: #fffaf0;
  }
  :focus-visible {
    outline: 3px solid #1a5d1a;
    outline-offset: 2px;
    box-shadow: 0 0 0 2px white;
  }
</style>
<p>Bron: 4-stol, bugun 19:00.</p>
<button class="bekor-havola" type="button" commandfor="bekor"
  command="show-modal">Bronni bekor qilish</button>
<dialog id="bekor" aria-labelledby="bekor-sarlavha">
  <h2 id="bekor-sarlavha">Bronni bekor qilasizmi?</h2>
  <p>4-stol bugun 19:00 ga band qilingan edi.</p>
  <form method="dialog">
    <button type="submit" value="yoq" autofocus>
      Yo'q, qolsin</button>
    <button type="submit" value="ha">Ha, bekor qilish</button>
  </form>
</dialog>

Oynani oching: fokus "Yo'q, qolsin" da — tasodifiy Enter bronni saqlab qoladi. Pardaga bosing — oyna yopilmaydi: qaytarib bo'lmaydigan amalda mehmon aniq javob bersin. .bekor-havola — namoyish class'i. Halqaning ichki chizig'i oq — oyna foni oq bo'lgani uchun («Oyna ichida tugmalarning fokusi ko'rinmaydi» xatosi). Yuborilgan qiymatni (ha/yoq) o'qib, bronni haqiqatan bekor qilish — server yoki JavaScript ishi.

2-mashq (o'rta): rasm to'liq ekranda

Menyu sahifasida taom rasmini bosganda u to'liq ekranli modal oynada ochilsin: qora parda, rasm ekranga sig'adigan eng katta o'lchamda (nisbati buzilmasin), yuqori o'ng burchakda "". Sahifa orqada surilmasin.

Ishora: oynaning o'zi butun ekran: inline-size: 100%; block-size: 100%; max-inline-size: none; max-block-size: none; margin: 0. Rasm — object-fit: contain (Rasm va media); natijada rasm o'rniga SVG.

Yechim
html
<style>
  html:has(dialog:modal) { overflow: hidden; }
  body { font-family: system-ui, sans-serif; background: #fffaf0; }
  .kichik { inline-size: 10rem; border-radius: 0.5rem; }
  #rasm {
    box-sizing: border-box;
    inline-size: 100%;
    block-size: 100%;
    max-inline-size: none;
    max-block-size: none;
    margin: 0;
    padding: 3.5rem 1rem 1rem;
    border: 0;
    background: #000000e6;
  }
  #rasm[open] { display: grid; place-items: center; }
  #rasm::backdrop { background: transparent; }
  #rasm svg {
    inline-size: 100%;
    block-size: 100%;
  }
  #rasm button {
    position: absolute;
    inset-block-start: 0.5rem;
    inset-inline-end: 0.5rem;
    inline-size: 2.75rem;
    block-size: 2.75rem;
    border: 2px solid #fffaf0;
    border-radius: 50%;
    background: transparent;
    color: #fffaf0;
    font: inherit;
  }
  :focus-visible {
    outline: 3px solid #8fd19e;
    outline-offset: 2px;
  }
</style>
<button type="button" commandfor="rasm" command="show-modal"
  aria-label="Osh rasmini kattalashtirish">
  <svg class="kichik" viewBox="0 0 160 100" aria-hidden="true">
    <rect width="160" height="100" fill="#e9dcc3" />
    <ellipse cx="80" cy="52" rx="44" ry="20" fill="#f2c46d" />
  </svg>
</button>
<dialog id="rasm" aria-label="Osh rasmi">
  <button type="button" commandfor="rasm" command="close"
    aria-label="Yopish">✕</button>
  <svg viewBox="0 0 160 100" role="img" aria-label="Osh">
    <rect width="160" height="100" fill="#e9dcc3" />
    <ellipse cx="80" cy="52" rx="44" ry="20" fill="#f2c46d" />
  </svg>
</dialog>

Kichik rasmni bosing: butun natija oynasi qora, o'rtada rasm — eni yoki bo'yi bo'yicha sig'adigan eng katta o'lchamda, nisbati o'zgarmagan (SVG'ning viewBox i object-fit: contain kabi ishlaydi). "" yuqori o'ng burchakda, qora fonda och halqa bilan. Pardani shaffof qildik — oynaning o'zi butun ekranni qoplaydi. #rasm — id selektori, faqat mashq uchun.

3-mashq (qiyin): o'ngdan chiqadigan filtr paneli

Menyu sahifasida "Filtr" tugmasi o'ngdan chiqadigan modal panel ochsin: "Achchiq", "Vegetarian", "Yangi" checkbox'lari va pastda "Qo'llash" tugmasi. Navbar darsidagi popover panelidan farqi: panel ochiqligida orqa sahifa inert va surilmaydi.

  1. Panel butun balandlikda, eni min(20rem, 85%), o'ng chetda.
  2. Kompyuterda ham, telefonda ham bir xil.
  3. Harakat kamaytirilganda siljish yo'q.

Ishora: margin-inline-end: 0, margin-block: 0, block-size: 100%, max-block-size: none — markazlashni bekor qilish (Top layer). Siljish — --_kirish: 100% 0.

Yechim
html
<style>
  html:has(dialog:modal) { overflow: hidden; }
  body { font-family: system-ui, sans-serif; background: #fffaf0; }
  #filtr {
    box-sizing: border-box;
    inline-size: min(20rem, 85%);
    block-size: 100%;
    max-block-size: none;
    margin-block: 0;
    margin-inline-end: 0;
    padding: 1rem;
    border: 0;
    background: white;
    overscroll-behavior: contain;
  }
  #filtr[open] {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }
  #filtr::backdrop { background: #1f2a2466; }
  #filtr fieldset { border: 0; padding: 0; }
  #filtr form {
    display: flex;
    flex: 1;
    flex-direction: column;
  }
  #filtr button[type="submit"] { margin-block-start: auto; }
  @media (prefers-reduced-motion: no-preference) {
    #filtr {
      translate: 100% 0;
      transition:
        translate 0.25s ease-out,
        display 0.25s allow-discrete,
        overlay 0.25s allow-discrete;
    }
    #filtr[open] { translate: 0 0; }
    @starting-style {
      #filtr[open] { translate: 100% 0; }
    }
  }
  button { min-block-size: 2.75rem; font: inherit; }
</style>
<button type="button" commandfor="filtr" command="show-modal">
  Filtr
</button>
<dialog id="filtr" aria-labelledby="filtr-sarlavha">
  <h2 id="filtr-sarlavha">Filtr</h2>
  <form method="dialog">
    <fieldset>
      <legend>Taomlar</legend>
      <p><input id="f1" type="checkbox"><label for="f1">
        Achchiq</label></p>
      <p><input id="f2" type="checkbox"><label for="f2">
        Vegetarian</label></p>
      <p><input id="f3" type="checkbox"><label for="f3">
        Yangi</label></p>
    </fieldset>
    <button type="submit">Qo'llash</button>
  </form>
</dialog>

"Filtr" ni bosing: oq panel o'ngdan suzib kiradi, qolgan qismi qoraygan parda, "Qo'llash" panelning eng pastida (margin-block-start: auto — Auto margin). Chrome'da tekshirdik: panel ochiqligida orqadagi "Filtr" tugmasiga Tab bilan borib bo'lmaydi — modal orqa sahifani inert qildi. Popover panelida esa Tab panel tugagach sahifaga qaytadi. "Qo'llash" form method="dialog" ichida — oynani yopadi. Filtrni haqiqatan qo'llash (taomlarni yashirish) — JavaScript ishi, 09-qismda. #filtr — mashq uchun id selektori.

4-mashq: Portfolio qadami — "Ma'lumotlaringiz qanday ishlatiladi?"

Portfolio'da Top layer darsidan beri dialog uchun umumiy uslub bor, lekin haqiqiy oyna yo'q. Aloqa sahifasiga birinchi modal oynani qo'shamiz: forma ostida "Ma'lumotlaringiz qanday ishlatiladi?" tugmasi qisqa izohli oynani ochadi.

  1. aloqa/index.html da: tugma va dialog (sarlavha, 2–3 gaplik matn, "Tushunarli" tugmasi). JavaScript'siz.
  2. Oyna ochiqligida sahifa surilmasin va skrollbar yo'qolganda sakramasin.
  3. Telefonda (40rem gacha) oyna pastdan chiqadigan sheet bo'lsin.
  4. Fokus "Tushunarli" tugmasida bo'lsin, pardaga bosish oynani yopsin (bu oynada yo'qotiladigan narsa yo'q).

Ishora: portfolio'dagi [popover], dialog uslubi max-inline-size, padding, ramka va shaffoflik o'tishini allaqachon beradi — faqat yetishmaganini qo'shing. Tugma ikkinchi variantda (Tugmalar tizimi, footer'dagi tugma kabi).

Yechim

aloqa/index.html da, formadan keyin:

html
<p>
  <button type="button" commandfor="maxfiylik"
    command="show-modal">Ma'lumotlaringiz qanday ishlatiladi?</button>
</p>
<dialog id="maxfiylik" aria-labelledby="maxfiylik-sarlavha"
  closedby="any">
  <h2 id="maxfiylik-sarlavha">Ma'lumotlaringiz haqida</h2>
  <p>Ismingiz va email'ingiz faqat menga keladi va faqat javob
    yozish uchun ishlatiladi. Ular hech kimga berilmaydi.</p>
  <form method="dialog">
    <button type="submit" autofocus>Tushunarli</button>
  </form>
</dialog>

asos.css ga (html dagi scrollbar-gutter: stable portfolio'da Overflow darsidan beri bor — skrollbar yo'qolganda sahifa sakramaydi):

css
/* Modal ochiqligida sahifa surilmaydi (Modal oyna darsi) */
html:has(dialog:modal) {
  overflow: hidden;
}

komponentlar.css dagi top layer qismiga:

css
/* Ma'lumot tugmasi — ikkinchi variant */
main > p > button[command="show-modal"] {
  --_fon: transparent;
  --_matn: var(--rang-asosiy);
  --_chegara: var(--rang-asosiy);
}

dialog {
  overscroll-behavior: contain;
}

dialog h2 {
  margin-block-start: 0;
}

dialog form {
  display: flex;
  justify-content: end;
}

@media (width < 40rem) {
  dialog {
    inline-size: 100%;
    max-inline-size: 100%;
    margin-block-end: 0;
    border-end-start-radius: 0;
    border-end-end-radius: 0;
  }
}
  • Oynaning max-inline-size: min(100% - 2rem, 28rem) i (Top layer darsidagi portfolio uslubi) telefonda 100% ga almashadi — sheet butun enda.
  • Ochilish animatsiyasi o'zgarmadi: portfolio oynasi faqat shaffoflik bilan ochiladi. Sheet'ni pastdan chiqarmoqchi bo'lsangiz — «Bahor»dagi --_kirish usulini no-preference ichida qo'shing.
  • aloqa/index.html dagi form formaning dialog ichidagisi bilan chalkashmasin: dialog form selektori faqat oyna ichidagisiga tegadi. Aloqa formasining display: grid qoidasi (form teg selektori) ham oyna formasiga tushadi — dialog form og'irroq, flex yutadi.
  • closedby="any" — Safari'da ishlamaydi, u yerda "Tushunarli" va Esc qoladi. Bu oynada yo'qotiladigan narsa yo'q, shuning uchun parda bilan yopish qulay.
  • html-validate (11.16.1 da tekshirdik) command, commandfor va closedby ni taniydi. Faqat bitta talab: oyna formasidagi tugmada ham type="submit" yozilsin — aks holda no-implicit-button-type xatosi.

Tekshiring: tugmani bosing — fokus "Tushunarli" da; pardada g'ildirakni aylantiring — sahifa qimirlamaydi; 1280 pikselda ochib-yopganda forma chetlari joyidan siljimaydi. So'ng npm run check.

9. Real ishda

  • Deyarli har mahsulotda modal bor: kirish oynasi, tasdiqlash, savat, rasm ko'rish. Ilgari ular div + z-index: 9999 + JavaScript'dagi fokus tuzog'i bilan yozilardi — yuzlab qator. Radix, Headless UI kabi kutubxonalar (kursda keyinroq) bugun ichkarida dialog ga o'tmoqda.
  • Mobil ilovalardagi sheet — Telegram'da xabar menyusi, taksi ilovasida manzil tanlash — veb'da ham shu naqsh: pastdan chiqadi, bosh barmoq yetadi.
  • Skroll bloklash — kod ko'rigida tez-tez uchraydigan izoh: "Modal ochiq, lekin orqa sahifa suriladi" va "Modal ochilganda sahifa 15 piksel sakraydi".
  • Intervyuda so'raladi: "Modal ochilganda orqa sahifa skrollini qanday bloklaysiz?", "scrollbar-gutter nima uchun?", "overscroll-behavior qanday muammoni hal qiladi?", "Modal va popover farqi?".

Xulosa

  • Modal — dialog + command="show-modal" (JavaScript'da showModal()): top layer, orqa sahifa inert, Esc yopadi, fokus ichkariga kiradi va qaytadi.
  • Skroll bloklash — uch qator: html:has(dialog:modal) { overflow: hidden }, html { scrollbar-gutter: stable }, dialog { overscroll-behavior: contain }.
  • :modal, [open] emas: non-modal ochiq oyna sahifani bloklamasin.
  • Uzun kontent: oyna — flex ustun, ichki qism — min-block-size: 0; overflow-y: auto; sarlavha va tugmalar joyida.
  • Telefonda — sheet: margin-block-end: 0, butun en, tepa burchaklar yumaloq; harakat bitta --_kirish o'zgaruvchisidan.
  • closedby="any" va overscroll-behavior — Baseline emas, scrollbar-gutter, invoker commands va @starting-style — yangi Baseline; hammasi ishlamagan joyda ham oyna ishlaydi.

Keyingi dars: Tooltip, dropdown va toast — oynadan kichikroq, lekin tez-tez uchraydigan "suzuvchi" komponentlar: tugma yonidagi izoh, ochiladigan ro'yxat va "Bron qabul qilindi" xabari.

Manbalar

  • MDN: "<dialog>", "Invoker Commands API", ":modal", "::backdrop", "scrollbar-gutter", "overscroll-behavior", "closedby", "@starting-style", "overlay" — developer.mozilla.org
  • HTML Standard: "The dialog element" — html.spec.whatwg.org
  • WAI-ARIA Authoring Practices: "Dialog (Modal) pattern" — w3.org/WAI/ARIA/apg
  • web-features (Baseline ma'lumotlari): "dialog", "invoker-commands", "dialog-closedby", "scrollbar-gutter", "overscroll-behavior", "starting-style", "overlay", "has" — github.com/web-platform-dx/web-features
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
CSS modal oyna: dialog, skroll bloklash va mobil "sheet" — IlmHamroh