Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. O'z ajratkichingiz
- 2.1 WAI-ARIA Window Splitter
- 2.2 Jonli misol
- 2.3 Layoutni saqlash
- 3. react-resizable-panels
- 3.1 v4 API
- 3.2 Eski kodda ko'rsangiz: v3
- 3.3 Biz sinab ko'rdik
- 4. Masonry
- 4.1 CSS yo'li va uning chegarasi
- 4.2 JavaScript yo'li: eng qisqa ustunga
- 5. ResizeObserver React'da
- 5.1 Nega kerak
- 5.2 useMeasure hook
- 5.3 Telefonda: split view emas, ikki sahifa
- 5.4 Sticky
- 6. Tajriba: nega vazifalar ga kirmadi
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): ajratkichning ARIA'si
- 2-mashq (o'rta): aria-valuetext va chegaralar
- 3-mashq (qiyin): yig'iladigan panel
- 9. Real ishda
- Xulosa
- Manbalar
Resizable panellar, split view va masonry React'da: ajratkich, layoutni saqlash va ResizeObserver
Qisqacha: Split view — ekran ikki-uch panelga bo'linadi, ular orasidagi ajratkichni sudrab o'lchamni o'zgartirasiz (pochta: ro'yxat | xat). A11y uchun ajratkich —
role="separator", fokus oladi,aria-valuenowbilan foizni aytadi va strelkalar bilan suriladi (WAI-ARIA Window Splitter). Tayyor yo'l —react-resizable-panels(v4:Group,Panel,Separator; v3 dagiPanelGroup— eski kodda), layoutuseDefaultLayoutbilan saqlanadi. Masonry — har xil balandlikdagi kartalar ustunlarda zich terilgan to'r: CSScolumns(o'qish tartibi ustun bo'yicha) yoki JavaScript (masonic— qisqa ustunga qo'yish, tartib qator bo'yicha). O'lchamni kuzatish —ResizeObserver. Kanon ularning hech birini olmadi:vazifalar— 36rem bitta ustun, panellar +13.1 KB bo'lardi, foydasi yo'q.
Bu darsda
- WAI-ARIA naqshi bo'yicha o'z ajratkichingizni yozasiz: sichqoncha va klaviatura bilan.
- Panel o'lchamini
localStorageda saqlab, qayta ochilganda tiklaysiz. react-resizable-panelsv4 API'sini o'qiysiz va v3 kodini taniysiz.- Masonry'ning ikki yo'lini (CSS
columnsva JavaScript) o'qish tartibi bilan solishtirasiz. ResizeObserverbilan element enini React'da kuzatasiz.- Bu vositalar nega
vazifalarga kirmaganini o'lchov bilan tushuntirasiz.
Oldin bilishingiz kerak: Drag and drop: dnd-kit, ResizeObserver va MutationObserver, Layout naqshlari: masonry, Container queries, Fokus boshqaruvi.
1. Nega bu kerak?
O'tgan darsda qatorlarni sudrab tartibladik. Sudrashning yana bir turi bor — o'lchamni sudrash.
«Bahor» admin panelining kompyuter versiyasi uchun Jasur aka shunday dedi: "Chapda buyurtmalar ro'yxati, o'ngda tanlangan buyurtma tafsiloti. Ba'zan ro'yxatni keng ko'rmoqchiman, ba'zan tafsilotni — o'rtadagi chiziqni o'zim suray." Bu — split view (bo'lingan ko'rinish): VS Code, Telegram Desktop, pochta dasturlari aynan shunday.
Ikkinchi so'rov — "Mijozlar fikrlari" sahifasi. Fikrlarning ba'zisi bir qator, ba'zisi o'n qator. Oddiy to'rda (grid) qisqa kartalar ostida bo'sh joy qoladi. Pinterest kabi zich terish kerak — masonry.
Hayotiy o'xshatish: kitob javoni va g'isht devor. Javon ichidagi to'siqni surib, bir bo'limni kengaytirib, ikkinchisini toraytirasiz — bu split view. G'isht teruvchi esa har g'ishtni eng past joyga qo'yadi, shunda devor tekis o'sadi — bu masonry ("mason" — inglizcha g'isht teruvchi).
2. O'z ajratkichingiz
2.1 WAI-ARIA Window Splitter
Ajratkich ko'rinishda — oddiy chiziq. Lekin Bobur uni Tab bilan topishi, Malika esa "panel o'lchami, 40 foiz" ni eshitishi kerak. WAI-ARIA APG'da buning tayyor naqshi bor — Window Splitter:
| Atribut / tugma | Ma'nosi |
|---|---|
role="separator" + tabIndex={0} |
fokus oladigan ajratkich ("bo'luvchi") |
aria-valuenow, aria-valuemin, aria-valuemax |
asosiy panelning hozirgi o'lchami va chegaralari (odatda 0–100) |
aria-controls |
qaysi panel o'zgaradi |
aria-orientation="vertical" |
ajratkich tik (panellar yonma-yon) |
| ← / → | ajratkichni surish |
| Home / End | eng kichik / eng katta o'lcham |
| Enter | asosiy panelni yig'ish yoki qaytarish |
Nima uchun aria-orientation="vertical", panellar esa gorizontal joylashgan? Chunki ajratkich chizig'i tik. ARIA chiziqning yo'nalishini so'raydi, uning harakat yo'nalishini emas.
2.2 Jonli misol
Chap panel o'lchami — bitta state, foizda. Klaviatura va sichqoncha ikkalasi ham shu sonni o'zgartiradi. «Ishga tushir» dan keyin Tab bilan ajratkichga keling va strelkalarni bosing; sichqoncha bilan sudrab ham ko'ring:
import { useEffect, useRef, useState } from "react";
const MIN = 20;
const MAX = 80;
const STEP = 5;
const clamp = (v) => Math.min(MAX, Math.max(MIN, Math.round(v)));
export default function App() {
const [left, setLeft] = useState(40); // chap panel, foiz
const box = useRef(null);
useEffect(() => {
console.log(`chap panel: ${left}%`);
}, [left]);
function onKeyDown(e) {
const keys = {
ArrowLeft: left - STEP,
ArrowRight: left + STEP,
Home: MIN,
End: MAX,
};
if (!(e.key in keys)) return;
e.preventDefault(); // sahifa aylanmasin
setLeft(clamp(keys[e.key]));
}
function onPointerDown(e) {
const handle = e.currentTarget;
handle.setPointerCapture(e.pointerId);
const rect = box.current.getBoundingClientRect();
const move = (ev) =>
setLeft(clamp(((ev.clientX - rect.left) / rect.width) * 100));
const up = () => {
handle.removeEventListener("pointermove", move);
handle.removeEventListener("pointerup", up);
};
handle.addEventListener("pointermove", move);
handle.addEventListener("pointerup", up);
}
return (
<div ref={box} style={{ display: "flex", height: 120 }}>
<section id="orders" style={{ flexBasis: `${left}%` }}>
Buyurtmalar
</section>
<div
role="separator"
tabIndex={0}
aria-label="Panel o'lchami"
aria-controls="orders"
aria-orientation="vertical"
aria-valuenow={left}
aria-valuemin={MIN}
aria-valuemax={MAX}
onKeyDown={onKeyDown}
onPointerDown={onPointerDown}
style={{
width: 8,
cursor: "col-resize",
touchAction: "none",
background: "#99a",
}}
/>
<section style={{ flex: 1 }}>Tafsilot</section>
</div>
);
}Konsolda:
chap panel: 40%
chap panel: 45%
chap panel: 50%
chap panel: 45%Ekranda:
Buyurtmalar
TafsilotKlaviatura yo'li hamma joyda bir xil ishlaydi — u faqat sonni o'zgartiradi. Sichqoncha yo'li esa konteynerning haqiqiy enini o'lchaydi (getBoundingClientRect) — u faqat brauzerda ishlaydi. Biz Chrome 155 da ajratkichni sichqoncha bilan 120 px o'ngga surdik (konteyner eni 584 px). Konsolda:
chap panel: 40%
chap panel: 43%
chap panel: 45%
…
chap panel: 59%
chap panel: 61%Sichqoncha 10 qadamda harakatlandi — har pointermove da yangi qiymat (o'rtadagi qatorlar qisqartirildi). Hisob: 120 ÷ 584 ≈ 20.5%, ya'ni 40 + 20.5 ≈ 61. aria-valuenow ham 61 bo'ldi.
Kodning to'rt muhim joyi:
e.preventDefault()— strelkalar odatda sahifani aylantiradi. Ajratkich fokusda bo'lsa, ular faqat panelni o'zgartirsin.clamp— 20% dan kichik, 80% dan katta bo'lmasin.Math.round—aria-valuenowbutun son bo'lsin: Malika "43,7" emas, "44" ni eshitsin.setPointerCapture— sichqoncha ajratkichdan chiqib ketsa ham harakat unga keladi (Barmoq bilan surish darsidagi usul).touch-action: none— telefonda barmoq bilan surish sahifani aylantirmasin.flexBasisfoizda — o'lcham konteyner eniga nisbatan. Oyna kengaysa, panellar nisbati saqlanadi.
Tekshirib ko'ring: Ajratkichni
<div>emas,<button>qilsak bo'lmaydimi? U ham fokus oladi.
Javob
<button> ning roli — tugma: Malika "Panel o'lchami, tugma" ni eshitadi va uni bosishni kutadi. Lekin ajratkich bosilmaydi — u qiymatga ega (40 foiz) va strelkalar bilan o'zgaradi. role="separator" (fokus oladigan, aria-valuenow li) aynan shu ma'noni beradi: "o'lchami o'zgaradigan bo'luvchi, hozir 40". Ekran o'quvchi ham strelkalar unga tegishli ekanini biladi.
2.3 Layoutni saqlash
Jasur aka ro'yxatni 60% ga kengaytirdi, sahifani yangiladi — yana 40%. Foydalanuvchi tanlagan layout saqlanishi kerak. Bu shaxsiy qulaylik — localStorage yetadi (localStorage va sessionStorage darsi). Server va URL — ortiqcha: layout ulashilmaydi.
Natija oynasida localStorage bor (sahifa yangilanganda tozalanadi). Panelni yopib-ochish bilan tekshiramiz:
import { useEffect, useState } from "react";
const KEY = "bahor:panel:orders";
function readSize() {
const saved = Number(localStorage.getItem(KEY));
// noto'g'ri yoki bo'sh qiymat — standart
return saved >= 20 && saved <= 80 ? saved : 40;
}
function SplitView() {
const [left, setLeft] = useState(readSize);
useEffect(() => {
console.log("tiklandi:", readSize());
}, []);
useEffect(() => {
localStorage.setItem(KEY, String(left));
}, [left]);
return (
<div>
<p>Buyurtmalar: {left}%</p>
<button onClick={() => setLeft(Math.min(80, left + 20))}>
Kengaytirish
</button>
</div>
);
}
export default function App() {
const [open, setOpen] = useState(true);
return (
<div>
<button onClick={() => setOpen(!open)}>
{open ? "Yopish" : "Ochish"}
</button>
{open && <SplitView />}
</div>
);
}Konsolda:
tiklandi: 40
tiklandi: 60Ekranda:
Yopish
Buyurtmalar: 60%
KengaytirishPanel yopilib qayta ochilganda (komponent yangidan yaratildi) o'lcham localStorage dan tiklandi — 60%. Uch nozik joy:
useState(readSize)— funksiyaning o'zi beriladi, chaqiruvi emas (useState darsidagi dangasa boshlang'ich qiymat):localStoragefaqat birinchi renderda o'qiladi.- Tekshirish — saqlangan qiymat buzilgan bo'lishi mumkin (eski versiya, qo'lda tahrir). Chegaradan tashqarida — standart.
- Kalitda ilova nomi —
bahor:panel:orders. Bir domendagi boshqa sahifalar bilan to'qnashmasin.
3. react-resizable-panels
3.1 v4 API
O'z ajratkichingiz ikki panel uchun yetadi. Uch-to'rt panel, ichma-ich guruhlar (chapda ro'yxat, o'ngda tepa-past bo'lingan tafsilot), yig'iladigan panel, minimal o'lcham pikselda — bularning hammasi react-resizable-panels da tayyor. U shadcn/ui'ning Resizable komponenti asosi.
Kanon tajribasidagi versiya — 4.14.3. 4-versiya (2025-yil dekabr) nomlarni o'zgartirgan:
import {
Group,
Panel,
Separator,
useDefaultLayout,
} from "react-resizable-panels";
function OrdersScreen() {
// layoutni localStorage'da saqlash va tiklash
const layout = useDefaultLayout({ id: "bahor-orders" });
return (
<Group
orientation="horizontal"
defaultLayout={layout.defaultLayout}
onLayoutChanged={layout.onLayoutChanged}
>
<Panel id="list" defaultSize="40" minSize="20" collapsible>
<OrderList />
</Panel>
<Separator />
<Panel id="details" minSize={240}>
<OrderDetails />
</Panel>
</Group>
);
}O'qiymiz:
Group— panellar guruhi,orientation— yonma-yon ("horizontal") yoki ustma-ust.Panel— o'lcham qoidalari:defaultSize,minSize,maxSize,collapsible. Birlikka e'tibor bering: son — piksel (minSize={240}), birliksiz satr — foiz ("40"), boshqa birlik aniq yoziladi ("20rem").Separator— ajratkich. Hujjat uni "majburiy emas, lekin tavsiya" deydi: klaviatura a11y'si aynan undan keladi (biz yozganrole="separator"va ARIA xususiyatlari). Ajratkich ikki marta bosilsa, panel standart o'lchamiga qaytadi.useDefaultLayout({ id })— layoutnilocalStorageda saqlaydi va tiklaydi.onLayoutChangedsudrash tugagach chaqiriladi — har piksel harakatda saqlamaydi.
Panelni koddan boshqarish uchun panelRef bor: collapse(), expand(), resize(size), isCollapsed().
3.2 Eski kodda ko'rsangiz: v3
Internetdagi ko'p misollar (va shadcn'ning eski Resizable i) 3-versiyada:
| v3 (eski) | v4 (yangi) |
|---|---|
PanelGroup direction="horizontal" |
Group orientation="horizontal" |
PanelResizeHandle |
Separator |
autoSaveId="…" |
useDefaultLayout({ id: "…" }) |
defaultSize={40} — foiz |
defaultSize="40" — foiz, {40} — piksel |
Oxirgi qator — eng xavfli tuzoq: v3 dagi defaultSize={40} v4 da 40 piksel bo'lib qoladi. Kod xatosiz ishlaydi, lekin panel juda tor.
3.3 Biz sinab ko'rdik
Kanon tajribasi (tajriba/kitlar/src/k/panel.tsx, kanondan aynan, qatorlar o'qish uchun bo'lingan):
// #18 react-resizable-panels 4.14.3: ro'yxat | tafsilot
import { Group, Panel, Separator } from "react-resizable-panels";
import { createRoot } from "react-dom/client";
createRoot(document.getElementById("ilova")!).render(
<main>
<h1>Panel</h1>
<Group orientation="horizontal" style={{ height: 300 }}>
<Panel defaultSize={40}>Royxat</Panel>
<Separator />
<Panel>Tafsilot</Panel>
</Group>
</main>,
);Natija (natijalar/tajriba/kutubxonalar.txt, aynan):
asos JS 67719 (+ 0.0 KB) CSS 0 | CSP 0 | xato: yo'q
panel JS 81136 (+ 13.1 KB) CSS 0 | CSP 0 | xato: yo'q+13.1 KB gzip, CSP 0. E'tibor bering: tajriba kodida defaultSize={40} — v4 da bu 40 piksel. Tajriba faqat hajm va CSP'ni o'lchagani uchun bu muhim emas edi, lekin xuddi shu satr real ilovada yuqoridagi tuzoq bo'lardi.
4. Masonry
4.1 CSS yo'li va uning chegarasi
Layout naqshlari darsida masonry'ni CSS bilan qurganmiz: columns: 12rem va break-inside: avoid. Yangi display: grid-lanes ham bor, lekin Baseline emas — biz Chrome 155 da ham tekshirdik: CSS.supports("display", "grid-lanes") — false.
CSS columns ning bitta xususiyati React ilovasida muammo bo'ladi — tartib. Haqiqiy brauzerda raqamlarga qarang:
<style>
body { font: 1rem/1.4 system-ui, sans-serif; margin: 1rem; }
.masonry { columns: 3; column-gap: 0.5rem; }
.card { break-inside: avoid; margin: 0 0 0.5rem; padding: 0.5rem;
border-radius: 0.5rem; background: #e8eef8; }
</style>
<div class="masonry">
<div class="card" style="height: 60px">1</div>
<div class="card" style="height: 120px">2</div>
<div class="card" style="height: 40px">3</div>
<div class="card" style="height: 90px">4</div>
<div class="card" style="height: 50px">5</div>
<div class="card" style="height: 110px">6</div>
<div class="card" style="height: 70px">7</div>
</div>Biz Chrome 155 da kartalarning joyini o'lchadik: 1-ustunda — 1 va 2, 2-ustunda — 3, 4, 5, 3-ustunda — 6 va 7 (oyna eni boshqacha bo'lsa, taqsimot ham o'zgaradi). Multi-column matnni gazetadek ustun bo'yicha to'ldiradi: avval birinchi ustun pastgacha, keyin ikkinchisi. Fikrlar lentasida esa eng yangisi tepada, chapdan o'ngga o'qilishi kerak: 1, 2, 3 — qator bo'yicha. Cheksiz yuklashda bundan ham yomoni: yangi kartalar qo'shilganda hamma ustun qaytadan taqsimlanadi va eski kartalar boshqa ustunga sakraydi.
4.2 JavaScript yo'li: eng qisqa ustunga
JavaScript masonry'ning g'oyasi g'isht teruvchiniki: har yangi karta eng qisqa ustunga qo'yiladi. Kartalar tartibi qator bo'yicha saqlanadi, yangi kartalar eskilarini qimirlatmaydi. Balandliklar ma'lum bo'lsa (yoki o'lchangan bo'lsa), algoritm — oddiy sikl:
const HEIGHTS = [60, 120, 40, 90, 50, 110, 70];
function layout(heights, columnCount, gap = 8) {
const columns = Array.from({ length: columnCount }, () => []);
const tops = new Array(columnCount).fill(0);
heights.forEach((h, i) => {
// eng qisqa ustun (teng bo'lsa — chaprog'i)
const col = tops.indexOf(Math.min(...tops));
columns[col].push(i + 1);
tops[col] += h + gap;
});
return { columns, tops };
}
const { columns, tops } = layout(HEIGHTS, 3);
columns.forEach((c, i) => {
const list = c.join(", ");
console.log(`${i + 1}-ustun: ${list} (balandligi ${tops[i]})`);
});Konsolda:
1-ustun: 1, 5, 6 (balandligi 244)
2-ustun: 2, 7 (balandligi 206)
3-ustun: 3, 4 (balandligi 146)Kartalar 1, 2, 3 — uchta ustunning tepasida, chapdan o'ngga. 4-karta eng qisqa ustunga (3-ustun, 40 px li karta ostiga) tushdi. 5 va 6 — o'sha paytda eng qisqa bo'lib qolgan 1-ustunga. Har karta qo'yilgan paytdagi eng past joyga tushadi — g'isht teruvchi kabi.
masonic (4.1.0) kutubxonasi aynan shuni qiladi, ustiga ikki narsa qo'shadi: kartalarni o'zi o'lchaydi (ResizeObserver bilan — rasm yuklanib balandlik o'zgarsa ham) va virtualizatsiya qiladi — 10 000 kartadan faqat ko'rinadiganlari DOM'da (virtualizatsiya):
// #18 masonic 4.1.0: masonry
import { Masonry } from "masonic";
import { createRoot } from "react-dom/client";
const items = Array.from({ length: 30 }, (_, i) => ({
id: i,
h: 40 + (i % 5) * 20,
}));
createRoot(document.getElementById("ilova")!).render(
<main>
<h1>Masonry</h1>
<Masonry
items={items}
columnWidth={120}
render={({ data }) => (
<div style={{ height: data.h, background: "#ddd" }}>
{data.id}
</div>
)}
/>
</main>,
);Bu — kanon tajribasi (tajriba/kitlar/src/k/masonic.tsx, kanondan aynan, qatorlar bo'lingan). columnWidth — ustunning eng kichik eni: ustunlar soni konteyner eniga qarab o'zi hisoblanadi. Natija:
masonic JS 73377 (+ 5.5 KB) CSS 0 | CSP 0 | xato: yo'q+5.5 KB, CSP 0. masonic har kartani position: absolute bilan style orqali joylaydi (CSSOM) — CSP'ga mos.
| Yo'l | Tartib | Narx | Qachon |
|---|---|---|---|
CSS columns |
ustun bo'yicha | 0 KB | statik galereya, tartib muhim emas |
display: grid-lanes |
qator bo'yicha | 0 KB | kelajakda, Baseline bo'lgach |
| JS (masonic) | qator bo'yicha | +5.5 KB | lenta, cheksiz yuklash, minglab karta |
5. ResizeObserver React'da
5.1 Nega kerak
Media query butun oyna eniga qaraydi. Panel esa sudralganda oyna o'zgarmaydi — faqat panel eni o'zgaradi. Panel ichidagi jadval "torayganimni" qanday biladi? CSS'da — container queries (@container). JavaScript'da — ResizeObserver (ResizeObserver darsi): element o'lchami o'zgarganda xabar beradi. masonic ham ustunlar sonini shu bilan hisoblaydi.
5.2 useMeasure hook
O'tgan darsdagi qorovul hooki bilan bir xil naqsh: ref callback ichida kuzatuvchi, React 19 cleanup'ida uzish. Tekshiruv muhitida (jsdom) haqiqiy joylashuv yo'q — o'lchanadigan en ham yo'q, shuning uchun blok brauzerda ishlaydi. «Ishga tushir» dan keyin brauzer oynasining enini o'zgartirib ko'ring:
import { useCallback, useState } from "react";
function useMeasure() {
const [width, setWidth] = useState(0);
const ref = useCallback((el) => {
const ro = new ResizeObserver(([entry]) => {
setWidth(Math.round(entry.contentRect.width));
});
ro.observe(el);
return () => ro.disconnect();
}, []);
return [ref, width];
}
export default function App() {
const [ref, width] = useMeasure();
const columns = width >= 480 ? 3 : width >= 320 ? 2 : 1;
return (
<div ref={ref}>
<p>Eni: {width}px — ustunlar: {columns}</p>
</div>
);
}Biz Chrome 155 da oyna enini uch marta o'zgartirib sinadik (600, 420 va 300 px; sahifa chetidagi 8 px lik bo'shliqlar ayiriladi):
Eni: 584px — ustunlar: 3
Eni: 404px — ustunlar: 2
Eni: 284px — ustunlar: 1Ikki eslatma:
contentRect—paddingsiz ichki o'lcham. Chegara vapaddingbilan kerak bo'lsa —entry.borderBoxSize.- Har o'zgarishda state. Sudrash paytida
ResizeObserverhar kadrda xabar beradi va har safar komponent qayta chiziladi. Faqat ustunlar soni kerak bo'lsa,setColumns(hisob)— React bir xil qiymatda qayta chizmaydi (bailout).
Tayyor hook — react-use-measure (2.1.7): const [ref, bounds] = useMeasure() — bounds da width, height, top, left. Kanon uni o'lchamadi: vazifalar da o'lchamga bog'liq JavaScript yo'q — hammasi CSS bilan.
5.3 Telefonda: split view emas, ikki sahifa
Split view kompyuter ekrani uchun. 360 px lik telefonda ikki panel sig'maydi: 40% — 144 px, unda buyurtma raqami ham zo'rg'a ko'rinadi. Shuning uchun tor ekranda ro'yxat → tafsilot naqshi ishlatiladi (inglizcha master-detail): avval faqat ro'yxat, buyurtma bosilganda — faqat tafsilot va «Orqaga» tugmasi.
React'da buni ikki yo'l bilan qilish mumkin:
- Router bilan (
/buyurtmalarva/buyurtmalar/:id, 19-qism). Kompyuterda ikkala marshrut bitta layout ichida yonma-yon chiziladi (<Outlet />o'ng panelda), telefonda esa — alohida sahifalar. Bu eng toza yo'l: "Orqaga" tugmasi va havolani ulashish o'zi ishlaydi. - Konteyner eni bilan —
useMeasuredan kelgan en 640 px dan kichik bo'lsa,Groupo'rniga bitta panel. Bu yo'l faqat ilovaning bir qismi (masalan, dashboard'dagi vidjet) uchun mos.
Ikkala holatda ham bitta qoida: kichik ekranda ajratkich chizilmaydi. Barmoq bilan 8 px lik chiziqni tortish noqulay va panelning o'zi sig'maydi. react-resizable-panels hujjati ham teginish uchun kattaroq nishonni tavsiya qiladi (resizeTargetMinimumSize, Apple tavsiyasiga ko'ra teginishda ~37 px).
Ichma-ich guruhlar ham ko'p uchraydi: chapda ro'yxat, o'ngda tepada tafsilot, pastda izohlar. Panel ichiga yana bir Group qo'yiladi, faqat yo'nalishi boshqa:
<Group orientation="horizontal">
<Panel id="list" defaultSize="35">…</Panel>
<Separator />
<Panel id="right">
<Group orientation="vertical">
<Panel id="details">…</Panel>
<Separator />
<Panel id="comments" collapsible minSize="20">…</Panel>
</Group>
</Panel>
</Group>Klaviatura bilan ishlaganda bir nechta ajratkich orasida Tab bilan yuriladi. APG naqshi yana bitta ixtiyoriy tugmani tilga oladi — F6: panellar orasida sakrash. Uni kod muharrirlari ishlatadi, oddiy veb-ilovada odatda shart emas.
5.4 Sticky
Split view'da yana bir CSS vositasi tez-tez kerak: panel ichida aylantirganda sarlavha tepada yopishib tursin. Bu position: sticky — JavaScript'siz (fixed va sticky darsi). Tuzog'i o'sha darsda: sticky o'zining eng yaqin aylantiriladigan ota-elementiga yopishadi. Split view'da har panel o'zi aylanadi (overflow: auto) — sarlavha aynan panelning tepasiga yopishadi, sahifaning emas. Bu aynan kerakli xulq.
6. Tajriba: nega vazifalar ga kirmadi
Bu darsda vazifalar kodi o'zgarmaydi. Kanon uchala vositani o'lchadi va hech birini olmadi.
Split view. vazifalar — kengligi 36rem (576 px) bitta ustunli ilova. Telefonda (360 px) ikkita panel umuman sig'maydi, kompyuterda esa ikkinchi panelga ko'rsatadigan narsa yo'q: vazifa tafsiloti 19-qismdan beri alohida sahifa (/vazifa/:id). +13.1 KB — hamma foydalanuvchi uchun, foydasi esa yo'q.
Masonry. Vazifalar — bir xil balandlikdagi qatorlar ro'yxati, kartalar to'ri emas. Masonry yechadigan muammo (har xil balandlikdagi kartalar) yo'q.
ResizeObserver. Ilovada o'lchamga qarab o'zgaradigan hamma narsa CSS bilan qilingan: media query'lar va Tailwind variantlari. JavaScript o'lchovi — qo'shimcha qayta renderlar.
Kanon qoidasi har darsda bir xil: kutubxona — muammo bor bo'lsa va narxi o'lchangan bo'lsa. Bu darsdagi vositalar admin panellar, pochta va dizayn dasturlari, galereyalar uchun — «Real ishda» bo'limida.
- react-resizable-panels 4.14.3CSP 013,1 KB
- masonic 4.1.0CSP 05,5 KB
- o'z ajratkichimiz~60 qator0 KB
Manba: IlmHamroh kurs tajribasi — kutubxonalar o'lchovi (Vite 8.3.3, Chrome 155, 2026-10-14); o'z ajratkichimiz — shu darsdagi kod
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| ajratkichni Tab bilan topib bo'lmaydi | div fokus olmaydi |
role="separator" + tabIndex={0} |
| strelka bosilsa sahifa aylanadi | preventDefault yo'q |
onKeyDown da e.preventDefault() |
| telefonda sudrash sahifani aylantiradi | touch-action yo'q |
ajratkichda touch-action: none |
| sichqoncha ajratkichdan chiqsa, sudrash uziladi | pointer capture yo'q | setPointerCapture |
| sahifa yangilansa layout yo'qoladi | saqlanmagan | localStorage yoki useDefaultLayout |
| v4 da panel juda tor | defaultSize={40} — piksel |
defaultSize="40" (foiz) |
PanelGroup is not exported |
v3 kod, v4 paket | Group, Panel, Separator |
| masonry'da tartib aralash | CSS columns — ustun bo'yicha |
qator tartibi kerak bo'lsa — JS (masonic) |
ResizeObserver loop completed with undelivered notifications |
kuzatuvchi ichida o'sha elementning o'lchamini o'zgartirish | o'lchamni o'zgartirmang yoki keyingi kadrga qoldiring |
Oxirgi qator — brauzer ogohlantirishi. Tarjimasi: "ResizeObserver sikli yetkazilmagan xabarlar bilan tugadi". Kuzatuvchi o'lcham haqida xabar berdi, siz unga javoban o'sha elementni kattalashtirdingiz, u yana xabar bermoqchi — brauzer cheksiz siklni bir kadrda to'xtatadi.
8. Mashqlar
Mashqlar kurs/mashqlar/20/18-layout-vositalari/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): ajratkichning ARIA'si
Ikki panel yonma-yon, orasida tik chiziq. Ajratkichga nima kerak?
- Roli →
role= - Chiziq yo'nalishi →
aria-orientation= - Hozirgi o'lcham (foiz) →
- Qaysi panelni o'zgartiradi →
Yechim
separator— vatabIndex={0}: fokus oladigan ajratkich.vertical— chiziqning o'zi tik (panellar gorizontal joylashgan bo'lsa ham).aria-valuenow—aria-valuemin/aria-valuemaxbilan birga.aria-controls— asosiy panelningidsi.
2-mashq (o'rta): aria-valuetext va chegaralar
«O'z ajratkichingiz» misoliga qo'shing:
aria-valuetext— Malika "40" emas, "Buyurtmalar 40 foiz" ni eshitsin.- Home — 20%, End — 80% (misolda bor — sinab ko'ring).
- Ajratkich ostida
role="status"emas, oddiy matn: «Buyurtmalar: 40% · Tafsilot: 60%».
Ishora: aria-valuetext — satr; aria-valuenow qoladi (ba'zi yordamchi texnologiyalar raqamni o'qiydi). Matn — shablon satr bilan.
Yechim
import { useState } from "react";
const MIN = 20;
const MAX = 80;
const clamp = (v) => Math.min(MAX, Math.max(MIN, v));
export default function App() {
const [left, setLeft] = useState(40);
function onKeyDown(e) {
const keys = {
ArrowLeft: left - 5,
ArrowRight: left + 5,
Home: MIN,
End: MAX,
};
if (!(e.key in keys)) return;
e.preventDefault();
setLeft(clamp(keys[e.key]));
}
return (
<div>
<div style={{ display: "flex", height: 80 }}>
<section id="orders" style={{ flexBasis: `${left}%` }} />
<div
role="separator"
tabIndex={0}
aria-label="Panel o'lchami"
aria-controls="orders"
aria-orientation="vertical"
aria-valuenow={left}
aria-valuemin={MIN}
aria-valuemax={MAX}
aria-valuetext={`Buyurtmalar ${left} foiz`}
onKeyDown={onKeyDown}
style={{ width: 8, background: "#99a" }}
/>
<section style={{ flex: 1 }} />
</div>
<p>
Buyurtmalar: {left}% · Tafsilot: {100 - left}%
</p>
</div>
);
}Ekranda:
Buyurtmalar: 75% · Tafsilot: 25%3-mashq (qiyin): yig'iladigan panel
Ajratkichga Enter qo'shing: bosilganda chap panel yig'iladi (0%), yana bosilganda — yig'ilishdan oldingi o'lchamiga qaytadi. Konsolga har o'zgarish: chap: 40%. Yig'ilgan holatda strelkalar ishlamasin.
Ishora: oldingi o'lchamni eslab qolish — useRef (useRef I), chunki u ekranga chiqmaydi. aria-valuemin endi 0 (yig'ilgan holat ham qiymat).
Yechim
import { useEffect, useRef, useState } from "react";
const MIN = 20;
const MAX = 80;
const clamp = (v) => Math.min(MAX, Math.max(MIN, v));
export default function App() {
const [left, setLeft] = useState(40);
const beforeCollapse = useRef(40);
useEffect(() => {
console.log(`chap: ${left}%`);
}, [left]);
function onKeyDown(e) {
if (e.key === "Enter") {
e.preventDefault();
if (left === 0) {
setLeft(beforeCollapse.current);
} else {
beforeCollapse.current = left;
setLeft(0);
}
return;
}
if (left === 0) return; // yig'ilganda — faqat Enter
const keys = { ArrowLeft: left - 5, ArrowRight: left + 5 };
if (!(e.key in keys)) return;
e.preventDefault();
setLeft(clamp(keys[e.key]));
}
return (
<div style={{ display: "flex", height: 80 }}>
<section id="orders" style={{ flexBasis: `${left}%` }} />
<div
role="separator"
tabIndex={0}
aria-label="Panel o'lchami"
aria-controls="orders"
aria-orientation="vertical"
aria-valuenow={left}
aria-valuemin={0}
aria-valuemax={MAX}
onKeyDown={onKeyDown}
style={{ width: 8, background: "#99a" }}
/>
<section style={{ flex: 1 }} />
</div>
);
}Konsolda:
chap: 40%
chap: 45%
chap: 0%
chap: 45%9. Real ishda
- Admin panellar va ichki dasturlar — ro'yxat | tafsilot (CRM, pochta, buyurtmalar), kod muharrirlari, dashboard'lardagi o'zgaruvchan bloklar. shadcn'ning
Resizablei —react-resizable-panelsustida. - Galereyalar va lentalar — Pinterest tipidagi rasmlar, mahsulot kartochkalari, portfolio. Kichik galereyaga CSS
columns, cheksiz lentaga — JS masonry. - Moslashuvchan komponentlar — panel, karta yoki vidjet o'z eniga moslashadi: avval container queries (CSS), JavaScript o'lchov — faqat hisob kerak bo'lganda (masalan, masonry ustunlari).
- Intervyu savollari: "Split view'ni a11y bilan qanday qilasiz?", "Masonry'ni CSS va JS bilan qilish farqi?", "
ResizeObserverva media query farqi?", "Panel o'lchamini qayerda saqlaysiz?".
Xulosa
- Ajratkich —
role="separator", fokus,aria-valuenow/min/max,aria-controls,aria-orientation="vertical"; ← → Home End Enter; sichqonchadasetPointerCapture, telefondatouch-action: none. - Layout — shaxsiy qulaylik:
localStorage(tekshirib o'qing, dangasa boshlang'ich qiymat). react-resizable-panelsv4:Group/Panel/Separator,useDefaultLayout; son — piksel, satr — foiz. v3 (PanelGroup,PanelResizeHandle,autoSaveId) — eski kodda. +13.1 KB.- Masonry: CSS
columns— ustun bo'yicha tartib, 0 KB; JS — eng qisqa ustunga, qator tartibi, masonic +5.5 KB (o'lchash + virtualizatsiya);grid-lanes— hali Baseline emas (Chrome 155 da yo'q). ResizeObserver— element eni (panel, konteyner); avval container queries.sticky— panel ichida yopishadigan sarlavha.vazifalar— 36rem bitta ustun: bu vositalar yechadigan muammo yo'q, shuning uchun kanonga kirmadi.
Keyingi dars: Node-diagramma va canvas: React Flow va Konva — vizual muharrirlar uchun tugun va bog'lanishlar (React Flow), canvas grafikasi (Konva) va qachon SVG, qachon canvas; Trusted Types ostida nima ochilmadi.
Manbalar
- WAI-ARIA APG: Window Splitter Pattern — w3.org/WAI/ARIA/apg/patterns/windowsplitter
- react-resizable-panels (4.14.3) — github.com/bvaughn/react-resizable-panels, react-resizable-panels.vercel.app
- masonic (4.1.0) — github.com/jaredLunde/masonic
- MDN: Resize Observer API,
position: sticky, CSS multi-column layout — developer.mozilla.org - react-use-measure (2.1.7) — github.com/pmndrs/react-use-measure
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!