Mundarija (39)
- Bu darsda
- 1. Nega bu kerak?
- 2. matchMedia asoslari
- 2.1 Birinchi savol
- 2.2 media nimani ko'rsatadi
- 2.3 Zamonaviy diapazon sintaksisi
- 3. change hodisasi: javob o'zgarganda
- 3.1 Bir marta tekshirish yetmaydi
- 3.2 resize bilan taqqoslash
- 3.3 Eski usul: addListener
- 4. Foydalanuvchi sozlamalari
- 4.1 Tizim nimani aytadi
- 4.2 Sozlamani DevTools'da sinash
- 5. Tema: tizim, yorug' yoki qorong'i
- 5.1 Uch holat
- 5.2 Jonli misol
- 5.3 Kod qatorma-qator
- 5.4 Telefon paneli: theme-color
- 5.5 «Bahor» va portfolio
- 6. Harakatni kamaytirish
- 6.1 JavaScript animatsiyalari ham so'rashi kerak
- 6.2 "Kamaytirish" — o'chirish degani emas
- 6.3 Ishlab turgan harakatni to'xtatish
- 7. JavaScript yoki CSS?
- 8. Node'da yo'q
- 9. Ko'p uchraydigan xatolar
- 9.1 Faqat change ga tayanish
- 9.2 Qavssiz savol
- 9.3 resize + innerWidth
- 9.4 Faqat tizimga qarash
- 9.5 Sozlamani "majburlash"
- 10. Mashqlar
- 1-mashq (oson): Sichqoncha yoki barmoq
- 2-mashq (o'rta): Yumshoq aylantirish tugmasi
- 3-mashq (qiyin): watchMedia yordamchisi
- 4-mashq: Vazifalar qadami — tema tanlovi
- 11. Real ishda
- Xulosa
- Manbalar
JavaScript matchMedia: media query'lar va foydalanuvchi sozlamalari
Qisqacha:
matchMedia("(min-width: 40rem)")— CSS'dagi@mediasavolini JavaScript'dan beradi. UMediaQueryListobyektini qaytaradi:.matches— shart hozir bajarilyaptimi (true/false),changehodisasi — javob o'zgargan paytda keladi. Shu bilan tizimning qorong'i rejimi (prefers-color-scheme) va "harakatni kamaytirish" (prefers-reduced-motion) sozlamasiga JavaScript'dan moslashasiz.
Bu darsda
matchMediabilan istalgan media query'ni JavaScript'dan tekshira olasiz vamatches,mediaxususiyatlarini o'qiysiz.changehodisasiniresizedan farqlaysiz: u faqat javob almashganda keladi.- Tizim/yorug'/qorong'i tanlovli temani yozasiz: tizim o'zgarishi jonli kuzatiladi, telefon paneli rangi ham moslashadi.
- "Harakatni kamaytirish" sozlamasini JavaScript animatsiyalarida hurmat qilasiz.
vazifalarga tema tanlovini qo'shasiz.
Oldin bilishingiz kerak: Qorong'i rejim va temalash, prefers-reduced-motion va harakat etikasi, Fokus, scroll va resize hodisalari, localStorage va sessionStorage, requestAnimationFrame.
1. Nega bu kerak?
O'tgan darsda ResizeObserver bilan element o'lchamini kuzatdik. Bugun boshqa savol: ekran va foydalanuvchi haqida.
CSS'da bunga javob bor — media query. @media (min-width: 40rem) keng ekranda boshqa stil beradi, @media (prefers-color-scheme: dark) qorong'i rejimda. Ko'rinish o'zgarishi uchun shu yetadi. Lekin ba'zi qarorlarni CSS qila olmaydi:
- «Bahor» bosh sahifasida taomlar karuseli o'zi aylanadi. "Harakatni kamaytirish" yoqilgan mehmonga avto-aylanish kerak emas. Aylanishni JavaScript boshqaradi — demak u sozlamani bilishi kerak.
- Telefonda brauzerning yuqori paneli
<meta name="theme-color">rangida bo'yaladi. Mehmon qo'lda "Qorong'i" ni tanlasa, sahifa qorong'i bo'ladi, panel esa yorug' qoladi. Meta tegni faqat JavaScript o'zgartira oladi. <canvas>dagi grafik CSS'ni tushunmaydi. Qorong'i rejimda chiziq rangini JavaScript tanlaydi.- Keng ekranda yon panel ochiq turadi, tor ekranda — yopiq. Bu holat JavaScript'dagi o'zgaruvchida saqlanadi.
Fokus, scroll va resize darsida aytgan edik: "Ekran 40rem dan kengmi?" degan savolga resize siz javob beradigan vosita bor — CSS media query'ning JavaScript'dagi egizagi. U — matchMedia.
2. matchMedia asoslari
2.1 Birinchi savol
const wide = matchMedia("(min-width: 40rem)");
console.log(wide.matches); // true yoki false — ekraningizga qarab
console.log(wide.media); // (min-width: 40rem)matchMedia ga CSS'dagi @media dan keyin yoziladigan matnni berasiz — aynan shu sintaksis. U MediaQueryList obyektini qaytaradi. "Ro'yxat" so'zidan cho'chimang: bu bitta savolning javobi. Unda ikki asosiy xususiyat bor:
matches— shart hozir bajarilyaptimi. Oyna 40rem (640 px) dan keng bo'lsa —true.media— siz bergan savol, brauzer tushungan shaklda.
matchMedia — window ning metodi, xuddi setTimeout kabi. Shuning uchun window.matchMedia(...) ham, qisqa matchMedia(...) ham bir xil ishlaydi.
2.2 media nimani ko'rsatadi
Chrome 154 da sinab ko'rdik. matchMedia("(min-width:40rem)").media — "(min-width: 40rem)": brauzer bo'sh joyni o'zi qo'ydi. Savolni tushunmasa-chi? Qavslar unutilgan matchMedia("min-width: 600px") ning media si — "not all", matches esa doim false. Ya'ni "hech qaysi ekranga mos emas".
Bu muhim tuzoq. Xato sintaksis xato bermaydi. Kod jim ishlaydi, faqat shart hech qachon bajarilmaydi. Shubha tug'ilsa — media ni konsolga chiqaring. "not all" ko'rsangiz, qavslarni tekshiring.
Yana bir nozik joy: brauzer tanimaydigan xususiyat (masalan, (foo: bar)) "not all" ga aylanmaydi — media o'zgarmay qoladi, matches — false. Demak media ga qarab "brauzer bu xususiyatni biladimi?" deb xulosa chiqarib bo'lmaydi.
2.3 Zamonaviy diapazon sintaksisi
Foydalanuvchi afzalliklari darsidagi barcha media query'lar shu yerda ham ishlaydi, jumladan yangi diapazon yozuvi:
matchMedia("(width >= 40rem)").matches; // min-width bilan bir xil
matchMedia("(400px <= width <= 700px)").matches; // oraliq
matchMedia("(orientation: portrait)").matches; // tik ekran
matchMedia("(hover: hover) and (pointer: fine)").matches; // kursorOxirgisi qiziq: "sichqonchasi bor qurilmami?" — (hover: hover) (kursor elementlar ustida "suza" oladi) va (pointer: fine) (aniq ko'rsatkich). Telefon barmog'i — (pointer: coarse), ya'ni "qo'pol". Bu "telefonmi?" savoliga ekran kengligidan ko'ra to'g'riroq javob: katta planshet ham barmoq bilan boshqariladi.
Tekshirib ko'ring: Sardor
matchMedia("max-width: 600px")yozdi. Telefonda ham, kompyuterda hammatches—false. Nega? Qanday tekshirasiz?
Javob
Qavslar yo'q — brauzer savolni tushunmadi va uni "not all" ("hech qaysi ekran") deb qabul qildi. Xato chiqmaydi, shuning uchun sezish qiyin. console.log(mq.media) — not all ko'rinadi. To'g'risi: matchMedia("(max-width: 600px)").
3. change hodisasi: javob o'zgarganda
3.1 Bir marta tekshirish yetmaydi
matches — faqat hozirgi javob. Foydalanuvchi telefonni yotqizdi, oynani toraytirdi yoki tizimni qorong'i rejimga o'tkazdi — eski javob eskirdi. Yangi javobni bilish uchun MediaQueryList ning change hodisasini tinglaymiz:
const wide = matchMedia("(min-width: 40rem)");
wide.addEventListener("change", (event) => {
console.log(event.matches ? "keng ekran" : "tor ekran");
});Hodisa obyekti — MediaQueryListEvent. Unda ham matches (yangi javob) va media (savol) bor. event.matches va wide.matches — bir xil qiymat, qaysi biri qulay bo'lsa, shunisini o'qing.
3.2 resize bilan taqqoslash
Fokus, scroll va resize darsida resize ni debounce qilgan edik — u oyna har piksel o'zgarganda keladi. change esa faqat chegaradan o'tganda keladi. Ko'rib chiqing:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
#mode { font-size: 1.5rem; font-weight: 700; margin: 0; }
.wide #mode { color: #1f6f4a; }
</style>
<p>Ekran: <span id="mode">…</span></p>
<p>resize hodisalari: <b id="resizes">0</b>,
change hodisalari: <b id="changes">0</b></p>
<script>
const wide = matchMedia("(min-width: 40rem)");
const mode = document.getElementById("mode");
let resizes = 0;
let changes = 0;
function show(isWide) {
mode.textContent = isWide ? "keng (≥ 40rem)" : "tor (< 40rem)";
document.body.classList.toggle("wide", isWide);
}
addEventListener("resize", () => {
resizes++;
document.getElementById("resizes").textContent = resizes;
});
wide.addEventListener("change", (event) => {
changes++;
document.getElementById("changes").textContent = changes;
show(event.matches);
console.log(`change: ${event.matches ? "keng" : "tor"}`);
});
show(wide.matches); // boshlang'ich holat — bir marta qo'lda
</script>Oyna ostidagi 360, 768, 1280 tugmalarini bosing. Ekran 640 px chegarasidan o'tgandagina konsolga change: tor yoki change: keng chiqadi. Biz Chrome 154 da oyna kengligini 700 → 600 → 500 → 300 → 900 px qilib sinadik: resize — 5 marta, change — 2 marta (tor, keyin keng). 500 va 300 da chegara kesib o'tilmadi — hodisa ham yo'q.
Oxirgi qatorga e'tibor bering: show(wide.matches). change faqat o'zgarishda keladi, sahifa ochilganda emas. Boshlang'ich holatni o'zingiz bir marta qo'llashingiz kerak. Bu ko'p uchraydigan xato — "Ko'p uchraydigan xatolar" bo'limida yana bor.
Konsolga sahifa ochilganda hech narsa chiqmaydi — ataylab. Boshlang'ich javob ekraningizga bog'liq: telefonda bir xil, kompyuterda boshqa.
3.3 Eski usul: addListener
Eski kodda shunday yozuvni uchratasiz:
wide.addListener((event) => console.log(event.matches));addListener va removeListener — eskirgan (deprecated) metodlar. Ular hali ishlaydi (Chrome 154 da sinadik — change bilan birga chaqirildi), lekin yangi kodda addEventListener("change", ...) yozing. Eski metodlar 2020-yilgacha Safari uchun kerak edi: u MediaQueryList da addEventListener ni 14-versiyadan boshlab qo'llagan.
Tinglashni to'xtatish ham odatdagidek: removeEventListener("change", handler) yoki Listener opsiyalari darsidagi signal bilan.
Tekshirib ko'ring: Oyna 1000 px dan 300 px gacha sekin toraytirildi.
(min-width: 40rem)uchunchangenecha marta keladi?resizechi?
Javob
change — bir marta: 640 px chegarasidan o'tganda (matches true dan false ga). resize esa ko'p marta — brauzer oyna o'lchamini har yangilaganda. Shuning uchun "qaysi rejimdaman?" savoliga change tejamkor javob: debounce ham kerak emas.
4. Foydalanuvchi sozlamalari
4.1 Tizim nimani aytadi
Telefon va kompyuter sozlamalarida odam o'zi uchun muhim narsalarni tanlaydi: qorong'i rejim, harakatni kamaytirish, yuqori kontrast. Brauzer bularni sahifaga foydalanuvchi afzalliklari (user preferences) media xususiyatlari orqali aytadi. CSS'da ularni Foydalanuvchi afzalliklari darsida ishlatgansiz — JavaScript'da xuddi shu nomlar:
| Media xususiyat | Nima deydi |
|---|---|
(prefers-color-scheme: dark) |
tizim qorong'i rejimda |
(prefers-reduced-motion: reduce) |
harakatni kamaytirish yoqilgan |
(prefers-contrast: more) |
yuqori kontrast so'ralgan |
(forced-colors: active) |
tizim ranglarni o'zi majburlayapti (Windows kontrast temalari) |
Chrome 154 bularning hammasini tushunadi. Yana biri — (prefers-reduced-transparency: reduce), "shaffoflikni kamaytirish". Uni hamma brauzer ham bilmaydi: Chrome va Edge'da bor, Firefox va Safari'da hali yo'q (web-features, 2026-yil oktabr).
4.2 Sozlamani DevTools'da sinash
Har safar tizim sozlamasini o'zgartirish noqulay. Chrome DevTools buni o'zi "soxtalashtiradi": Ctrl + Shift + P → Rendering deb yozing → Emulate CSS media feature prefers-color-scheme ro'yxatidan dark ni tanlang. Sahifa darhol qorong'i rejimda deb hisoblaydi va change hodisasi ham keladi. Xuddi shu panelda prefers-reduced-motion, prefers-contrast va forced-colors ham bor.
Biz darsdagi misollarni aynan shu emulyatsiya orqali sinadik: prefers-color-scheme ni dark ga, keyin prefers-reduced-motion ni reduce ga o'tkazganda ikkala change tinglovchisi ham ishladi.
5. Tema: tizim, yorug' yoki qorong'i
5.1 Uch holat
Qorong'i rejim darsida «Bahor»ning CSS tomonini qurgan edik. Ranglar light-dark() bilan yozilgan, <html data-tema="qorongi"> esa tizimga qaramay qorong'i rejim beradi. <head> dagi kichik tema.js saqlangan tanlovni sahifa chizilishidan oldin qo'yadi. Footer'da esa uch radio tugma bor: Tizim, Yorug', Qorong'i — hidden holida. O'shanda "tugmalar kodini matchMedia darsida yozamiz" degan edik. Mana u dars.
Mantiq oddiy:
flowchart TD
A["Tanlov: tizim / yorug / qorongi"] --> B{"tizim?"}
B -- yo'q --> C["data-tema = tanlov"]
B -- ha --> D["data-tema yo'q"]
D --> E["CSS: prefers-color-scheme"]
C --> F["Ko'rinadigan tema"]
E --> F
F --> G["theme-color meta"]
H["Tizim o'zgardi: change"] --> FRanglarni CSS o'zi almashtiradi. JavaScript'ning ishi uchta: tanlovni saqlash, data-tema ni qo'yish yoki olib tashlash va ko'rinadigan temani hisoblab, telefon paneli rangini moslash. "Tizim" tanlangan bo'lsa, ko'rinadigan tema tizimga bog'liq — uni matchMedia aytadi.
5.2 Jonli misol
<style>
:root { color-scheme: light dark; }
:root[data-tema="yorug"] { color-scheme: light; }
:root[data-tema="qorongi"] { color-scheme: dark; }
body { font: 1rem/1.5 system-ui, sans-serif; margin: 0;
padding: 1rem; min-height: 9rem;
background: light-dark(#f7faf8, #14201a);
color: light-dark(#1f2a24, #e6efe9); }
fieldset { border: 1px solid light-dark(#c9d6cf, #3a4d43);
border-radius: 0.5rem; }
</style>
<fieldset id="theme-choice" hidden>
<legend>Tema</legend>
<input type="radio" name="tema" id="tema-tizim" value="tizim">
<label for="tema-tizim">Tizim</label>
<input type="radio" name="tema" id="tema-yorug" value="yorug">
<label for="tema-yorug">Yorug'</label>
<input type="radio" name="tema" id="tema-qorongi" value="qorongi">
<label for="tema-qorongi">Qorong'i</label>
</fieldset>
<p>Ko'rinadigan tema: <b id="visible">…</b></p>
<script>
const THEMES = ["tizim", "yorug", "qorongi"];
const group = document.getElementById("theme-choice");
const systemDark = matchMedia("(prefers-color-scheme: dark)");
const saved = localStorage.getItem("tema");
let choice = THEMES.includes(saved) ? saved : "tizim";
function visibleTheme() {
if (choice !== "tizim") return choice;
return systemDark.matches ? "qorongi" : "yorug";
}
function apply() {
const html = document.documentElement;
if (choice === "tizim") delete html.dataset.tema;
else html.dataset.tema = choice;
document.getElementById("visible").textContent = visibleTheme();
group.elements.namedItem(`tema-${choice}`).checked = true;
}
group.addEventListener("change", (event) => {
choice = event.target.value;
if (choice === "tizim") localStorage.removeItem("tema");
else localStorage.setItem("tema", choice);
apply();
console.log(`Tanlov: ${choice}, ko'rinadi: ${visibleTheme()}`);
});
systemDark.addEventListener("change", apply);
apply();
group.hidden = false;
</script>«Qorong'i» ni bosing — oyna qorong'i bo'ladi va konsolda Tanlov: qorongi, ko'rinadi: qorongi. «Yorug'» — Tanlov: yorug, ko'rinadi: yorug. «Tizim» — ko'rinadigan tema kompyuteringiz sozlamasiga qaytadi. Uchala tugmani bosib tekshirdik (tizim yorug' holatida): oxirgisi Tanlov: tizim, ko'rinadi: yorug berdi.
Natija oynasida localStorage haqiqiy emas — sayt uning o'rinbosarini beradi, tanlov shu blok uchun saqlanadi. «Qayta ishga tushirish» ni bosing: oxirgi tanlov joyida. Haqiqiy saytingizda — brauzerning o'z xotirasi.
5.3 Kod qatorma-qator
systemDark— bir marta yaratiladi va doim "tirik": uningmatchesi har o'qilganda tizimning hozirgi javobini beradi.THEMES.includes(saved)— xotiradagi qiymatga ishonmaymiz. Kimdir DevTools'da"dark"yozib qo'ygan bo'lishi mumkin — unda "tizim". Bu oq ro'yxat g'oyasi.visibleTheme()— tanlov "tizim" bo'lmasa, tanlovning o'zi. "Tizim" bo'lsa —matchMediajavobi.group.elements.namedItem(`tema-${choice}`)—fieldsetningelementsro'yxatidan maydonniid(yokiname) bo'yicha topadi.checked = true— saqlangan tanlovni ekranda ham belgilaydi."tizim"tanlansa, xotiradagi yozuv o'chiriladi,"tizim"deb yozilmaydi. Yozuv yo'q — tanlov yo'q — tizim ishlaydi.<head>dagitema.jsham shunday o'ylaydi.systemDark.addEventListener("change", apply)— "Tizim" tanlangan paytda kompyuter kechki rejimga o'tsa, sahifa ham darhol o'tadi. CSS buni o'zi qiladi, lekin#visiblematni va telefon paneli rangi — JavaScript ishi.group.hidden = false— eng oxirida. JavaScript ishlamasa, tugmalar yashirin qoladi: ishlamaydigan tugmani ko'rsatish mehmonni aldash bo'lardi.
5.4 Telefon paneli: theme-color
Qorong'i rejim darsidagi ogohlantirishni eslang: media li ikkita <meta name="theme-color"> faqat tizimga qaraydi. Tizimi yorug' mehmon «Qorong'i» ni tanlasa, sahifa qorong'i, panel esa oq qoladi. Yechim — bitta meta teg va uning content ini JavaScript yangilaydi:
const PANEL_COLORS = { yorug: "#f7faf8", qorongi: "#14201a" };
const panel = document.querySelector('meta[name="theme-color"]');
// apply() ichiga, ko'rinadigan tema hisoblangandan keyin:
panel.content = PANEL_COLORS[visibleTheme()];Rang — sahifa fonining o'zi: panel va sahifa bir butun bo'lib ko'rinadi. apply() tizim o'zgarishida ham chaqirilgani uchun panel "Tizim" rejimida ham to'g'ri qoladi.
5.5 «Bahor» va portfolio
«Bahor»ning assets/js/tema-tanlash.js fayli — yuqoridagi kodning o'zi, faqat radio tugmalar footer'da va theme-color qo'shilgan. Forma komponentlari darsida "tanlovni eslab qolish — tema-tanlash.js ishi" degan edik — endi siz uni o'qiy olasiz. Yakuniy landing loyihasidagi tugmali variant ham xuddi shu uch qadamni qiladi: guruhni ochadi, tanlovni belgilaydi, saqlaydi.
Portfolio'ngizdagi bitta "Qorong'i rejim" tugmasiga ham bitta tuzatish kerak. addEventListener asoslari darsida tugma aria-pressed="false" bilan boshlanardi. Tizimi qorong'i odam uchun bu yolg'on: sahifa allaqachon qorong'i. Boshlang'ich holatni tizimdan oling: aria-pressed = matchMedia("(prefers-color-scheme: dark)").matches, va tema:ozgardi hodisasi tizim change ida ham yuborilsin.
Tekshirib ko'ring: Mehmon «Yorug'» ni tanladi. Keyin kompyuteri kechki qorong'i rejimga o'tdi. Sahifa qaysi temada bo'ladi?
apply()chaqiriladimi?
Javob
Sahifa yorug' qoladi — qo'lda tanlov tizimdan ustun (data-tema="yorug" → color-scheme: light). apply() esa chaqiriladi, chunki change tinglovchisi doim ulangan. U hech narsani buzmaydi: visibleTheme() tanlovni qaytaradi va hamma narsa avvalgidek qoladi.
6. Harakatni kamaytirish
6.1 JavaScript animatsiyalari ham so'rashi kerak
prefers-reduced-motion darsida CSS animatsiyalarini o'chirishni o'rgandik. Lekin JavaScript animatsiyasini CSS to'xtata olmaydi: requestAnimationFrame sikli, karusel taymeri, scrollIntoView({ behavior: "smooth" }), video autoplay. Ular sozlamani o'zi so'rashi kerak.
requestAnimationFrame darsidagi buyurtmalar hisoblagichini eslang: raqam 1 soniyada 0 dan 1 284 gacha "yuguradi". O'shanda "harakatni kamaytirish yoqilgan odamga yakuniy raqamni darhol ko'rsatamiz" degan edik:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
.counter { font: 700 3rem/1.2 ui-monospace, monospace; margin: 0; }
button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<p>Bugun «Bahor»da buyurtmalar:</p>
<p class="counter" id="counter">0</p>
<button id="again" type="button">Qayta sanash</button>
<script>
const counter = document.getElementById("counter");
const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)");
const target = 1284;
const duration = 1000;
function formatNumber(n) {
return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, " ");
}
function start() {
if (reduceMotion.matches) {
counter.textContent = formatNumber(target); // darhol
console.log("Harakatsiz: 1 284");
return;
}
let startTime = null;
function step(time) {
startTime ??= time;
const progress = Math.min((time - startTime) / duration, 1);
const value = Math.round(progress * target);
counter.textContent = formatNumber(value);
if (progress < 1) requestAnimationFrame(step);
else console.log("Animatsiya bilan: 1 284");
}
requestAnimationFrame(step);
}
document.getElementById("again").addEventListener("click", start);
start();
</script>Konsolda:
Animatsiya bilan: 1 284Bu — sozlama o'chiq bo'lgan oddiy holat. Biz xuddi shu blokni prefers-reduced-motion: reduce emulyatsiyasi bilan ham ochdik: raqam darhol 1 284 bo'ldi va konsolda Harakatsiz: 1 284. O'zingiz DevTools → Rendering → Emulate CSS media feature prefers-reduced-motion → reduce ni tanlab, «Qayta sanash» ni bosing.
reduceMotion.matches har start() da qayta o'qiladi. Shuning uchun change tinglovchisi shart emas: keyingi bosishda yangi sozlama o'zi hisobga olinadi. Tinglovchi faqat ishlab turgan harakatni to'xtatish kerak bo'lganda kerak — keyingi bo'limdagi karusel kabi.
6.2 "Kamaytirish" — o'chirish degani emas
Sozlama "kamroq harakat" deydi, "hech qanday o'zgarish yo'q" emas. Hisoblagichda yakuniy raqam baribir ko'rinadi. Karusel to'xtaydi, lekin tugmalar bilan varaqlanadi. Silliq aylantirish o'rniga sahifa darhol sakraydi:
const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)");
section.scrollIntoView({
behavior: reduceMotion.matches ? "auto" : "smooth",
});Muhim ma'lumot harakat ichida bo'lmasin. Odam animatsiyani ko'rmasa ham, natijani ko'rishi kerak.
6.3 Ishlab turgan harakatni to'xtatish
Karusel har 5 soniyada aylanadi. Mehmon sahifa ochiq turganda sozlamani yoqdi. Endi change kerak:
const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)");
let timerId = null;
function updateAutoplay() {
clearInterval(timerId);
if (!reduceMotion.matches) {
timerId = setInterval(nextSlide, 5000);
}
}
reduceMotion.addEventListener("change", updateAutoplay);
updateAutoplay(); // boshlang'ich holatIkki narsaga qarang. updateAutoplay ham boshida, ham har o'zgarishda chaqiriladi — bitta funksiya, ikki chaqiruv. Va u avval har doim eski taymerni to'xtatadi — ikki taymer parallel aylanib qolmaydi. prefers-reduced-motion darsidagi autoplay li fon videosi ham shunday to'xtatiladi: video.pause() va play().
Tekshirib ko'ring: Hisoblagichda
changetinglovchisi yo'q, karuselda bor. Nega?
Javob
Hisoblagich bir martalik: sozlama har start() da qayta o'qiladi, keyingi bosishda yangi qiymat ishlaydi. Karusel esa to'xtovsiz ishlaydi: sozlama yoqilgan paytda u allaqachon aylanyapti. Uni to'xtatish uchun o'zgarish paytini bilish kerak — change.
7. JavaScript yoki CSS?
Qoida ResizeObserver darsidagidek: ko'rinish — CSS'da, qaror — JavaScript'da.
| Vazifa | Qayerda |
|---|---|
| Rang, o'lcham, joylashuv ekranga qarab | CSS @media |
| Qorong'i rejim ranglari | CSS light-dark() |
| Taymer, rAF, karuselni to'xtatish | JS matchMedia |
theme-color, canvas rangi, JS holati |
JS matchMedia |
JavaScript'dagi media query CSS'dagini takrorlasa, 40rem soni ikki joyda yoziladi. Biri o'zgarsa, ikkinchisi unutiladi — CSS "tor" desa, JavaScript "keng" deydi. Raqamni JavaScript'da bitta o'zgarmasda saqlang (const WIDE_QUERY = "(min-width: 40rem)") va CSS'dagisi bilan bir xilligini izohda yozing.
8. Node'da yo'q
matchMedia — brauzerning ekrani bilan bog'liq, Node'da ekran yo'q:
const wide = matchMedia("(min-width: 40rem)");Node 24 da:
ReferenceError: matchMedia is not definedTarjimasi: "matchMedia aniqlanmagan". Shuning uchun vazifalar dagi tema kodi ikki qismga bo'lingan: sof funksiyalar (Node'da testlanadi) va DOM'ga tegadigan qism (faqat brauzerda) — "Vazifalar qadami" da ko'rasiz.
9. Ko'p uchraydigan xatolar
9.1 Faqat change ga tayanish
// ❌ sahifa ochilganda hech narsa bo'lmaydi
wide.addEventListener("change", (e) => show(e.matches));change — faqat o'zgarishda. Sahifa keng ekranda ochilib, hech qachon toraytirilmasa — show hech qachon chaqirilmaydi. Tuzatish: tinglovchidan keyin bir marta show(wide.matches).
9.2 Qavssiz savol
matchMedia("min-width: 600px") — xato yo'q, media — "not all", matches doim false. Tuzatish: xususiyat doim qavs ichida: "(min-width: 600px)".
9.3 resize + innerWidth
// ❌ har piksel o'zgarishida ishlaydi
addEventListener("resize", () => {
sidebar.hidden = innerWidth < 640;
});Ishlaydi, lekin keraksiz tez-tez. Ustiga, 640 piksel CSS'dagi 40rem bilan har doim teng emas: foydalanuvchi brauzer shriftini kattalashtirsa, rem ham kattalashadi. Tuzatish: matchMedia("(min-width: 40rem)") — CSS bilan aynan bir xil o'lchov.
9.4 Faqat tizimga qarash
prefers-color-scheme — tizim sozlamasi. Saytda qo'lda tanlov bo'lsa, faqat matchMedia ga qarab rang tanlagan kod (canvas, theme-color) qo'lda tanlovni e'tiborsiz qoldiradi. Tuzatish: "ko'rinadigan tema" ni bitta funksiyada hisoblang — visibleTheme() kabi — va hamma joyda shuni ishlating.
9.5 Sozlamani "majburlash"
Harakatni kamaytirgan odamga "Animatsiyani yoqing" degan xabar chiqarish yoki sozlamani e'tiborsiz qoldirish — yo'q. Bu odam uchun bosh aylanishi va ko'ngil aynishi bo'lishi mumkin (harakat etikasi). Sozlama — foydalanuvchining qarori.
10. Mashqlar
1-mashq (oson): Sichqoncha yoki barmoq
Sahifada «Bahor» menyusi uchun maslahat chiqsin: sichqonchali qurilmada Taom ustiga olib boring — tarkibi chiqadi, barmoqli qurilmada Taomni bosing — tarkibi chiqadi. Savol: (hover: hover) and (pointer: fine) («Zamonaviy diapazon sintaksisi» bo'limi). Matnni #hint ga yozing.
Yechim
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
</style>
<p id="hint">…</p>
<script>
const mouse = matchMedia("(hover: hover) and (pointer: fine)");
const hint = document.getElementById("hint");
function showHint() {
hint.textContent = mouse.matches
? "Taom ustiga olib boring — tarkibi chiqadi"
: "Taomni bosing — tarkibi chiqadi";
}
mouse.addEventListener("change", showHint);
showHint();
</script>Kompyuterda birinchi matn, telefonda ikkinchisi. DevTools'ning qurilma rejimida (Ctrl + Shift + M) telefonni tanlab sahifani yangilang — matn almashadi. change tinglovchisi ham bor: planshetga sichqoncha ulansa, matn o'zi yangilanadi.
2-mashq (o'rta): Yumshoq aylantirish tugmasi
«Bahor» menyu sahifasida «Bronga o'tish» tugmasi pastdagi #bron bo'limiga aylantiradi. scrollIntoView ga behavior ni sozlamaga qarab bering ("Kamaytirish — o'chirish degani emas" bo'limi). Har bosishda konsolga qaysi usul ishlatilganini chiqaring: aylantirish: smooth yoki aylantirish: auto.
Yechim
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
.gap { height: 900px;
background: linear-gradient(#f7faf8, #e8f1ec); }
button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<button id="go" type="button">Bronga o'tish</button>
<div class="gap"></div>
<h2 id="bron">Stol bron qilish</h2>
<script>
const reduceMotion = matchMedia("(prefers-reduced-motion: reduce)");
document.getElementById("go").addEventListener("click", () => {
const behavior = reduceMotion.matches ? "auto" : "smooth";
document.getElementById("bron").scrollIntoView({ behavior });
console.log(`aylantirish: ${behavior}`);
});
</script>Tugmani bosing — konsolda aylantirish: smooth, sahifa silliq pastga tushadi (bosib tekshirdik). DevTools'da reduce ni yoqing va yana bosing — aylantirish: auto, sahifa darhol sakraydi. Sozlama har bosishda qayta o'qiladi, shuning uchun change kerak emas.
3-mashq (qiyin): watchMedia yordamchisi
Har safar ikki qator yozamiz: addEventListener("change", ...) va boshlang'ich chaqiruv. Bitta yordamchi yozing: watchMedia(query, callback) — callback(matches) ni darhol va har o'zgarishda chaqirsin, va tinglashni to'xtatadigan funksiya qaytarsin. Sinov: (min-width: 40rem) ni kuzating, natijani #state ga yozing, «To'xtatish» tugmasi kuzatuvni to'xtatsin va konsolga kuzatuv to'xtadi desin.
Yechim
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; padding: 0.4rem 0.8rem; }
</style>
<p>Holat: <b id="state">…</b></p>
<button id="stop" type="button">To'xtatish</button>
<script>
function watchMedia(query, callback) {
const mq = matchMedia(query);
const handler = (event) => callback(event.matches);
mq.addEventListener("change", handler);
callback(mq.matches); // boshlang'ich holat
return () => mq.removeEventListener("change", handler);
}
const state = document.getElementById("state");
const stop = watchMedia("(min-width: 40rem)", (isWide) => {
state.textContent = isWide ? "keng" : "tor";
});
document.getElementById("stop").addEventListener("click", () => {
stop();
console.log("kuzatuv to'xtadi");
});
</script>360 va 1280 tugmalari bilan oynani almashtiring — holat o'zgaradi. «To'xtatish» dan keyin almashtirsangiz — holat qotib qoladi. Biz tekshirdik: to'xtatishdan oldin 360 px da tor, to'xtatgandan keyin 1280 px da ham tor qoldi.
handler alohida o'zgaruvchida — removeEventListener ga aynan o'sha funksiya kerak. React'dagi "media query hook'lari" ham ichida shuni qiladi (React — 17-qismda).
4-mashq: Vazifalar qadami — tema tanlovi
vazifalar ga 13-qismning ikkinchi qadami: uch holatli tema (tizim, yorug', qorong'i). Bu «Bahor»da qurgan naqshning o'zi, faqat modul ko'rinishida va testlar bilan. Qo'shimcha — prefers-reduced-motion CSS'da hurmat qilinadi.
1. Branch:
git switch -c feature/tema2. index.html, <head> da — ikki meta va oddiy skript (CSS'dan oldin):
<meta name="color-scheme" content="light dark">
<meta name="theme-color" content="#f7faf8">
<script src="assets/js/tema.js"></script>
<link rel="stylesheet" href="assets/css/vazifalar.css"><body> da, import formasi ostida — «Bahor»dagi radio guruh, faqat id tema-tanlov va legend Tema. modulepreload ro'yxatiga tema-tanlash.js qo'shiladi.
3. assets/js/tema.js — chizishdan oldin ishlaydigan oddiy skript:
try {
const tema = localStorage.getItem("vazifalar:tema");
if (tema === "yorug" || tema === "qorongi") {
document.documentElement.dataset.tema = tema;
}
} catch {
// localStorage yopiq — tizim rejimi qoladi
}U modul emas: modul kechiktiriladi, bu skript esa sahifa chizilishidan oldin ishlashi shart — aks holda sahifa bir lahza tizim rangida "chaqnaydi" (Qorong'i rejim darsidagi FOUC). Shu sababli u saqlash.js ni import qila olmaydi. Bu loyihadagi yagona joy: localStorage to'g'ridan-to'g'ri o'qiladi (kanonda bu istisno TEXNIK-QARZ ga yozilgan).
4. saqlash.js — kalit "vazifalar:tema":
// "tizim" — yozuv o'chadi: tanlov yo'q, tizim sozlamasi ishlaydi
export function temaniSaqla(tema) {
try {
if (tema === "tizim") {
localStorage.removeItem(TEMA_KALITI);
} else {
localStorage.setItem(TEMA_KALITI, tema);
}
} catch (xato) {
console.warn(`Tema saqlanmadi: ${xato.name}`);
}
}
export function temaniOl() {
try {
return localStorage.getItem(TEMA_KALITI);
} catch {
return null;
}
}5. Yangi modul assets/js/tema-tanlash.js — ikki qism. Sof funksiyalar (DOM'siz, Node'da testlanadi):
export const TEMALAR = Object.freeze(["tizim", "yorug", "qorongi"]);
// Xotiradagi qiymat qo'lda o'zgartirilgan bo'lishi mumkin
export function tanlovniOl(qiymat) {
return TEMALAR.includes(qiymat) ? qiymat : "tizim";
}
// Ekranda qaysi tema ko'rinadi: "tizim" — tizim sozlamasiga qarab
export function korinadiganTema(tanlov, tizimQorongimi) {
if (tanlov !== "tizim") {
return tanlov;
}
return tizimQorongimi ? "qorongi" : "yorug";
}Va DOM'ga tegadigan temaniUla() — u import paytida emas, chaqirilganda ishlaydi. Shuning uchun Node'dagi test faylni import qilganda matchMedia is not defined xatosi chiqmaydi:
export function temaniUla() {
const guruh = document.getElementById("tema-tanlov");
const panel = document.querySelector('meta[name="theme-color"]');
const tizimQorongi = matchMedia("(prefers-color-scheme: dark)");
let tanlov = tanlovniOl(temaniOl());
function qolla() {
const html = document.documentElement;
if (tanlov === "tizim") {
delete html.dataset.tema;
} else {
html.dataset.tema = tanlov;
}
const tema = korinadiganTema(tanlov, tizimQorongi.matches);
panel.content = PANEL_RANGLARI[tema];
guruh.elements.namedItem(`tema-${tanlov}`).checked = true;
}
guruh.addEventListener("change", (event) => {
tanlov = tanlovniOl(event.target.value);
temaniSaqla(tanlov);
qolla();
});
// "Tizim" tanlanganda tizim o'zgarishi jonli kuzatiladi
tizimQorongi.addEventListener("change", qolla);
qolla();
guruh.hidden = false; // JS ishlamasa — tugmalar ko'rinmaydi
}Jonli misoldagi apply() bilan solishtiring: qolla — o'sha funksiya. Farqi — korinadiganTema alohida, sof funksiya bo'lib chiqdi: uni ikki argument bilan testlash mumkin. PANEL_RANGLARI — { yorug: "#f7faf8", qorongi: "#14201a" }, ilova fonining ikki rangi.
6. asosiy.js — ikki qator: import { temaniUla } from "./tema-tanlash.js"; va ishga tushirishda, render() dan oldin, temaniUla();.
7. vazifalar.css — ranglar light-dark() bilan, @supports ichida. Bu qoidani bilmaydigan eski brauzer ichkariga kirmaydi va yuqoridagi oddiy yorug' ranglarda qoladi:
@supports (color: light-dark(white, black)) {
:root {
color-scheme: light dark;
--rang-fon: light-dark(#f7faf8, #14201a);
--rang-matn: light-dark(#1f2a24, #e6efe9);
/* … qolgan tokenlar ham shunday */
}
:root[data-tema="yorug"] { color-scheme: light; }
:root[data-tema="qorongi"] { color-scheme: dark; }
}Ikki yangi token — --rang-yuza (tugma foni) va --rang-asosiy-ustida (yashil tugmadagi matn): oldin u yerda #fff qattiq yozilgan edi va qorong'i rejimda oq dog' bo'lib qolardi. body ga transition: background-color 0.2s, color 0.2s — tema yumshoq almashadi. Va oxirida:
/* Harakatni kamaytirish so'ralgan: o'tishlar deyarli darhol */
@media (prefers-reduced-motion: reduce) {
*, ::before, ::after {
transition-duration: 0.01ms !important;
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
}
}Nega 0 emas, 0.01ms? Nol davomiylikda o'tish umuman boshlanmaydi va transitionend hodisasi kelmaydi. Unga tayanadigan kod "o'tish tugashini" abadiy kutib qoladi. Chrome 154 da sinadik: 0s li elementda hodisa yo'q, 0.01ms lisida — bor. Qiymat ko'zga sezilmaydi, hodisa esa keladi.
8. Testlar. tekshiruv/tema.test.js — 4 ta: tanlovniOl (faqat uch qiymat, "dark" → "tizim"), korinadiganTema ikki holatda va temaniSaqla ("tizim" yozuvni o'chiradi). Natija — 70/70; lint, format:check, tip — toza.
9. Brauzerda (kanon tekshiruvi, 60/60 ning bir qismi):
✅ tema: tizim qorong'i — fon (CSS): "rgb(20, 32, 26)"
✅ tema: tizim qorong'i — panel (change hodisasi): "#14201a"
✅ tema: strelka → Yorug': ["tema-yorug","yorug","yorug"]
✅ tema: qayta ochilishda modul kelguncha qo'yilgan: "qorongi"
✅ tema: tizim — yozuv o'chdi: [null,false]
✅ tema: reduced-motion — o'tish yo'q: "1e-05s"Ikkinchi qatorga qarang: tizim emulyatsiyada qorong'iga o'tganda panel rangi change hodisasi orqali yangilandi. Uchinchisi — klaviatura: radio guruhda strelka tanlovni almashtiradi (Bobur uchun). To'rtinchisi — tema.js ishi: modul hali yuklanmagan paytda data-tema allaqachon qo'yilgan. Oxirgisi — 0.01ms ning brauzer ko'rsatadigan shakli.
O'zingiz: Live Server'da oching, DevTools → Rendering'da prefers-color-scheme: dark ni yoqing — «Tizim» tanlangan ilova qorong'iga o'tadi. «Yorug'» ni tanlang va emulyatsiyani almashtiring — ilova yorug' qoladi.
10. Commit va PR. To'liq xabar 107 belgi — Git sarlavhasi uchun uzun (≤ 72). Shuning uchun ikkiga bo'lamiz — sarlavha va tana:
git add index.html assets/js/tema.js assets/js/tema-tanlash.js \
assets/js/saqlash.js assets/js/asosiy.js \
assets/css/vazifalar.css tekshiruv/tema.test.js
git commit \
-m "feat: tizim/yorug'/qorong'i tema — matchMedia, prefers-color-scheme" \
-m "prefers-reduced-motion hurmat qilinadi"
git push -u origin feature/tema
gh pr create --fill
gh pr merge --mergeDiff: 7 fayl, +180 −3.
11. Real ishda
- Tema tanlovi — GitHub, YouTube, Telegram Web, VS Code: deyarli hammasida "Tizim / Yorug' / Qorong'i" uchligi. Next.js (React ustiga qurilgan freymvork, kursda keyinroq) loyihalarida
next-themeskutubxonasi aynan shu mantiqni (saqlash, FOUC skripti,matchMedia) tayyor beradi. - Animatsiya kutubxonalari — GSAP'ning
gsap.matchMedia(), Motion (avvalgi nomi Framer Motion) kutubxonasininguseReducedMotioni — ichida shumatchMedia. - Moslashuvchan komponentlar — "telefonda pastki panel, kompyuterda yon menyu" holati JavaScript'da kerak bo'lganda. React'da bunday hook'lar keng tarqalgan (React — 17-qismda).
- Qulaylik (accessibility) auditi — qo'lda tekshiruvda "harakatni kamaytirish hurmat qilinadimi?" degan band bor (WCAG 2.3.3 mezoni). Lighthouse buni avtomatik tekshirmaydi — DevTools emulyatsiyasi bilan o'zingiz sinaysiz.
- Intervyu. "
resizevamatchMediafarqi?", "Qorong'i rejimni qanday qilasiz, FOUC'dan qanday qochasiz?" — frontend intervyularida tez-tez.
Xulosa
matchMedia(query)→MediaQueryList:matches— hozirgi javob,media— tushunilgan savol ("not all"— sintaksis xato).changefaqat javob almashganda keladi —resizedan tejamkor; boshlang'ich holatni bir marta qo'lda qo'llang.prefers-color-scheme,prefers-reduced-motion,prefers-contrast,forced-colors— foydalanuvchi sozlamalari; DevTools Rendering bilan sinanadi.- Tema: tanlov saqlanadi, "tizim" — yozuv yo'q; ko'rinadigan tema bitta funksiyada;
theme-colorJavaScript'dan. - JavaScript animatsiyasi sozlamani o'zi so'raydi; ishlab turgan harakat
changeda to'xtatiladi.
Keyingi dars: Clipboard va Web Share API — «Nusxalash» tugmasi va telefonning "Ulashish" oynasi bir necha qatorda.
Manbalar
- CSSWG: "CSSOM View Module" —
matchMedia,MediaQueryList— drafts.csswg.org/cssom-view - CSSWG: "Media Queries Level 5" — user preference media features — drafts.csswg.org/mediaqueries-5
- MDN:
Window.matchMedia(),MediaQueryList,prefers-reduced-motion— developer.mozilla.org - web.dev: "prefers-reduced-motion: Sometimes less movement is more" — web.dev
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!