Mundarija (39)
- Bu darsda
- 1. Nega bu kerak?
- 2. Chegaradan nima o'tadi
- 2.1 Jadval
- 2.2 CSS o'zgaruvchilari — mavzuning asosiy yo'li
- 3. :host — host'ni ichkaridan bezash
- 3.1 Asosiy shakl
- 3.2 :host() — holatga qarab
- 3.3 :host-context() — ishlatmang
- 4. Kaskad: ichkari va tashqari to'qnashsa
- 4.1 Sinov
- 4.2 Va'da: kaskadning "kontekst" bosqichi
- 5. ::slotted() — slotga tushgan kontent
- 5.1 Asosiy shakl
- 5.2 Uchta cheklov
- 5.3 Matnni bezash — slot ning o'ziga
- 6. ::part() — tashqariga ochilgan elementlar
- 6.1 Komponent eshik ochadi
- 6.2 Nima mumkin va nima mumkin emas
- 6.3 exportparts — ichma-ich komponentlar
- 7. adoptedStyleSheets — bitta stil, ko'p komponent
- 7.1 Muammo
- 7.2 Constructable stylesheets
- 7.3 CSS modul skriptlari
- 8. :state() — komponentning ichki holatlari
- 9. Ko'p uchraydigan xatolar
- 9.1 :host { display: block } ni unutish
- 9.2 Ichkarini tashqi selektor bilan bezashga urinish
- 9.3 :host ni sahifa qoidasidan kuchli deb o'ylash
- 9.4 ::slotted() bilan chuqur bezash
- 9.5 Shriftni shadow ichida e'lon qilish
- 10. Mashqlar
- 1-mashq (oson): Mavzu o'zgaruvchilari
- 2-mashq (o'rta): Tugmani hamkor uslubida
- 3-mashq (qiyin): Holat va umumiy stil
- 4-mashq: Amaliy tajriba — ikki sayt, bitta komponent
- 11. Real ishda
- Xulosa
- Manbalar
CSS va Shadow DOM: :host, ::slotted(), ::part() bilan web komponentlarni stillash
Qisqacha: Shadow DOM sahifa CSS'ini ichkariga kiritmaydi, lekin to'liq yopiq emas — uning maxsus "eshiklari" bor. Ichkaridan:
:host(host'ning o'zi),:host(.main)(holatiga qarab),::slotted(…)(slotga tushgan kontent). Tashqaridan: meros bo'ladigan xususiyatlar (color,font), CSS o'zgaruvchilari (chegaradan o'tadi — mavzu uchun asosiy yo'l) va::part(nom)(komponentpart="nom"bilan ochgan elementlar). Bir stilni ko'p komponent bo'lishishi uchun —adoptedStyleSheets.
Bu darsda
- Shadow chegarasidan nima o'tishi va nima o'tmasligini aniq ayta olasiz.
- Komponent ichidan host'ni
:hostva:host()bilan, slotdagi kontentni::slotted()bilan bezay olasiz. - Komponentni tashqaridan CSS o'zgaruvchilari va
::part()bilan sozlashga imkon bera olasiz. - Tashqi va ichki qoidalar to'qnashsa, qaysi biri yutishini (kaskaddagi "kontekst" bosqichi) tushuntira olasiz.
adoptedStyleSheetsva:state()ni ishlata olasiz.
Oldin bilishingiz kerak: Web Components'ga kirish, Web Components, Kaskad, Meros va global kalit so'zlar, CSS o'zgaruvchilari 2.
1. Nega bu kerak?
O'tgan darsda <dish-card> ni yozdik. Uning stili Shadow DOM ichida — hamkor kafe sahifasining h3 qoidasi uni endi buzmaydi. Muammo hal bo'ldi.
Lekin yangi so'rov keldi. Hamkor kafe brendi to'q sariq. Ular aytdi: "Kartangiz yaxshi, faqat sarlavhasi bizning rangimizda bo'lsin, tugmasi esa bizning tugmalarimizdek dumaloq". Sardor hamkor sahifasiga dish-card h3 { color: orange; } yozib ko'rdi — hech narsa o'zgarmadi. Albatta: biz aynan shuni xohlagan edik, tashqi CSS ichkariga kirmasin.
Demak, ikki talab bir-biriga zid tuyuladi:
- Tashqi CSS komponentni tasodifan buzmasin.
- Tashqi sahifa komponentni ataylab sozlay olsin.
Shadow DOM ikkalasini ham beradi. U devor emas, eshiklari bor uy. Bu darsda shu eshiklarni birma-bir ochamiz: qaysilarini komponent muallifi ochadi va qaysilarini sahifa muallifi ishlatadi.
2. Chegaradan nima o'tadi
2.1 Jadval
Avvalgi ikki darsda chegaraning asosiy qoidasini ko'rdik. Endi to'liq ro'yxat. Hammasini Chrome 154 da tekshirdik:
| Narsa | Chegaradan o'tadimi? |
|---|---|
Sahifadagi selektorlar (h3, .price, *) |
Yo'q — ichki elementlarni topmaydi |
Meros bo'ladigan xususiyatlar (color, font-family, line-height) |
Ha — host'dan ichkariga meros |
CSS o'zgaruvchilari (--color) |
Ha — ular ham meros bo'ladi |
Host'ning o'ziga yozilgan qoida (dish-card { … }) |
Ha — host light DOM'da |
::part() bilan ochilgan elementlar |
Ha — faqat ochilganlari |
Bitta kutilmagan oqibat. Ko'p loyihada umumiy "reset" bor: * { box-sizing: border-box; }. U shadow ichiga kirmaydi. Chrome'da tekshirdik: sahifada shu qoida bor, lekin shadow ichidagi div content-box qolib ketdi. Komponent kerakli reset'ni o'z ichiga yozishi kerak.
2.2 CSS o'zgaruvchilari — mavzuning asosiy yo'li
CSS o'zgaruvchilari meros bo'ladi. Ichkarida var(--name) ishlatilsa, qiymat host'dan, undan yuqoridagi body dan, :root dan keladi. Shuning uchun komponent muallifi "sozlanadigan joylar"ni o'zgaruvchi qilib qo'yadi:
/* komponent ichida */
h3 { color: var(--dish-card-color, #2e7d32); }var() ning ikkinchi qiymati — zaxira (CSS o'zgaruvchilari 2 darsidan): sahifa hech narsa bermasa, yashil.
Endi hamkor sahifasi bitta qator yozadi:
/* hamkor sahifasida */
:root { --dish-card-color: #d35400; }Sinab ko'ring. Uch karta: birinchisiga hech narsa berilmagan, ikkinchisi butun sahifa mavzusini oladi, uchinchisiga alohida rang berilgan:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
.partner { --dish-card-color: #d35400; }
#special { --dish-card-color: #6a1b9a; }
</style>
<dish-card>Osh</dish-card>
<div class="partner">
<dish-card>Manti</dish-card>
<dish-card id="special">Lag'mon</dish-card>
</div>
<script></script>Konsolda:
Osh rgb(46, 125, 50)
Manti rgb(211, 84, 0)
Lag'mon rgb(106, 27, 154)Nimaga qarang: komponent kodi bitta, ranglar uch xil. Osh — zaxira yashil. Manti — .partner dan meros olgan to'q sariq. Lag'mon — o'ziga yozilgan binafsha: yaqinroq ota yutadi, oddiy meros qoidasi.
Komponentning sozlanadigan o'zgaruvchilari — uning ommaviy API'si. Dizayn tizimlari ularni hujjatda alohida ro'yxat qilib yozadi ("CSS custom properties"). Nom oldiga komponent nomini qo'shish (--dish-card-…) to'qnashuvdan saqlaydi: sahifadagi boshqa --color komponentni tasodifan bo'yab qo'ymaydi.
Tekshirib ko'ring: Komponent ichida
h3 { color: var(--color); }(zaxirasiz) yozilgan. Sahifa--colorni hech qayerda bermagan. Sarlavha qanday rangda bo'ladi?
Javob
Meros rangida — host'ning color i (masalan, body dagi qora). Zaxirasiz var() ga qiymat topilmasa, xususiyat "hisoblashda yaroqsiz" bo'ladi va color uchun meros qiymat olinadi (CSS o'zgaruvchilari 2 darsidagi qoida). Xato chiqmaydi, lekin dizayner kutgan rang ham chiqmaydi. Shuning uchun zaxira qiymat yozish — yaxshi odat.
3. :host — host'ni ichkaridan bezash
3.1 Asosiy shakl
Komponent ichidagi <style> host'ning o'zini oddiy selektor bilan topa olmaydi — host shadow daraxtda emas. Buning uchun maxsus pseudo-klass bor: :host.
:host {
display: block;
padding: 0.75rem 1rem;
border: 1px solid #c8d6c8;
}Muhim: custom element'ning standart display qiymati — inline, xuddi span kabi. Chrome'da tekshirdik: hech narsa yozilmagan host display: inline. Karta, panel kabi blok komponentlarda :host { display: block; } deyarli doim kerak. Aks holda padding va margin g'alati ishlaydi, width esa umuman ishlamaydi.
:host da yashirish uchun ham qoida qo'shish odat: :host([hidden]) { display: none; }. Sababi — display: block brauzerning [hidden] qoidasini bosib ketadi.
3.2 :host() — holatga qarab
Funksional shakl :host(selektor) — "host shu selektorga mos kelsa". Web Components darsidagi kartada ishlatgan edik:
:host([sold-out]) { opacity: 0.5; } /* atribut bor bo'lsa */
:host(.main) { border-width: 3px; } /* klass bor bo'lsa */
:host(:hover) { background: #f3f8f3; } /* sichqoncha ustida */
:host(:focus-within) { outline: 2px solid #2e7d32; }Qavs ichida faqat host'ning o'zi haqidagi oddiy (compound) selektor bo'ladi. :host(.menu .main) — host'ning otasi haqida so'rash — ishlamaydi.
3.3 :host-context() — ishlatmang
Ba'zi maqolalarda :host-context(.dark) ni uchratasiz: "host'ning ota-bobolari orasida .dark bo'lsa". U faqat Chromium brauzerlarida (Chrome, Edge) bor. Firefox va Safari uni qo'llamaydi — web-features 3.40 da :host-context() Baseline emas.
Uning o'rniga — CSS o'zgaruvchisi. Tungi rejimda sahifa --dish-card-bg ni o'zgartiradi, komponent shuni o'qiydi. Natija bir xil, hamma brauzerda ishlaydi.
4. Kaskad: ichkari va tashqari to'qnashsa
4.1 Sinov
Endi qiziq savol. Komponent ichida :host { border: 1px solid green; }. Sahifada esa dish-card { border: 3px solid orange; }. Ikkalasi bitta elementga — host'ga — tegadi. Qaysi biri yutadi?
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
dish-card { border: 3px solid #d35400; }
dish-card { color: #d35400 !important; }
</style>
<dish-card>Osh</dish-card>
<script>
class DishCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" }).innerHTML = `
<style>
:host { display: block; padding: 0.5rem;
border: 1px solid #2e7d32;
color: #2e7d32 !important; }
</style>
<slot></slot>`;
}
}
customElements.define("dish-card", DishCard);
const host = document.querySelector("dish-card");
const style = getComputedStyle(host);
console.log("border:", style.borderTopWidth, style.borderTopColor);
console.log("color:", style.color);
console.log("display:", style.display);
</script>Konsolda:
border: 3px rgb(211, 84, 0)
color: rgb(46, 125, 50)
display: blockUch natija, uch xulosa:
- Ramka — tashqariniki. Oddiy (
!importantsiz) qoidalarda sahifa yutadi. Ichki:host— komponent muallifining "standart" taklifi. Sahifa uni bemalol o'zgartira oladi. - Rang — ichkariniki.
!importantqoidalarda tartib teskari: ichki!importanttashqi!importantdan kuchli. Komponent muallifi "bunga tegmang" deb qat'iy turib olishi mumkin. display— ichkariniki. Sahifadisplayhaqida hech narsa demadi — to'qnashuv yo'q.
4.2 Va'da: kaskadning "kontekst" bosqichi
Kaskad darsida "standartda yana ikkita kam uchraydigan bosqich bor, biri Shadow DOM darsida" degan edik. Mana u — kontekst (encapsulation context) bosqichi. U manba va !important bosqichidan keyin, inline stildan oldin turadi:
flowchart TD
A["Bir xususiyatga<br/>bir nechta qiymat"] --> B["1. Manba va !important"]
B -->|"teng"| K["Kontekst:<br/>tashqarimi, ichkarimi?"]
K -->|"teng"| C["2. Inline stil"]
C -->|"teng"| D["3. Kaskad qatlami"]
D -->|"teng"| E["4. Specificity"]
E -->|"teng"| F["5. Yozilish tartibi"]
F --> G["G'olib qiymat"]Nimaga qarang: kontekst specificity'dan oldin. Ya'ni ichki :host ning specificity'si qanchalik baland bo'lmasin, oddiy qoidalarda tashqi dish-card (bitta teg — eng past og'irlik) baribir yutadi. Bu ataylab qilingan: komponentdan foydalanuvchi sahifa egasi oxirgi so'zni aytishi kerak.
Qoida qisqacha: oddiy qoidada tashqi kontekst yutadi, !important da ichki kontekst yutadi. Bu !important ning umumiy mantiqiga mos: Kaskad darsida ko'rganimizdek, !important da manbalar tartibi ham teskari aylanadi.
Tekshirib ko'ring: Komponent ichida
:host { margin: 1rem; }, sahifada esabody dish-card.main#hero { margin: 0; }(juda yuqori specificity). Qaysimarginishlaydi? Agar sahifa qoidasidish-card { margin: 0; }bo'lsa-chi?
Javob
Ikkala holatda ham margin: 0 — tashqi qoida. Specificity bu yerda ahamiyatsiz: kontekst bosqichi undan oldin hal qiladi va oddiy qoidalarda tashqi kontekst yutadi. Ichki qiymat faqat sahifa margin haqida umuman gapirmasa ishlaydi.
5. ::slotted() — slotga tushgan kontent
5.1 Asosiy shakl
template va slot darsida va'da bergan edik: slotga tushgan kontentni komponent ichidan bezash — ::slotted() pseudo-elementi bilan.
::slotted(p) { margin: 0; color: #555; }
::slotted([slot="price"]) { font-weight: 700; }
::slotted(*) { font-size: 0.95rem; }Qavs ichiga oddiy (compound) selektor yoziladi: teg, klass, atribut va ularning birikmasi. Psevdo-klass qavs ichida bo'lishi mumkin — ::slotted(a:hover).
5.2 Uchta cheklov
Chrome 154 da birma-bir tekshirdik:
- Faqat yuqori darajadagi elementlar.
::slotted(p)— slotga bevosita tushganp. Uning ichidagispanni topib bo'lmaydi:::slotted(p span)— yaroqsiz selektor, butun qoida tashlab yuboriladi.::slotted(*) bham yaroqsiz. - Qavsdan keyin psevdo-klass yo'q.
::slotted(a):hover— yaroqsiz. To'g'risi —::slotted(a:hover). - Sahifa yutadi. Slotdagi kontent light DOM'da — u sahifaga tegishli. Sahifadagi
span.note { color: purple }va ichki::slotted(span) { color: teal }to'qnashsa — binafsha. Bu o'sha kontekst qoidasi: oddiy qoidalarda tashqi yutadi. Ichki qoida faqat sahifa gapirmagan xususiyatlarga ta'sir qiladi.
Bu cheklovlar mantiqli. Slotdagi kontent — sahifa muallifiniki. Komponent unga faqat "standart ko'rinish" taklif qiladi, ichiga chuqur kirib, egasidan kuchliroq bo'la olmaydi.
5.3 Matnni bezash — slot ning o'ziga
Slotga oddiy matn tushsa (teg ichida emas), ::slotted() uni topa olmaydi — matn element emas. Yechim: slot elementining o'ziga stil berish. slot ning display qiymati — contents (o'z qutisi yo'q), lekin meros bo'ladigan xususiyatlar undan slotdagi kontentga o'tadi. Chrome'da tekshirdik: slot { color: green; } slotdagi span ni yashil qildi.
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
span.note { color: #6a1b9a; }
</style>
<dish-card>
<span slot="name">Osh</span>
<span class="note">Juma kuni maxsus</span>
Guruch, sabzi, go'sht.
</dish-card>
<script></script>Konsolda:
izoh rangi: rgb(106, 27, 154)
izoh shrifti: italicNimaga qarang: "Juma kuni maxsus" — binafsha (sahifa rangi yutdi), lekin qiya (sahifa font-style haqida gapirmadi, ichki qoida ishladi). "Osh" — firuza va qiya: unga sahifa qoidasi tegmaydi. Oddiy matn "Guruch, sabzi, go'sht." — kulrang, slot:not([name]) dan meros oldi.
6. ::part() — tashqariga ochilgan elementlar
6.1 Komponent eshik ochadi
CSS o'zgaruvchilari bitta-bitta qiymatni beradi: rang, o'lcham. Hamkor esa tugmani butunlay qayta bezamoqchi: dumaloq, soyali, boshqa shrift. Har xususiyat uchun alohida o'zgaruvchi — noqulay.
Buning uchun komponent muallifi ichki elementga part atributini qo'yadi:
<!-- komponent ichida -->
<button part="button">Savatga</button>
<h3 part="title">Osh</h3>part qiymati — o'zimiz tanlagan nom ("button" — tugma, "title" — sarlavha). U teg nomi emas: part="title" h3 da turibdi.
Sahifa esa uni ::part() pseudo-elementi bilan bezaydi:
/* sahifada */
dish-card::part(button) {
border-radius: 999px;
background: #d35400;
color: white;
}
dish-card::part(button):hover { background: #a84300; }part — komponentning ommaviy API'si, xuddi CSS o'zgaruvchilari kabi. Muallif qaysi elementlarni ochishni o'zi tanlaydi. Ochilmagan elementlar yopiq qoladi.
Bo'sh joyni to'ldiring: komponent ichida <button part="button"> bo'lsa, sahifa uni dish-card::(button) selektori bilan bezaydi.
6.2 Nima mumkin va nima mumkin emas
Chrome 154 da tekshirdik:
| Selektor | Holati |
|---|---|
dish-card::part(button) |
|
dish-card::part(button):hover, :focus-visible |
foydalanuvchi holatlari |
dish-card::part(button)::before |
pseudo-element |
dish-card::part(button) span |
ichiga kirish yo'q |
dish-card::part(button):first-child |
tuzilma psevdo-klasslari yo'q |
Mantiq bir xil: part — eshik, lekin faqat ochilgan elementning o'zigacha. Uning ichki tuzilmasi (span, tartib) yopiq qoladi. Shunda komponent muallifi ichki tuzilmani o'zgartirsa, sahifaning CSS'i buzilmaydi.
Bitta elementda bir nechta nom bo'lishi mumkin: part="button primary". ::part(button primary) — ikkala nom bor elementni topadi.
6.3 exportparts — ichma-ich komponentlar
<dish-card> ichida yana bir komponent bor deylik — <price-badge> ("narx belgisi", o'tgan darsning 1-mashqi), uning ichida part="value". Sahifa dish-card::part(value) yozsa — ishlamaydi. ::part() faqat bir qavat chuqurlikka ishlaydi.
Ichki komponentning part ini yuqoriga uzatish uchun — exportparts:
<!-- dish-card ichida -->
<price-badge exportparts="value: price-value"></price-badge>Endi sahifa dish-card::part(price-value) yoza oladi. Ikki nuqta orqali qayta nomlash ixtiyoriy: exportparts="value" — nom o'zgarmasdan uzatiladi. Chrome'da tekshirdik: exportparts li ichki element tashqi ::part() qoidasini oldi, exportparts siz qo'shni nusxa esa olmadi.
Tekshirib ko'ring: Hamkor tugma ichidagi ikonkani (
<button part="button"><svg>…</svg> Savatga</button>) yashirmoqchi.dish-card::part(button) svg { display: none; }ishlaydimi? Komponent muallifi nima qilishi kerak?
Javob
Ishlamaydi — ::part() dan keyin ichki elementga o'tib bo'lmaydi, selektor yaroqsiz. Muallif ikonkaga alohida part="icon" berishi kerak, keyin dish-card::part(icon) { display: none; }. Yoki ikonkani atribut bilan boshqaradi: :host([no-icon]) svg { display: none; }.
7. adoptedStyleSheets — bitta stil, ko'p komponent
7.1 Muammo
Hozirgacha har karta o'z <style> tegini oladi. 30 ta karta — 30 ta <style>, brauzer har birini alohida saqlaydi. Dizaynni o'zgartirish uchun esa har nusxaga tegish kerak.
7.2 Constructable stylesheets
Constructable stylesheet (JavaScript'da yasaladigan stil jadvali) — new CSSStyleSheet() bilan yaratiladigan stil obyekti. U bir marta yaratiladi va istalgancha shadow root'ga ulanadi — nusxa olinmaydi:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button#theme { font: inherit; padding: 0.3rem 0.8rem; }
</style>
<dish-card>Osh</dish-card>
<dish-card>Manti</dish-card>
<dish-card>Lag'mon</dish-card>
<button id="theme" type="button">Mavzuni almashtirish</button>
<script>
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
:host { display: block; padding: 0.4rem 0.8rem;
margin: 0.3rem 0; border-left: 4px solid #2e7d32;
background: #f3f8f3; }`);
class DishCard extends HTMLElement {
constructor() {
super();
const root = this.attachShadow({ mode: "open" });
root.adoptedStyleSheets = [sheet];
root.innerHTML = "<slot></slot>";
}
}
customElements.define("dish-card", DishCard);
const cards = document.querySelectorAll("dish-card");
const shared = [...cards].every(
(card) => card.shadowRoot.adoptedStyleSheets[0] === sheet);
console.log("Hammasida bitta obyekt:", shared);
document.getElementById("theme").addEventListener("click", () => {
sheet.replaceSync(`
:host { display: block; padding: 0.4rem 0.8rem;
margin: 0.3rem 0; border-left: 4px solid #d35400;
background: #fdf0e6; }`);
const color = getComputedStyle(cards[2]).borderLeftColor;
console.log("Lag'mon ramkasi:", color);
});
</script>Konsolda:
Hammasida bitta obyekt: true«Mavzuni almashtirish» ni bosing: uchala karta bir zumda to'q sariqqa o'tadi va konsolga Lag'mon ramkasi: rgb(211, 84, 0) chiqadi (biz bosib tekshirdik). Biz faqat bitta obyektni o'zgartirdik.
Qatorma-qator:
new CSSStyleSheet()— bo'sh stil jadvali.sheet.replaceSync(matn)— uning ichini CSS matni bilan almashtiradi. Asinxron varianti —sheet.replace(matn), u Promise qaytaradi.root.adoptedStyleSheets = [sheet]— shadow root bu jadvalni "asrab oladi" (adopt). Bu oddiy massiv: bir nechta jadval berish mumkin, tartibi muhim — keyingisi yutadi.- Shu xususiyat
documentda ham bor:document.adoptedStyleSheets— sahifaning o'ziga JavaScript'dan stil qo'shish.
Bitta cheklov: replaceSync ichida @import ishlamaydi. Chrome 154 uni tashlab yuborib, konsolda ogohlantiradi: @import rules are not allowed here. Qolgan qoidalar ishlayveradi.
Constructable stylesheets 2023-yil martidan Baseline, 2025-yil sentabridan widely available (web-features 3.40).
7.3 CSS modul skriptlari
Dinamik import darsida bir qatorni ko'rgan edik: import sheet from "./dish-card.css" with { type: "css" }. Natija — aynan shu CSSStyleSheet obyekti, uni adoptedStyleSheets ga berasiz. CSS alohida faylda turadi, muharrir uni CSS deb taniydi. Lekin u hali Safari'da yo'q — Baseline emas. Hozircha replaceSync yoki bundler (16-qismda) ishonchliroq.
8. :state() — komponentning ichki holatlari
:host([sold-out]) atribut bilan ishlaydi. Lekin ba'zi holatlar atribut bo'lmasligi kerak: masalan, "loading" yoki "selected". Ularni HTML'da ko'rsatish shart emas, sahifa ularni o'zgartirmasligi ham kerak.
Buning uchun custom state (maxsus holat) bor. Komponent ElementInternals orqali holatni qo'shadi:
class DishCard extends HTMLElement {
#internals = this.attachInternals();
set loading(value) {
if (value) this.#internals.states.add("loading");
else this.#internals.states.delete("loading");
}
}CSS esa uni :state() bilan o'qiydi — ichkaridan ham, tashqaridan ham:
/* komponent ichida */
:host(:state(loading)) { opacity: 0.6; cursor: progress; }
/* sahifada */
dish-card:state(loading) { outline: 2px dashed #999; }states — Set ga o'xshash obyekt: add, delete, has. Chrome'da tekshirdik: holat qo'shilgach, ichki :host(:state(…)) ham, tashqi dish-card:state(…) ham ishladi, element.matches(":state(loading)") — true. Atributdan farqi: sahifa holatni o'qiy oladi, lekin o'zgartira olmaydi — #internals private.
:state() 2024-yil mayidan Baseline (Chrome 125, Firefox 126, Safari 17.4).
9. Ko'p uchraydigan xatolar
9.1 :host { display: block } ni unutish
Custom element — inline. Karta width olmaydi, margin yuqori-pastda ishlamaydi. Tuzatish: har blok komponentda :host { display: block; } va :host([hidden]) { display: none; }.
9.2 Ichkarini tashqi selektor bilan bezashga urinish
dish-card h3 { color: orange; } — jim. Shadow ichiga selektor kirmaydi. Tuzatish: komponent CSS o'zgaruvchisi yoki part ochgan bo'lsa — shular orqali. Ochmagan bo'lsa — muallifdan so'rang yoki boshqa komponent tanlang.
9.3 :host ni sahifa qoidasidan kuchli deb o'ylash
Muallif :host { padding: 1rem } yozdi, lekin sahifadagi dish-card { padding: 0 } uni bosib ketdi. Specificity'ni oshirish yordam bermaydi — kontekst bosqichi oldinroq. Tuzatish: bu xato emas, xususiyat: sahifa oxirgi so'zni aytadi. Muallif rostdan qat'iy bo'lishi kerak bo'lsa — !important, lekin faqat zarur joyda.
9.4 ::slotted() bilan chuqur bezash
::slotted(ul li) — yaroqsiz, butun qoida tashlanadi. Tuzatish: ::slotted(ul) va meros, yoki ichki ro'yxat uchun sahifaning o'z CSS'i — kontent baribir sahifaniki.
9.5 Shriftni shadow ichida e'lon qilish
Komponent ichidagi <style> ga @font-face yozildi, shrift chiqmadi. Chrome'da tekshirdik: shadow ichidagi @font-face document.fonts ro'yxatiga tushmadi. Tuzatish: @font-face ni sahifa CSS'ida e'lon qiling, komponent ichida faqat font-family nomini ishlating — u meros bo'lib ham o'tadi.
10. Mashqlar
1-mashq (oson): Mavzu o'zgaruvchilari
<discount-tag> ("chegirma yorlig'i") komponentini yozing: ichida <slot>, fon rangi --discount-tag-bg (zaxira #c0392b), matn rangi --discount-tag-color (zaxira white). Sahifada uch yorliq: biri zaxira ranglarda, ikkitasi .partner ichida, u yerda --discount-tag-bg: #d35400. Konsolga uchala yorliqning fon rangini chiqaring.
Ishora: «CSS o'zgaruvchilari — mavzuning asosiy yo'li» bo'limidagi kod; fon :host ga yoziladi; :host { display: inline-block; }.
Yechim
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
.partner { --discount-tag-bg: #d35400; }
</style>
<discount-tag>-10%</discount-tag>
<p class="partner">
<discount-tag>-15%</discount-tag>
<discount-tag>-20%</discount-tag>
</p>
<script></script>Konsolda:
-10% rgb(192, 57, 43)
-15% rgb(211, 84, 0)
-20% rgb(211, 84, 0)Fon :host ga yozilgan — getComputedStyle(tag) uni host'ning o'zida ko'radi. --discount-tag-color hech qayerda berilmagan, shuning uchun matn uchalasida ham oq (zaxira).
2-mashq (o'rta): Tugmani hamkor uslubida
Quyidagi komponentda tugmaga part="button" qo'shing. Keyin sahifa CSS'ida faqat ::part() bilan tugmani dumaloq (border-radius: 999px), to'q sariq fonli va oq matnli qiling, sichqoncha ustida esa to'qroq (#a84300). Konsolga tugmaning border-radius qiymatini chiqaring.
this.attachShadow({ mode: "open" }).innerHTML =
`<button type="button">Savatga</button>`;Ishora: ::part() dan keyin :hover mumkin.
Yechim
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
dish-card::part(button) {
font: inherit; padding: 0.4rem 1rem; border: 0;
border-radius: 999px; background: #d35400; color: white;
}
dish-card::part(button):hover { background: #a84300; }
</style>
<dish-card></dish-card>
<script>
class DishCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: "open" }).innerHTML =
`<button type="button" part="button">Savatga</button>`;
}
}
customElements.define("dish-card", DishCard);
const button = document.querySelector("dish-card")
.shadowRoot.querySelector("button");
console.log("radius:", getComputedStyle(button).borderRadius);
</script>Konsolda:
radius: 999pxKomponentda faqat bitta atribut qo'shildi — part="button". Qolgan hammasini sahifa hal qildi. Komponent muallifi tugma ichini keyin o'zgartirsa ham (masalan, ikonka qo'shsa), hamkorning stili buzilmaydi.
3-mashq (qiyin): Holat va umumiy stil
<order-row> ("buyurtma qatori") komponentini yozing. Talablar:
- Barcha nusxalar bitta
CSSStyleSheetni bo'lishsin (adoptedStyleSheets). - Bosilganda komponent
selectedcustom state'ini yoqib-o'chirsin (states.add/states.delete). - Ichki stil:
:host(:state(selected))— yashil chap ramka. - Sahifa stili:
order-row:state(selected)— qalin shrift. - Sahifada ikki qator. Ikkinchisini bosgandan keyin konsolga ikkala qatorning
matches(":state(selected)")qiymatini chiqaring.
Ishora: holatni almashtirish — has bilan tekshirib, add yoki delete. Bosish tinglovchisini konstruktorda this ga qo'shing.
Yechim
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
order-row:state(selected) { font-weight: 700; }
</style>
<order-row>Osh × 2</order-row>
<order-row id="second">Manti × 1</order-row>
<script>
const sheet = new CSSStyleSheet();
sheet.replaceSync(`
:host { display: block; padding: 0.3rem 0.6rem; cursor: pointer;
border-left: 4px solid transparent; }
:host(:state(selected)) { border-left-color: #2e7d32; }`);
class OrderRow extends HTMLElement {
#internals = this.attachInternals();
constructor() {
super();
const root = this.attachShadow({ mode: "open" });
root.adoptedStyleSheets = [sheet];
root.innerHTML = "<slot></slot>";
this.addEventListener("click", () => this.#toggle());
}
#toggle() {
const states = this.#internals.states;
if (states.has("selected")) states.delete("selected");
else states.add("selected");
}
}
customElements.define("order-row", OrderRow);
document.getElementById("second").addEventListener("click", () => {
for (const row of document.querySelectorAll("order-row")) {
console.log(row.textContent, row.matches(":state(selected)"));
}
});
</script>Ikkinchi qatorni bossangiz, u qalin bo'ladi va yashil ramka oladi. Konsolda:
Osh × 2 false
Manti × 1 trueBiz bosib tekshirdik. Komponentning click tinglovchisi sahifanikidan oldin ishladi: u konstruktorda qo'shilgan — sahifa skripti esa keyinroq. Bitta elementdagi tinglovchilar qo'shilish tartibida chaqiriladi, shuning uchun konsol yangi holatni ko'rsatdi. Klaviatura uchun qatorga tabindex="0" va role="button" hamda Enter tinglovchisi ham kerak bo'lardi — Bobur uchun buni o'zingiz qo'shib ko'ring.
4-mashq: Amaliy tajriba — ikki sayt, bitta komponent
kurs/mashqlar/13/26-mavzu/ papkasida ikki sahifa yarating: bahor.html va hamkor.html. Ikkalasi bir xil dish-card.js ni ulasin (Web Components darsining 4-mashqidagi modul). Komponentga quyidagi API'ni qo'shing va hujjatlang (fayl boshidagi izohda):
- CSS o'zgaruvchilari:
--dish-card-color(sarlavha),--dish-card-bg. - Part'lar:
title,button.
hamkor.html faqat shu API orqali kartani to'q sariq va dumaloq tugmali qilsin. Live Server bilan ikkala sahifani yonma-yon oching.
Yechim
dish-card.js boshida izoh:
/**
* <dish-card name="Osh" price="35000" [sold-out]>tavsif</dish-card>
* CSS o'zgaruvchilari: --dish-card-color, --dish-card-bg
* Part'lar: title, button
* Hodisa: add-to-cart (detail: { name, price })
*/Komponent stilida: :host { background: var(--dish-card-bg, white); }, h3 { color: var(--dish-card-color, #2e7d32); }, qolipda <h3 class="name" part="title"> va <button type="button" part="button">. hamkor.html da:
:root { --dish-card-color: #d35400; --dish-card-bg: #fffaf5; }
dish-card::part(button) { border-radius: 999px; border: 0;
background: #d35400; color: white; }Hamkor komponent kodiga tegmadi. Ertaga «Bahor» kartaga yangi maydon qo'shsa, hamkor sahifasi uni avtomatik oladi — hujjatlangan API o'zgarmaguncha.
git add 13/26-mavzu
git commit -m "13/26: dish-card uchun CSS o'zgaruvchilari va part"11. Real ishda
- Dizayn tizimlari hujjatlari. Material Web, Spectrum, Web Awesome sahifalarida har komponent uchun "CSS custom properties" va "CSS parts" jadvallari bor — mavzu shular orqali beriladi.
- Brendlash. Bitta vidjet (to'lov, chat, bron) o'nlab mijoz saytida ishlaydi; har mijoz o'z ranglarini o'zgaruvchilar bilan beradi.
- Tungi rejim.
:rootdagi o'zgaruvchilarprefers-color-schemebilan almashadi — barcha komponentlar o'zi moslashadi,:host-contextkerak emas. - Intervyu. "Shadow DOM ichidagi elementni tashqaridan qanday bezaysiz?", "
::partva CSS o'zgaruvchisi farqi?", ":hoststili nega ishlamayapti?" — Web Components bilan ishlaydigan jamoalarda so'raladi.
Xulosa
- Chegaradan selektorlar o'tmaydi; meros bo'ladigan xususiyatlar va CSS o'zgaruvchilari o'tadi.
- Ichkaridan:
:host,:host(selektor)— host'ni;::slotted(selektor)— slotdagi yuqori darajadagi elementlarni bezaydi.:host { display: block }ni unutmang. - Tashqaridan: CSS o'zgaruvchilari (qiymatlar),
::part(nom)(butun element), ichma-ich komponentlardaexportparts. - Kaskadning kontekst bosqichi: oddiy qoidada tashqi yutadi,
!importantda ichki yutadi — specificity'dan oldin. adoptedStyleSheets— bittaCSSStyleSheetko'p shadow root'da;:state()— atributsiz ichki holatlar.
Keyingi dars: Rendering pipeline va layout thrashing — brauzer stil, joylashuv va bo'yashni qanday bajaradi, JavaScript qaysi o'qishda uni "majburan" ishlatadi va buni Chrome'da qanday o'lchaymiz.
Manbalar
- CSS Scoping Module Level 1:
:host,::slotted(), shadow kontekst kaskadi — w3.org/TR/css-scoping-1 - CSS Shadow Parts:
::part(),exportparts— w3.org/TR/css-shadow-parts-1 - CSS Cascading and Inheritance Level 5: "Context" (encapsulation context) — w3.org/TR/css-cascade-5
- MDN:
:host,::slotted(),::part(),CSSStyleSheet(),CustomStateSet— developer.mozilla.org - web-features 3.40:
shadow-parts,constructed-stylesheets,state,host-context— github.com/web-platform-dx/web-features
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!