CSS — Каскадні таблиці стилів¶
CSS (Cascading Style Sheets) визначає як виглядає HTML-розмітка. HTML — кістяк, CSS — одяг і косметика. Три способи підключення: inline,
<style>, зовнішній файл (рекомендований).
🧠 Ментальна модель: CSS — це НЕ мова програмування. Це мова ДЕКЛАРАЦІЙ. Ти не кажеш браузеру "зроби це кроком 1, кроком 2". Ти кажеш: "я ХОЧУ, щоб цей елемент виглядав так" — а браузер вже сам вирішує як це досягти. Як замовлення в ресторані: ти говориш що хочеш з'їсти, а кухар вирішує як приготувати.
📚 Чому це існує: До CSS вся стилізація вбудовувалась прямо в HTML через атрибути (
<font color="red">). Це призвело до жахливого дублювання — щоб змінити колір заголовків на всьому сайті, треба було редагувати тисячі тегів. CSS відокремив "що показати" від "як показати".🌐 CSSOM — паралельне дерево до DOM: Так само як HTML будує DOM (Document Object Model), браузер будує CSSOM (CSS Object Model) — паралельне дерево правил стилів. Тільки коли обидва дерева готові, браузер об'єднує їх у Render Tree і малює сторінку. Тому
<link rel="stylesheet">в<head>блокує рендеринг — браузер не намалює жодного пікселя, поки не завантажить і не розбере CSS. Повільний CSS = білий екран для користувача.❌ Типова помилка початківця: Вважати, що CSS "виконується" зверху вниз як Python. Насправді браузер спочатку збирає ВСІ правила, потім застосовує їх разом, вирішуючи конфлікти через каскад і специфічність. Порядок важливий тільки при однаковій специфічності.
0. Архітектура Frontend та Конвеєр Рендерингу¶
Separation of Concerns — як у backend¶
У backend ти звик до чіткого розділення відповідальності: - Model — дані - View — логіка - Template — представлення
У браузері те саме:
| Шар | Відповідальність |
|---|---|
| HTML | Структура та семантика даних |
| CSS | Візуальне представлення |
| JavaScript | Поведінка та динамічні зміни |
CSS ніколи не змінює HTML — він змінює DOM-вузли у пам'яті.
Rendering Pipeline — від байтів до пікселів¶
Цей діаграм показує повний шлях від твого Django-шаблону до пікселів на екрані. Зверни увагу, що HTML і CSS обробляються паралельно — це одна з причин, чому CSS у <head> блокуючий: браузер хоче мати CSSOM готовим одночасно з DOM.
[Django Template] --> HTTP Response (HTML-рядок)
|
▼
┌─── Browser Parser ───┐
│ │
[HTML] [CSS]
│ │
▼ ▼
(DOM Tree) (CSSOM Tree)
│ │
└──────────┬───────────┘
▼
(Render Tree)
(тільки видимі елементи)
│
▼
[Layout / Reflow]
(обчислення розмірів і позицій)
│
▼
[Paint]
(пікселі на екрані)
Детальні етапи¶
1. Парсинг DOM
Браузер читає HTML зверху вниз, будує дерево об'єктів у пам'яті.
Кожен тег → вузол (Node). <p>Текст</p> → два вузли: Element + Text.
2. Парсинг CSSOM
Паралельно читається CSS (зі <style> або зовнішніх .css).
Будується CSS Object Model — дерево правил з обчисленими значеннями.
3. Render Tree
DOM + CSSOM об'єднуються. Виключаються елементи display: none та <head>.
Кожен видимий вузол отримує стиль.
4. Layout (Reflow) Браузер обчислює точні координати і розміри кожного елемента. Результат: прямокутник (x, y, width, height) для кожного вузла.
5. Paint Заповнення пікселів: кольори, фони, рамки, тіні, текст.
Layout Thrashing — критична пастка продуктивності¶
Layout Thrashing — це коли JavaScript почергово читає і записує DOM-властивості, змушуючи браузер робити synchronous reflow після кожного читання. Аналогія: уяви офіціанта, якого ти запитуєш "скільки вільних столиків?" → він рахує → ти займаєш один → знову запитуєш → він знову рахує → і так по колу. Набагато краще: порахувати всі вільні місця одразу, а потім зайняти кілька.
// ❌ ПОГАНО — Layout Thrashing
// Кожне читання offsetHeight змушує браузер робити synchronous reflow
for (const el of elements) {
const h = el.offsetHeight; // браузер перераховує layout
el.style.height = (h + 10) + 'px'; // потім знову змінює
}
// ✅ ДОБРЕ — batch read, потім batch write
const heights = elements.map(el => el.offsetHeight); // всі читання
heights.forEach((h, i) => {
elements[i].style.height = (h + 10) + 'px'; // всі записи
});
Властивості що тригерять reflow: offsetHeight, offsetWidth, getBoundingClientRect(),
scrollTop, clientWidth, getComputedStyle().
Правило: Для backend-розробника Layout Thrashing — це аналог N+1 у ORM: замість одного запиту — сотні. Завжди групуй читання окремо від записів.
1. Підключення CSS¶
Є три способи підключити CSS. Зовнішній файл — найкращий, бо він кешується браузером: після першого завантаження CSS-файл зберігається локально, і наступні сторінки сайту завантажуються швидше. <style> в <head> корисний для невеликих критичних стилів. Inline — тільки для динамічних значень з Django context, бо він має найвищу специфічність і його важко перевизначити.
<!-- 1. Зовнішній файл (найкращий спосіб — кешується браузером) -->
<link rel="stylesheet" href="style.css">
<!-- 2. Блок <style> в <head> (для невеликих стилів або компонентів) -->
<style>
body { font-family: Arial; }
</style>
<!-- 3. Inline стиль (тільки для динамічних значень через Django context) -->
<div style="background-color: {{ user.color }}; width: 100px;">...</div>
2. Анатомія CSS-правила¶
Кожне CSS-правило складається з трьох частин: селектор (кого стилізувати), властивість (що змінити) і значення (на що змінити). Крапка з комою в кінці кожного оголошення — обов'язкова, без неї браузер "не бачить" наступне правило. Коментарі в CSS пишуться тільки через /* */, не через //.
/* Селектор { властивість: значення; } */
h1 {
color: #2c3e50; /* колір тексту */
font-size: 2rem; /* розмір шрифту */
font-weight: bold; /* товщина шрифту */
margin-bottom: 1rem; /* відступ знизу */
}
🧠 Ментальна модель: CSS-селектори — це як SQL WHERE для DOM.
.card p= "SELECT всі<p>, що знаходяться ВСЕРЕДИНІ елемента з класом.card". Ти не визначаєш що робити — ти визначаєш КОГО чіпати.📚 Чому це існує: Без селекторів довелось би стилізувати кожен елемент окремо через inline-стилі. Селектори дозволяють написати одне правило, яке автоматично застосовується до сотень відповідних елементів — і коли ти їх додаєш, стилі підхоплюються автоматично.
🌐 Як браузер обробляє це: Браузер читає складені селектори справа наліво для ефективності.
.card p→ спочатку знаходить усі<p>на сторінці, потім перевіряє чи кожен з них має предка.card. Тому уникай надмір глибоких селекторів — браузер перевіряє кожен елемент.❌ Типова помилка початківця: Плутати
.btn.active(без пробілу — обидва класи на ОДНОМУ елементі) і.btn .active(з пробілом —.activeВСЕРЕДИНІ.btn). Пробіл — це комбінатор "нащадок", і він повністю змінює значення селектора.
3. Селектори — як вибрати елементи¶
Базові селектори¶
Базові селектори — це фундамент. Тег вибирає всі елементи цього типу (обережно — дуже широко). Клас (.) — найпоширеніший спосіб, один елемент може мати кілька класів. ID (#) — унікальний на сторінці, використовуй для JS-хуків або anchor-посилань, а не для стилізації. Атрибутні селектори дозволяють фільтрувати елементи по вмісту атрибутів — корисно для форм.
/* Тег — всі елементи цього типу */
p { color: #333; }
h1, h2, h3 { font-family: Georgia; } /* кілька через кому */
/* Клас — починається з . */
.card { border: 1px solid #ddd; }
.btn.btn-primary { background: blue; } /* два класи одночасно (без пробілу) */
/* ID — починається з # — унікальний */
#header { background: #000; }
/* Атрибут */
input[type="text"] { border: 2px solid blue; }
a[href^="https"] { color: green; } /* href починається з https */
a[href$=".pdf"] { color: red; } /* href закінчується на .pdf */
a[href*="google"] { font-weight: bold; } /* href містить google */
Комбінатори (відносини між елементами)¶
Комбінатори описують відносини між елементами в DOM-дереві. Пробіл (нащадок) — найширший: вибирає елементи на будь-якій глибині вкладеності. > (прямий нащадок) — суворіший: тільки безпосередні діти. + і ~ — для сусідніх елементів. Знай різницю: .nav > li не зачепить li в підменю, а .nav li — зачепить все.
/* Нащадок (будь-який рівень вкладеності) */
.card p { color: #666; }
/* Прямий нащадок */
nav > ul { padding: 0; }
/* Сусідній елемент (одразу після) */
h2 + p { font-size: 1.1rem; }
/* Всі наступні сусіди */
h2 ~ p { color: #555; }
Псевдокласи¶
Псевдокласи дозволяють стилізувати елементи залежно від їх стану або позиції в DOM. Стани посилань (:hover, :active) — реакція на дії користувача. :focus — критично важливо для доступності (keyboard navigation). Структурні псевдокласи (:nth-child) — потужний інструмент для зебрових таблиць та складних списків без JavaScript.
/* Стан посилань */
a:link { color: blue; } /* непереглянуте */
a:visited { color: purple; } /* переглянуте */
a:hover { color: red; } /* при наведенні миші */
a:active { color: orange; } /* при натисканні */
/* Фокус (для keyboard navigation та форм) */
input:focus { outline: 2px solid blue; border-color: blue; }
button:focus-visible { outline: 3px solid orange; }
/* Структурні */
li:first-child { font-weight: bold; }
li:last-child { border-bottom: none; }
li:nth-child(2) { background: #f0f0f0; } /* 2-й елемент */
li:nth-child(odd) { background: #fafafa; } /* непарні */
li:nth-child(even) { background: #f0f0f0; } /* парні */
li:nth-child(3n) { color: red; } /* кожен 3-й */
/* Пусті/непусті */
p:empty { display: none; }
input:not([disabled]) { cursor: pointer; }
/* Форми */
input:valid { border-color: green; }
input:invalid { border-color: red; }
input:required::after { content: " *"; color: red; }
input:disabled { opacity: 0.5; cursor: not-allowed; }
input:checked + label { font-weight: bold; }
Псевдоелементи¶
Псевдоелементи (з подвійним ::) створюють "віртуальні" дочірні елементи, яких немає в HTML, але вони рендеряться у браузері. ::before і ::after вимагають властивість content — навіть якщо вона порожня (content: ""). Вони корисні для декоративних елементів (іконки, лічильники), щоб не засмічувати HTML.
/* Вміст до/після елемента */
.card::before { content: "★ "; color: gold; }
.required::after { content: " *"; color: red; }
/* Перший рядок / перший символ */
p::first-line { font-variant: small-caps; }
p::first-letter { font-size: 3rem; float: left; }
/* Виділений текст */
::selection { background: #ffd700; color: #000; }
/* Placeholder в input */
input::placeholder { color: #aaa; font-style: italic; }
🧠 Ментальна модель: Специфічність — це 4-значне число (a, b, c, d), де кожна цифра відповідає рівню селектора: (inline, ID, class/attr/pseudo-class, tag/pseudo-element). Порівнюй як числа:
(0,1,0,0)= 100,(0,0,10,0)= 100 теж — але ID завжди виграє у класів, бо порівняння йде зліва направо, поцифрово. Десять класів не переб'ють один ID.📚 Чому це існує: Без специфічності браузер не знав би яке правило застосувати, якщо два правила конфліктують. Специфічність — це детермінований алгоритм вирішення конфліктів, що гарантує передбачуваний результат незалежно від порядку підключення CSS-файлів.
🌐 Як браузер обробляє це: Браузер збирає ВСІ правила для елемента, обчислює специфічність кожного, сортує та застосовує переможця. При рівній специфічності перемагає правило, оголошене пізніше у файлі (або в файлі, підключеному пізніше).
❌ Типова помилка початківця: Використовувати
!importantщоб "виправити" конфлікт специфічності. Це як кричати на офіціанта замість того, щоб зрозуміти меню.!importantламає каскад і перетворює CSS у хаос: коли вже все важливо — нічого не важливо. Правильне рішення — зрозуміти чому виникає конфлікт і виправити структуру.
4. Специфічність — алгоритм вирішення конфліктів¶
Як CSS вирішує конфлікти (Cascade Algorithm)¶
Коли кілька правил намагаються змінити одну властивість елемента, браузер проходить через ієрархію пріоритетів:
1. !important оголошення (найвищий)
2. Inline стилі (style="...")
3. ID-селектори (#header)
4. Класи / Атрибути / Псевдокласи (.card, [type], :hover)
5. Теги / Псевдоелементи (p, h1, ::before)
6. Успадковані стилі від батьків (найнижчий)
При однаковій специфічності виграє правило оголошене останнім у файлі.
Математика специфічності¶
Ось як браузер розраховує "вагу" кожного селектора. Перша цифра (inline) завжди найсильніша — навіть один inline-стиль перебиває тисячу CSS-правил. ID перебиває будь-яку кількість класів. Це не просто "більше = краще" — це ієрархія рівнів.
Inline style → (1, 0, 0, 0) = 1000
#id → (0, 1, 0, 0) = 0100
.class / [attr] → (0, 0, 1, 0) = 0010
tag → (0, 0, 0, 1) = 0001
Нижче — практичне порівняння: зверни увагу, як поєднання селекторів додає їх специфічності. p.text = тег (0,0,0,1) + клас (0,0,1,0) = (0,0,1,1). Це більше ніж просто .text (0,0,1,0), але менше ніж #intro (0,1,0,0).
p { color: black; } /* (0,0,0,1) = 1 */
.text { color: blue; } /* (0,0,1,0) = 10 */
p.text { color: red; } /* (0,0,1,1) = 11 */
#intro { color: red; } /* (0,1,0,0) = 100 */
p#intro { font-size: 100%; } /* (0,1,0,1) = 101 */
p { font-size: 75%; } /* (0,0,0,1) = 1 */
/* !important — скасовує алгоритм, уникати */
p { color: pink !important; } /* перебиває всі без !important */
Prediction Exercise¶
Цей приклад — класичний тест на розуміння специфічності. Спробуй сам вирішити ПЕРЕД тим, як дивитись відповідь. Кожна властивість вирішується незалежно — color і font-size можуть прийти з різних правил.
/* Правило 1 */ p { color: black; font-size: 75%; }
/* Правило 2 */ .text { color: blue; }
/* Правило 3 */ #intro { color: red; }
/* Правило 4 */ p#intro { font-size: 100%; }
Питання: Яким буде фінальний вигляд елемента?
Відповідь (розбір по властивостях):
| Властивість | Переможець | Чому |
|---|---|---|
color |
#intro → red |
(0,1,0,0)=100 > .text (0,0,1,0)=10 > p (0,0,0,1)=1 |
font-size |
p#intro → 100% |
(0,1,0,1)=101 > p (0,0,0,1)=1 |
Підсумок: <p> буде червоним і з розміром 100%.
DevTools підказка: якщо не розумієш чому стиль не застосовується → F12 → вкладка Styles (перекреслені правила програли в специфічності) або вкладка Computed (фінальне значення після вирішення всього каскаду).
🧠 Ментальна модель: Кожен HTML-елемент — це КОРОБКА. Завжди. Навіть рядок тексту — це коробка. Навіть невидима точка — це коробка. Flexbox та Grid — це системи розміщення цих коробок. Box Model описує з чого складається КОЖНА коробка: чотири шари від центру назовні.
📚 Чому це існує: Box Model стандартизував як браузери розраховують розміри елементів. До нього різні браузери рахували розміри по-різному (IE Wars). Сьогодні всі браузери дотримуються одного стандарту, але є два режими:
content-box(за стандартом) іborder-box(практичний).🌐 Як браузер обробляє це: Під час фази Layout браузер обчислює прямокутник кожного елемента. Якщо
box-sizing: content-box→width= розмір content, і до нього ДОДАЮТЬСЯ padding + border. Якщоbox-sizing: border-box→width= розмір від краю до краю border, padding і border ВКЛЮЧЕНІ у width. Bootstrap глобально встановлюєborder-boxчерез* { box-sizing: border-box; }.❌ Типова помилка початківця: Задати
width: 300px; padding: 20px;і дивуватись, що елемент має 340px ширини (300 + 20 + 20). Це поведінкаcontent-box. Зborder-boxелемент буде рівно 300px — padding "стискає" вміст всередині. Завжди додавайbox-sizing: border-boxна початку свого CSS.
5. Box Model — фундамент верстки¶
Уяви картину в рамі на стіні. content — сама картина. padding — паспарту (білий матеріал між картиною і рамою). border — сама рама. margin — простір стіни навколо рами, до сусідніх картин. Браузер завжди рахує фізичний розмір елемента як content + padding + border. Margin — це простір ЗА межами елемента.
┌─────────────────────────────────────────┐
│ margin │ ← зовнішній відступ (прозорий)
│ ┌─────────────────────────────────┐ │
│ │ border │ │ ← рамка
│ │ ┌─────────────────────────┐ │ │
│ │ │ padding │ │ │ ← внутрішній відступ
│ │ │ ┌─────────────────┐ │ │ │
│ │ │ │ content │ │ │ │ ← вміст (width × height)
│ │ │ └─────────────────┘ │ │ │
│ │ └─────────────────────────┘ │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────────┘
Зверни особливу увагу на рядок box-sizing: border-box — це одна з найважливіших практик. Bootstrap автоматично додає її до всіх елементів через скидання стилів. Якщо пишеш CSS без Bootstrap — додай сам. margin: 0 auto на блочному елементі з фіксованою шириною центрує його горизонтально.
.box {
/* Розмір вмісту */
width: 300px;
height: 200px;
/* Внутрішній відступ (між вмістом і рамкою) */
padding: 20px; /* всі 4 сторони */
padding: 10px 20px; /* вертикально горизонтально */
padding: 10px 20px 15px 5px; /* top right bottom left (за годинниковою) */
padding-top: 10px;
padding-bottom: 15px;
/* Рамка */
border: 1px solid #ddd;
border-radius: 8px; /* заокруглення кутів */
border-top: 3px solid blue;
/* Зовнішній відступ (від елементу до сусідів) */
margin: 20px;
margin: 0 auto; /* центрування блоку горизонтально */
margin-bottom: 16px;
}
/* Важливо: box-sizing */
* {
box-sizing: border-box; /* width включає padding і border (Bootstrap робить це за замовчуванням) */
}
/* За замовчуванням: box-sizing: content-box; — width = тільки вміст */
6. Одиниці вимірювання¶
| Одиниця | Що означає | Коли використовувати |
|---|---|---|
px |
Пікселі — абсолютна | Рамки, тіні, дрібні деталі |
rem |
Відносно розміру шрифту <html> (зазвичай 16px) |
Шрифти, відступи — адаптивно |
em |
Відносно розміру шрифту батьківського елемента | Компоненти що мають масштабуватись |
% |
Відсоток від батьківського елемента | Ширина колонок, fluid layouts |
vw |
Viewport Width — % від ширини вікна | Повноекранні секції |
vh |
Viewport Height — % від висоти вікна | Hero секції, full-screen |
fr |
Fraction — частка у CSS Grid | Grid колонки/рядки |
ch |
Ширина символу "0" | Обмеження ширини тексту |
auto |
Браузер вираховує сам | Margin для центрування, height |
7. Відображення елементів — display¶
Властивість display визначає як елемент бере участь у потоці документа. block займає весь рядок. inline — тільки свій вміст, по рядку. inline-block — як inline, але можна задавати розміри. none — повне видалення з потоку (місце не займає, на відміну від visibility: hidden). flex і grid — сучасні системи верстки.
/* Block — займає всю ширину, нова строка до і після */
div, p, h1, section, article { display: block; }
/* Inline — рівно по вмісту, в рядку з іншими inline-елементами */
span, a, strong, img { display: inline; }
/* Не можна задавати width/height inline-елементам */
/* Inline-block — в рядку, але можна задавати width/height */
.badge { display: inline-block; padding: 4px 8px; }
/* None — приховати елемент (не займає місця) */
.hidden { display: none; } /* vs visibility: hidden; — займає місце але невидимий */
/* Flex — гнучкий контейнер (наступний розділ) */
.nav { display: flex; }
/* Grid — сіткова система (після flex) */
.layout { display: grid; }
🧠 Ментальна модель: Flexbox — це 1D система верстки. Вона розставляє елементи вздовж ОДНІЄЇ осі одночасно (або рядок, або стовпець). Ключове: є КОНТЕЙНЕР (
display: flex) і є ЕЛЕМЕНТИ (всі прямі діти). Контейнер керує розміщенням, елементи підпорядковуються — але кожен елемент може сам перевизначити деякі правила черезalign-self,order,flex.📚 Чому це існує: До Flexbox розробники верстали горизонтальні ряди через
float: left(потребував clearfix — хак ізoverflow: hidden) абоdisplay: inline-block(мав зазори між елементами через пробіли в HTML, які прибирали черезfont-size: 0на батьку). Flexbox — чиста, семантична заміна цих костилів.🌐 Як браузер обробляє це: Браузер визначає головну вісь (
flex-direction) і поперечну (перпендикулярна).justify-contentуправляє розподілом ВЗДОВЖ головної осі.align-itemsуправляє вирівнюванням ВЗДОВЖ поперечної осі. Це два незалежних параметри, і плутанина між ними — найпоширеніша помилка новачків.❌ Типова помилка початківця: Не розуміти вісі. Якщо
flex-direction: column, тоjustify-contentстає ВЕРТИКАЛЬНИМ (вздовж стовпця), аalign-items— ГОРИЗОНТАЛЬНИМ. Багато новачків думають щоjustify-content: centerзавжди центрує горизонтально — ні, тільки вздовж головної осі.
8. Flexbox — однонапрямна гнучка верстка¶
Перед тим як читати код — зрозумій архітектуру Flexbox через ASCII-діаграму. Головна вісь (main axis) — це напрямок flex-direction. Поперечна вісь (cross axis) — перпендикулярна до неї. justify-content завжди управляє головною віссю. align-items завжди управляє поперечною. Запам'ятай це — і Flexbox стане передбачуваним.
Main axis (flex-direction: row) →
┌─────────────────────────────────────┐
│ [item1] [item2] [item3] │ ← justify-content контролює розподіл тут
└─────────────────────────────────────┘
↑
align-items контролює вирівнювання тут (cross axis — вертикально для row)
Якщо змінити flex-direction: column, вісі міняються місцями:
Cross axis →
┌──────────┐
│ [item1] │ ← align-items контролює тут (горизонтально для column)
│ [item2] │
│ [item3] │
└──────────┘
↓ Main axis (justify-content контролює вертикально для column)
Нижче — повний набір властивостей Flexbox з поясненнями. Почни з розуміння display: flex, flex-direction, justify-content, align-items — це 80% всіх випадків використання.
/* Контейнер */
.flex-container {
display: flex;
flex-direction: row; /* row | column | row-reverse | column-reverse */
justify-content: space-between; /* вирівнювання по головній осі */
align-items: center; /* вирівнювання по поперечній осі */
flex-wrap: wrap; /* перенос на новий рядок якщо не влізає */
gap: 16px; /* відстань між дочірніми елементами */
}
/* justify-content значення:
flex-start → ← ← ← (зліва)
flex-end → → → (справа)
center → →→→ (по центру)
space-between → ← → ← → ← (рівна відстань між, без крайніх)
space-around → ←→ ←→ ←→ (рівна відстань навколо кожного)
space-evenly → ← → ← → ← → (рівна відстань між всіма включно з краями)
*/
/* align-items значення:
flex-start → елементи зверху
flex-end → елементи знизу
center → по центру вертикально
stretch → розтягнути по висоті (за замовчуванням)
baseline → по базовій лінії тексту
*/
/* Дочірній елемент */
.flex-item {
flex: 1; /* grow: 1, shrink: 1, basis: auto — займає рівну частину */
flex: 0 0 200px; /* фіксована ширина 200px, не росте, не стискається */
flex-grow: 2; /* росте в 2 рази більше за інші */
align-self: flex-start; /* перевизначення align-items для одного елемента */
order: -1; /* зміна порядку (за замовчуванням 0) */
}
Типові патерни Flexbox¶
Ці патерни — шаблони, які ти будеш використовувати щодня. Navbar з логотипом і меню по різних боках — space-between. Картки що переносяться на новий рядок — flex-wrap: wrap з flex: 1 1 300px. Центрування по екрану — justify-content: center + align-items: center + min-height: 100vh. Sticky footer — flex-direction: column на body і flex: 1 на main.
/* Navbar */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 20px;
}
/* Картки в рядок з переносом */
.cards {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.card { flex: 1 1 300px; } /* мінімум 300px, інакше переноситься */
/* Центрування елемента */
.centered {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh; /* по всій висоті екрану */
}
/* Footer внизу сторінки */
body {
display: flex;
flex-direction: column;
min-height: 100vh;
}
main { flex: 1; } /* займає весь залишок висоти */
footer { /* автоматично внизу */ }
🧠 Ментальна модель: Grid — це 2D система верстки. На відміну від Flexbox (одна вісь), Grid управляє рядками І колонками одночасно. Уяви електронну таблицю Excel: ти визначаєш скільки колонок і їх ширини, скільки рядків і їх висоти, а потім розміщуєш елементи в "клітинки" цієї таблиці.
📚 Чому це існує: Перед Grid складні макети (sidebar + content + header + footer) вимагали складних хаків з float, таблицями або абсолютним позиціонуванням. CSS Grid — перша нативна 2D система верстки у браузері, яка робить складні макети тривіальними.
🌐 Як браузер обробляє це: Браузер будує невидиму сітку ліній (grid lines).
grid-template-columns: 250px 1fr= дві вертикальні "залізничні рейки" що ділять простір на дві колонки. Елементи розміщуються між цими лініями.fr= "fractional unit" — частка від залишкового простору після вирахування фіксованих розмірів.❌ Типова помилка початківця: Намагатись вирішити все через Flexbox, коли задача двовимірна. Правило вибору просте: якщо думаєш про ряди І колонки одночасно (макет сторінки, dashboard) → Grid. Якщо думаєш про один напрямок (кнопки в рядок, список карток) → Flexbox.
9. CSS Grid — двовимірна сіткова верстка¶
Коли використовувати Grid vs Flexbox — це одне з найважливіших рішень у верстці: - Flexbox: "у мене є елементи, розстав їх в рядок або стовпець" (1D: навбар, група кнопок, рядок карток) - Grid: "у мене є сторінка з рядками І колонками одночасно" (2D: дашборд, журнальна верстка, Holy Grail Layout)
Підказка: якщо ти думаєш про рядки І колонки — Grid. Якщо тільки про один напрямок — Flexbox.
/* Контейнер */
.grid-container {
display: grid;
grid-template-columns: 1fr 2fr 1fr; /* 3 колонки: 25% | 50% | 25% */
grid-template-columns: repeat(3, 1fr); /* 3 рівні колонки */
grid-template-columns: 250px auto 1fr; /* фіксована, авто, залишок */
grid-template-rows: auto 1fr auto; /* header, main, footer */
gap: 20px; /* між колонками і рядками */
column-gap: 20px;
row-gap: 10px;
}
/* Дочірній елемент — розміщення */
.header { grid-column: 1 / -1; } /* від 1-ї до останньої колонки */
.sidebar { grid-column: 1; grid-row: 2; }
.main { grid-column: 2 / 4; }
.footer { grid-column: 1 / -1; }
/* Іменовані зони */
.layout {
display: grid;
grid-template-areas:
"header header header"
"sidebar main main"
"footer footer footer";
grid-template-columns: 250px 1fr 1fr;
}
header { grid-area: header; }
.sidebar { grid-area: sidebar; }
main { grid-area: main; }
footer { grid-area: footer; }
🧠 Ментальна модель:
position: static— елемент у нормальному потоці документа, як будинки на вулиці. Кожен стоїть на своєму місці.position: relative— будинок рухається, але його "місце" на вулиці зарезервовано.position: absolute— будинок "піднявся" в повітря і більше не займає місця в потоці.position: fixed— як рекламний щит, приклеєний до вашого вікна: завжди на тому ж місці незалежно від прокрутки.📚 Чому це існує: Для overlay-елементів (модалки, tooltips, dropdown меню, sticky навбар) потрібна можливість вийти з нормального потоку.
positionдає цю силу — але з великою силою приходить велика відповідальність: позиційовані елементи треба відстежувати, бо вони можуть перекрити інший вміст.🌐 Як браузер обробляє це: Позиційовані елементи (не static) створюють "стекінг контекст" і беруть участь у z-index сортуванні.
absoluteелемент шукає найближчого предка зposition!=static— і розміщується відносно нього. Якщо такого предка немає — розміщується відносно<html>.❌ Типова помилка початківця: Елемент з
position: absolute"вилітає" в неправильне місце. Причина: забули додатиposition: relativeна батьківський елемент. Завжди перевіряй: батько позиційованого absolute-елемента повинен матиposition: relative(абоabsolute/fixed/sticky).
10. Позиціонування¶
position — одна з найпотужніших і найнебезпечніших властивостей CSS. static — стандарт. relative — рухає елемент від його звичайного місця, але зберігає "резерв" місця. absolute — повністю виривається з потоку, розміщується відносно найближчого positioned предка. fixed — прикріплений до viewport, ідеально для sticky навбарів. sticky — комбінація: веде себе як relative поки не досягне порогу прокрутки, потім "прилипає".
.element {
position: static; /* за замовчуванням — в потоці документа */
position: relative; /* відносно своєї нормальної позиції (top/left/right/bottom) */
position: absolute; /* відносно найближчого non-static батька */
position: fixed; /* відносно viewport (не прокручується) */
position: sticky; /* relative + fixed (прилипає при прокрутці) */
}
/* Sticky navbar */
nav {
position: sticky;
top: 0;
z-index: 100; /* шар: більше = поверх */
background: white;
}
/* Overlay поверх зображення */
.image-container {
position: relative;
}
.overlay {
position: absolute;
top: 0; left: 0; right: 0; bottom: 0; /* розтягнути на весь батько */
background: rgba(0, 0, 0, 0.5);
}
/* Центрування через absolute */
.centered {
position: absolute;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
}
🧠 Ментальна модель: Media queries — це IF-оператори для CSS.
@media (min-width: 768px) { ... }= "ЯКЩО ширина вікна >= 768px, ТОДІ застосуй ці стилі". Браузер постійно перевіряє ці умови і миттєво перемикає стилі при resize вікна.📚 Чому Mobile First існує: Мобільний трафік давно перевищив десктопний. Крім того, Mobile First — краща архітектурна стратегія: базовий CSS для простого мобільного пристрою, потім ДОДАЄМО складності для більших екранів. Це простіше ніж починати з десктопу і СКАСОВУВАТИ складні стилі для мобільних. Менше скасувань = менше багів.
🌐 Як браузер обробляє це: Браузер завантажує ВСЕ CSS-правила завжди. Media queries не "не завантажуються" — вони просто не застосовуються. Тому
<link media="print">завантажується завжди, просто не застосовується до екрану.❌ Типова помилка початківця: Змішувати Desktop First і Mobile First в одному файлі — деякі правила через
max-width, деякі черезmin-width. Це створює непередбачувані конфлікти. Вибери ONE підхід і дотримуйся його. Bootstrap використовуєmin-width(Mobile First) — якщо пишеш разом з Bootstrap, роби так само.
11. Адаптивний дизайн — Media Queries¶
Media query — це умова для CSS. Стилі всередині @media застосовуються тільки якщо умова виконується. min-width = "від цього розміру і більше" (Mobile First). max-width = "до цього розміру" (Desktop First). Починай з Mobile First — це стандарт Bootstrap і сучасної веб-розробки.
/* Mobile First підхід (Bootstrap використовує цей!) */
/* За замовчуванням — стилі для мобільних */
.container { padding: 16px; }
.card { width: 100%; }
/* Планшет (≥768px) */
@media (min-width: 768px) {
.card { width: 48%; display: inline-block; }
}
/* Десктоп (≥992px) */
@media (min-width: 992px) {
.container { max-width: 960px; margin: 0 auto; }
.card { width: 30%; }
}
/* Великий екран (≥1200px) */
@media (min-width: 1200px) {
.container { max-width: 1140px; }
}
/* Орієнтація */
@media (orientation: landscape) { ... }
/* Темна тема */
@media (prefers-color-scheme: dark) {
body { background: #1a1a1a; color: #fff; }
}
/* Reduced motion (accessibility) */
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; transition: none !important; }
}
11b. Mobile First та Bootstrap Breakpoints¶
Архітектурна логіка Mobile First¶
Mobile First означає: базовий CSS пишеться для найменшого екрану, потім через min-width медіа-запити прогресивно розширюється для більших.
❌ Desktop First (застарілий підхід):
Пишеш для великих → override для малих → більше коду, більше конфліктів
✅ Mobile First (сучасний):
Пишеш для малих → додаєш для великих → менше коду, ніяких скасувань
Ось чому Mobile First простіший. При Desktop First ти пишеш складний layout для десктопу, а потім намагаєшся "спростити" його для мобільного через max-width — і часто забуваєш скасувати деякі правила. При Mobile First ти починаєш з простого (один стовпець) і ДОДАЄШ складність для більших екранів.
/* Mobile First: база — мобільний */
.card { width: 100%; padding: 12px; }
/* Планшет і більше */
@media (min-width: 768px) {
.card { width: 48%; padding: 16px; }
}
/* Десктоп і більше */
@media (min-width: 992px) {
.card { width: 31%; padding: 20px; }
}
Чому min-width, а не max-width?
/* max-width = Desktop First — ти скасовуєш стилі для маленьких */
@media (max-width: 768px) { ... }
/* min-width = Mobile First — ти додаєш стилі для великих */
@media (min-width: 768px) { ... }
Bootstrap 5 — Breakpoints¶
Bootstrap будує свою сітку на тих самих min-width медіа-запитах:
| Prefix | Breakpoint | min-width | Типовий пристрій |
|---|---|---|---|
| (none) | xs | 0px | Телефони (портрет) |
sm |
Small | 576px | Телефони (альбом) |
md |
Medium | 768px | Планшети |
lg |
Large | 992px | Десктопи |
xl |
X-Large | 1200px | Великі монітори |
xxl |
XX-Large | 1400px | Дуже великі екрани |
Bootstrap-класи читаються зліва направо як Mobile First декларації. col-12 = 100% на всіх екранах за замовчуванням. col-md-6 = 50% на планшетах і більше. col-lg-4 = 33% на десктопах і більше. Кожен наступний breakpoint перевизначає попередній.
<!-- Читання класів: Mobile First = зліва направо = менший → більший -->
<div class="col-12 col-md-6 col-lg-4">Контент</div>
Як браузер читає цей код:
Ширина 0–767px → col-12 → 12/12 = 100% (мобільний, 1 блок на рядок)
Ширина 768–991px → col-md-6 → 6/12 = 50% (планшет, 2 блоки на рядок)
Ширина 992px+ → col-lg-4 → 4/12 = 33% (десктоп, 3 блоки на рядок)
Префікс більшого екрана завжди перебиває менший, бо в CSS медіа-запит
з більшим min-width оголошений пізніше і має вищий пріоритет.
Prediction Quiz — Bootstrap Grid¶
Сценарій: у рядку .row — три однакових блоки:
<div class="row">
<div class="col-12 col-md-6 col-lg-4">Блок 1</div>
<div class="col-12 col-md-6 col-lg-4">Блок 2</div>
<div class="col-12 col-md-6 col-lg-4">Блок 3</div>
</div>
Питання: Скільки блоків у рядку на планшеті (800px) та моніторі (1200px)?
Варіанти: - А) Планшет — 1 блок (100%), монітор — 2 блоки (50%) - Б) Планшет — 2 блоки (50%), монітор — 3 блоки (33.3%) - В) На обох пристроях — 1 блок (col-12 завжди перемагає)
Відповідь: Б
800px → col-md-6 активний → кожен блок = 6/12 = 50%
6 + 6 = 12 → 2 блоки влазять, 3-й переноситься на новий рядок
1200px → col-lg-4 активний → кожен блок = 4/12 = 33.3%
4 + 4 + 4 = 12 → всі 3 блоки в одному рядку ✓
Чому В неправильна: col-12 — це base клас для xs (0px+).
Коли екран досягає 768px, col-md-6 перевизначає col-12 через вищий min-width.
Клас col-12 не "жорстко фіксує" — він просто база, яку перебивають більші breakpoints.
Адаптивні утиліти Bootstrap (відступи,
d-none d-lg-block,flex-column flex-md-rowтощо) описані в BOOTSTRAP_5.md §4 — Bootstrap Utilities.
12. CSS-змінні (Custom Properties)¶
CSS змінні (Custom Properties) — це іменовані значення, оголошені через -- і використовувані через var(). Їх перевага над Sass-змінними: вони живі (можна змінювати через JavaScript) і успадковуються по DOM. Оголошення в :root робить їх глобальними. Оголошення в компоненті — локальними.
/* Оголошення в :root — глобальні змінні */
:root {
--color-primary: #007bff;
--color-secondary: #6c757d;
--color-success: #28a745;
--color-danger: #dc3545;
--font-size-base: 16px;
--border-radius: 8px;
--shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
}
/* Використання */
.btn-primary {
background: var(--color-primary);
border-radius: var(--border-radius);
box-shadow: var(--shadow);
}
/* Перевизначення для компонента */
.dark-card {
--color-primary: #4dabf7; /* локальна версія */
}
13. Типографіка¶
Типографіка в CSS — це набагато більше ніж просто font-size. font-family з "стековим" набором гарантує запасний шрифт якщо основний не завантажиться. line-height: 1.5 без одиниць — краща практика: воно масштабується пропорційно з font-size. rem для розмірів шрифтів дозволяє користувачу масштабувати текст через налаштування браузера (accessibility).
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif;
font-size: 16px; /* базовий розмір */
line-height: 1.5; /* відстань між рядками (безрозмірне = x від font-size) */
color: #212529;
}
/* Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');
body { font-family: 'Roboto', sans-serif; }
h1 { font-size: 2.5rem; line-height: 1.2; }
h2 { font-size: 2rem; line-height: 1.3; }
h3 { font-size: 1.75rem; line-height: 1.4; }
p { font-size: 1rem; line-height: 1.6; margin-bottom: 1rem; }
/* Текстові утиліти */
.text-center { text-align: center; }
.text-uppercase { text-transform: uppercase; }
.text-truncate { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.text-muted { color: #6c757d; }
🧠 Ментальна модель: Holy Grail — це "Святий Грааль" верстки не просто так. Роками (1990-і — 2010-і) верстальники намагались реалізувати цей класичний макет (header + sidebar + main content + footer) чистим CSS — і не могли зробити це елегантно. Float-хаки, таблиці, абсолютне позиціонування — все давало або неповну функціональність, або жахливий код. CSS Grid нарешті вирішив задачу в 10 рядків.
📚 Чому macro/micro layout: Grid — для МАКРО (де знаходяться великі блоки сторінки). Flexbox — для МІКРО (як вміст розподіляється всередині блоку). Це не конкуренти — вони доповнюють один одного. Типова сторінка: Grid для page layout, Flexbox для navbar, Grid або Flexbox для карток, Flexbox для форм.
🌐 Як браузер обробляє це: Grid-контейнер і Flex-контейнер можуть бути вкладені один в одного.
.header { display: grid; ... }і всередині.header { display: flex; ... }— це нормально. Браузер обробляє кожен контейнер незалежно.❌ Типова помилка початківця: Намагатись зробити весь layout тільки Flexbox або тільки Grid. Правильна відповідь — поєднувати. Grid для загальної структури сторінки, Flexbox для компонентів всередині.
14. Holy Grail Layout — Класичний макет сторінки¶
Класичний патерн "Святий Грааль": Header + Sidebar + Content + Footer. Найкраще пояснює поєднання CSS Grid (макро) і Flexbox (мікро).
HTML — тільки структура, без позиціонування¶
HTML в цьому патерні — чиста семантика. Ніяких float, position, або inline-стилів. Весь layout — в CSS. Зверни увагу: порядок елементів в HTML може не відповідати візуальному порядку — Grid дозволяє розміщувати їх де завгодно.
<div class="app-layout">
<header class="header">Логотип та Навігація</header>
<aside class="sidebar">Бокове меню</aside>
<main class="content">Основний вміст</main>
<footer class="footer">Копірайт</footer>
</div>
CSS Grid — макро-архітектура (будівля)¶
Grid визначає "несучі стіни" — де знаходяться великі блоки. 1fr означає "весь залишковий простір". grid-column: 1 / -1 означає "від першої до останньої лінії" — тобто на всю ширину. -1 — індекс з кінця, як від'ємні індекси у Python.
.app-layout {
display: grid;
/* 2 колонки: фіксована ліва + гнучка права */
grid-template-columns: 250px 1fr;
/* 3 рядки: header | content | footer */
grid-template-rows: 60px 1fr 50px;
min-height: 100vh; /* вся висота екрану */
}
/* Header і footer — на всю ширину (обидві колонки) */
.header { grid-column: 1 / 3; } /* від 1-ї до 3-ї вертикальної лінії */
.footer { grid-column: 1 / 3; }
/* sidebar і content — по одній колонці (за замовчуванням) */
/* sidebar → колонка 1, content → колонка 2 */
Як браузер рахує 1fr:
1. Фіксований простір: 250px + 60px + 50px
2. 1fr = весь залишок viewport — це безпечніше ніж calc(100% - 250px)
+---------------------------------------+
| HEADER | ← 60px, span 2 columns
+-----------+---------------------------+
| | |
| SIDEBAR | CONTENT | ← 1fr (займає залишок висоти)
| (250px) | (1fr) |
| | |
+-----------+---------------------------+
| FOOTER | ← 50px, span 2 columns
+---------------------------------------+
Flexbox — мікро-архітектура (меблі в кімнаті)¶
Grid задав розміри шапки. Тепер Flexbox розставляє вміст всередині шапки. Це ключовий патерн: Grid + Flexbox на одному елементі одночасно — Grid grid-area властивість контролює ЯК елемент розміщується в grid, а display: flex контролює ЯК вміст всередині елемента розподіляється.
.header {
grid-column: 1 / 3; /* Grid: займає обидві колонки */
display: flex; /* Flexbox: розставляємо вміст всередині */
justify-content: space-between; /* логотип зліва, меню справа */
align-items: center; /* вертикально по центру */
padding: 0 20px;
background: #2c3e50;
color: white;
}
Ключова інтуїція: Grid керує тим, ЯКИЙ РОЗМІР займає шапка відносно всієї сторінки. Flexbox керує тим, як ВМІСТ ШАПКИ розподіляється всередині неї.
Адаптивність — Mobile First¶
На мобільному немає місця для sidebar поруч з content — вони мають бути в стовпець. Ця адаптивна версія перемикається між одноколоночним (мобільний) і двоколоночним (десктоп) layout через media query. Браузер виконує Reflow при кожній зміні розміру вікна.
/* Мобільний (за замовчуванням) — одна колонка */
.app-layout {
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 60px auto 1fr 50px;
min-height: 100vh;
}
.header, .footer { grid-column: 1 / 2; }
/* Десктоп (≥768px) — дві колонки */
@media (min-width: 768px) {
.app-layout {
grid-template-columns: 250px 1fr;
grid-template-rows: 60px 1fr 50px;
}
.header { grid-column: 1 / 3; }
.footer { grid-column: 1 / 3; }
}
Що відбувається при resize: Коли viewport < 768px → браузер перебудовує (Reflow) сітку на льоту. Sidebar стає вище content — вертикальний стек для мобільного.
Sticky Header з z-index¶
Sticky header прилипає до верху при прокрутці. z-index: 100 гарантує, що він буде поверх sidebar і content. Важливо: z-index без position не працює. Sticky header автоматично отримує position: sticky, тому z-index тут діє.
.header {
grid-column: 1 / 3;
position: sticky; /* прилипає при прокрутці */
top: 0;
z-index: 100; /* поверх sidebar і content під час прокрутки */
background: #2c3e50;
}
/* Важливо: z-index працює тільки на позиційованих елементах
(position: relative/absolute/fixed/sticky) */
15. Практичні патерни¶
Картка (card)¶
Картка — найпоширеніший UI-компонент. Ці стилі демонструють декілька важливих технік: border-radius для заокруглених кутів, box-shadow для тіні глибини, overflow: hidden щоб зображення не виходило за rounded corners, transition для плавної анімації при hover, і transform: translateY для ефекту "підняття" картки — дешева анімація що не тригерить reflow.
.card {
background: #fff;
border-radius: 12px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
.card-img { width: 100%; height: 200px; object-fit: cover; }
.card-body { padding: 16px 20px; }
.card-title { font-size: 1.25rem; font-weight: 600; margin-bottom: 8px; }
.card-text { color: #666; line-height: 1.6; }
Кнопка¶
Кнопка з display: inline-flex — сучасний підхід: дозволяє вирівнювати іконки і текст по вертикалі через align-items: center. transition: all 0.2s ease додає плавність. transform: translateY(-1px) при hover дає ефект натискання. cursor: pointer — важливо для <button> і <a> що стилізовані як кнопки.
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 1rem;
font-weight: 500;
cursor: pointer;
text-decoration: none;
transition: all 0.2s ease;
}
.btn-primary { background: #007bff; color: #fff; }
.btn-primary:hover { background: #0056b3; transform: translateY(-1px); }
.btn-primary:active { transform: translateY(0); }
🧠 Ментальна модель: CSS дебагінг — це детективна робота. У тебе є "злочин" (стиль не застосовується або виглядає неправильно) і є докази (DevTools). Твоє завдання: знайти "злочинця" (правило, що перемагає) через систематичне звуження кола підозрюваних.
📚 Чому це існує: CSS — декларативна мова без виключень і стектрейсів. Браузер не каже "помилка в рядку 42". DevTools — єдиний інструмент, що дає видимість у те, що браузер реально застосовує. Без DevTools CSS дебагінг — це сліпа гра.
🌐 Як браузер обробляє це: Вкладка Styles в DevTools показує ВСІ правила для елемента, відсортовані по специфічності, з перекресленими тими що програли. Computed — показує фінальне значення після вирішення всього каскаду. Layout — Box Model візуально.
❌ Типова помилка початківця: Змінювати CSS наосліп, перезавантажувати сторінку і дивуватись. Завжди починай з DevTools. 5 хвилин в DevTools заощаджують 50 хвилин сліпих правок.
16. Дебагінг CSS — Debugging Intuition¶
Перш ніж читати алгоритм — запам'ятай систематичний чеклист для будь-якої CSS-проблеми. Слідуй цим крокам по порядку:
CSS не працює? Слідуй цьому чеклисту:
1. Hard reload: Ctrl+Shift+R (очищає кеш — найчастіша причина "старих" стилів)
2. F12 → Elements → клікни на елемент
3. Перевір панель Styles: чи є твоє правило там?
- Немає → CSS-файл не завантажено (перевір Network tab)
- Перекреслено → конфлікт специфічності (перевір яке правило перемогло)
4. Перевір панель Computed → побач ФІНАЛЬНЕ значення
5. Перевір Box Model у панелі Layout
Алгоритм дебагінгу: "Стиль не застосовується"¶
Стиль не працює → ти змінив CSS, але браузер показує старе?
│
├── 1. Чи файл зберігається?
│ Ctrl+S → оновити браузер (Ctrl+Shift+R — hard reload без кешу)
│
├── 2. Відкрий DevTools (F12) → вкладка Elements → клікни на елемент
│
├── 3. Дивись на вкладку Styles (праворуч)
│ ┌─────────────────────────────────────┐
│ │ .card { ... } │ ← активне правило
│ │ ~~p { color: black; }~~ │ ← перекреслено = програло
│ │ ~~.old-class { padding: 5px; }~~ │ ← перекреслено = скасовано
│ └─────────────────────────────────────┘
│
├── 4. Якщо твоє правило перекреслено → специфічність нижча
│ Подивись що перемагає → збільш специфічність або перевір порядок
│
├── 5. Якщо правила взагалі немає в Styles → CSS не завантажено
│ F12 → Network → знайди свій .css файл → статус 200? 404?
│ Django: чи підключено {% load static %} + {% static 'css/...' %}?
│
└── 6. Вкладка Computed → фінальне значення після всього каскаду
"Which properties actually apply to this element right now?"
DevTools — покрокова інструкція¶
F12 → Elements tab:
┌─── HTML структура ──────────────┬─── CSS правила ──────────────────┐
│ ▼ <body> │ Styles Computed Layout ... │
│ ▼ <main> │ │
│ ▼ <div class="card"> ←──── │ element.style { } │
│ <h2>Заголовок</h2> │ │
│ <p>Текст</p> │ .card { │
│ │ border-radius: 12px; │
│ │ box-shadow: ...; │
│ │ } │
│ │ │
│ │ ~~div { color: red; }~~ │
│ │ ↑ перекреслено = програло │
└─────────────────────────────────┴──────────────────────────────────┘
Box Model у вкладці Layout:
┌──────────────────────────────────────────┐
│ margin: 16px │
│ ┌──────────────────────────────────┐ │
│ │ border: 1px │ │
│ │ ┌──────────────────────────┐ │ │
│ │ │ padding: 20px │ │ │
│ │ │ ┌──────────────────┐ │ │ │
│ │ │ │ 300 × 150 px │ │ │ │
│ │ │ └──────────────────┘ │ │ │
│ │ └──────────────────────────┘ │ │
│ └──────────────────────────────────┘ │
└──────────────────────────────────────────┘
Клікни на будь-яке число в Box Model — зміниш значення прямо в DevTools.
Типові CSS-баги та їх причини¶
| Симптом | Причина | Рішення |
|---|---|---|
| Стиль не застосовується | Нижча специфічність | DevTools → Styles → знайди правило що перемогло |
width: 200px але елемент ширший |
box-sizing: content-box |
Додай box-sizing: border-box |
margin-top не працює між двома div |
Margin collapse (злиття відступів) | Використай padding, overflow: auto, або display: flex |
z-index не піднімає елемент |
Немає position: relative/absolute/fixed |
Додай position: relative |
| Flex-дочірній розтягнутий по висоті | align-items: stretch (за замовчуванням) |
align-items: flex-start або align-self: flex-start |
inline-block елементи мають зазор |
Whitespace між тегами в HTML | Видали пробіли, або font-size: 0 на батьку |
overflow: hidden обрізає тінь |
box-shadow виходить за межі overflow |
Перенеси тінь на батьківський елемент |
position: sticky не "прилипає" |
Батько має overflow: hidden/auto |
Прибери overflow з батька |
| CSS-анімація тримтить на мобільних | Анімується top/left замість transform |
Заміни на transform: translateX/Y |
Margin Collapse — найпідступніший баг¶
Очікування: відстань між блоками = 30 + 20 = 50px Реальність: відстань = max(30, 20) = 30px — відступи зливаються!
Margin Collapse відбувається між:
- сусідніми block-елементами по вертикалі
- батьком і першим/останнім дочірнім елементом (якщо немає border/padding)
Не відбувається:
- в flex-контейнерах
- в grid-контейнерах
- між inline-block елементами
- якщо між ними є border або padding
Рішення:
/* Варіант 1: додай padding замість margin */
.parent { padding-top: 1px; } /* розриває злиття */
/* Варіант 2: зроби батька flex-контейнером */
.parent { display: flex; flex-direction: column; }
/* Варіант 3: overflow (хак, уникати) */
.parent { overflow: auto; }
Stacking Context — контекст накладання (z-index)¶
z-index не глобальний — він працює всередині свого stacking context.
<div style="position: relative; z-index: 1;"> <!-- Контекст A -->
<div style="position: relative; z-index: 999;">
<!-- z-index=999, але він всередині контексту A (z-index=1) -->
</div>
</div>
<div style="position: relative; z-index: 2;"> <!-- Контекст B -->
<div style="position: relative; z-index: 1;">
<!-- z-index=1, але весь контекст B (z-index=2) поверх A -->
</div>
</div>
Результат: Елемент з z-index: 1 з контексту B буде поверх елемента з z-index: 999 з контексту A, тому що контекст B (z-index: 2) вище за контекст A (z-index: 1).
Що створює новий stacking context:
- position + z-index (не auto)
- opacity < 1
- transform, filter, clip-path
- will-change
- isolation: isolate
/* Ізоляція компонента від глобального z-index */
.modal-wrapper {
isolation: isolate; /* ← явно створюємо контекст, захищає від витікання */
}
🧠 Ментальна модель: Продуктивність CSS — це про те, наскільки дорогі операції ти змушуєш браузер виконувати. Деякі CSS-зміни коштують 1 одиницю роботи (opacity), деякі — 1000 одиниць (зміна width перераховує весь layout). Знаючи "вартість", ти можеш писати анімації і стилі, що не "гальмують" браузер.
📚 Чому CSS performance важливо для Django: Якщо твій CSS-файл 500KB (незмінений Bootstrap + кастомні стилі), користувач бачить білий екран ВСЕ цей час завантаження, бо CSS блокує рендеринг. Рішення: мінімізуй (видаляй пробіли/коментарі), використовуй тільки потрібні компоненти Bootstrap, підключай критичний CSS inline, а решту — async.
🌐 GPU layers: Деякі CSS властивості (transform, opacity, filter) запускають на GPU (Graphics Processing Unit) замість CPU. GPU спеціалізований на паралельній обробці пікселів — анімації на GPU набагато плавніші (60fps vs 15fps).
will-change: transform"просить" браузер перемістити елемент на GPU layer заздалегідь.❌ Типова помилка початківця: Анімувати
top/left/width/heightзамістьtransform.top: 0 → top: 100pxтригерить Layout (найдорожча операція).transform: translateY(0) → translateY(100px)обробляється GPU без Layout — у десятки разів дешевше.
17. CSS Performance — Що відбувається під капотом¶
Critical Rendering Path¶
Ще раз про Critical Rendering Path — тепер з фокусом на продуктивність. CSS — "рендер-блокуючий" ресурс. Браузер не намалює нічого, поки не завантажить і не обробить весь CSS у <head>. Тому: мінімізуй розмір CSS, використовуй CDN для Bootstrap (кешується між сайтами), розглянь Critical CSS (inline найважливіший CSS прямо в HTML).
Блокуючий рендеринг: <link rel="stylesheet"> блокує рендеринг до завантаження CSS.
Тому Bootstrap CDN підключають в <head> — браузер не малює нічого, поки не знає стилі.
Властивості по вартості¶
Знання "вартості" CSS-властивостей допомагає писати ефективні анімації. Загальне правило: якщо CSS-зміна вимагає перерахунку позицій інших елементів (Layout/Reflow) — це дорого. Якщо тільки перемальовує пікселі без зміни позицій — дешевше. Якщо обробляється GPU без участі CPU — найдешевше.
Дешево (не тригерить layout):
opacity, transform, filter, color, background-color
Дорого (тригерить Reflow — перерахунок позицій):
width, height, margin, padding, font-size, border,
top, left, right, bottom
Найдорожче (тригерить синхронний reflow в JS):
el.offsetHeight, el.clientWidth, el.getBoundingClientRect()
Ось конкретний приклад чому transform ефективніший за left для анімацій. Обидва виглядають однаково для користувача, але один навантажує CPU і тригерить Layout, а інший — легкий GPU-рендеринг.
/* ❌ Анімація яка тригерить Layout → дорого */
@keyframes slide { from { left: 0; } to { left: 100px; } }
/* ✅ Анімація тільки на Composite layer → дешево */
@keyframes slide { from { transform: translateX(0); } to { transform: translateX(100px); } }
will-change — підказка браузеру¶
will-change — це "попередження" браузеру: "цей елемент скоро зміниться". Браузер може заздалегідь перемістити його на GPU layer, що робить майбутню анімацію плавнішою. Але зловживати не можна: кожен GPU layer споживає відеопам'ять. Додавай will-change тільки до елементів що БУДУТЬ анімуватись, і прибирай після завершення анімації.
/* Перемістити елемент на GPU layer заздалегідь */
.animated-card {
will-change: transform; /* браузер промотуватиме на GPU layer */
}
/* Прибери will-change коли анімація завершилась — вона споживає пам'ять GPU */
18. Питання для самоперевірки¶
- Браузер отримав HTML і CSS одночасно. Що будується першим — DOM чи CSSOM?
- Яка різниця між
display: noneіvisibility: hidden? Який тригерить reflow? - Є два правила:
.card p { color: blue; }і#main .card p { color: red; }. Яке переможе? - Чому
margin: 0 autoцентрує блок горизонтально, але не вертикально? - Елемент з
position: absoluteтаz-index: 999ховається за елемент зz-index: 1. Чому? - Чому
width: 100%на flex-дочірньому елементі іноді не займає весь рядок? @media (min-width: 768px)проти@media (max-width: 768px)— яка різниця в підходах?- Є клас Bootstrap
col-12 col-md-6. На екрані 900px — яка ширина? - Чому
transition: all 0.3sє поганою практикою для продуктивності? - Які три властивості CSS можна анімувати без тригеру reflow?