Що таке злиття відступів у CSS
Margin collapsing (злиття відступів) - це поведінка CSS, коли два вертикальних відступи стикаються і замість їх суми береться лише більший.
Теорія
TL;DR
- Зливаються тільки
margin-topіmargin-bottom. Горизонтальні відступи ніколи не зливаються. - Три сценарії: сусідні блоки, батько і перший/останній нащадок, порожні елементи.
- Перемагає більший відступ. Якщо обидва рівні, результат дорівнює одному з них.
- Flexbox, Grid і
overflow: hiddenвимикають злиття. - Тільки нормальний потік. Абсолютно позиціоновані та плаваючі елементи не зливаються.
Швидкий приклад
.card-top {
margin-bottom: 40px;
}
.card-bottom {
margin-top: 24px;
}
/* Фактичний проміжок між ними: 40px, не 64px */Браузер бере більше значення. 24px просто зникають.
Три сценарії злиття
Сусідні блоки - найпоширеніший випадок. Коли margin-bottom одного блока межує з margin-top наступного, вони зливаються в один відступ. Саме це дивує розробників вперше, коли вони задають відступи між картками або прев'ю статей.
Батько і перший/останній нащадок - складніший сценарій. Якщо в батьківського елемента немає padding-top, border-top і жодного inline-контенту перед першим нащадком, margin-top нащадка зливається з margin-top батька. Відступ ніби вистрибує з батьківського блока. Те саме відбувається знизу з останнім нащадком.
<section> <!-- немає padding або border -->
<p style="margin-top: 32px;">Перший абзац</p>
</section>Весь section зсувається вниз на 32px. Абзац всередині не рухається відносно section.
Порожні блоки зливають свій верхній і нижній відступ в один. В сучасному коді це рідко стає проблемою, але пояснює дивні пробіли в старій розмітці.
Як зупинити злиття
Для сценарію батько-нащадок будь-який з цих варіантів на батьківському елементі:
padding-topабоpadding-bottomborder(навіть1px solid transparent)overflow: hiddenабоoverflow: autodisplay: flow-root(найчистіший варіант, без побічних ефектів)display: flexабоdisplay: grid
Для злиття сусідніх блоків рішення знаходиться на рівні контейнера. Перевести обгортку в display: flex з gap - і відступи більше не зливатимуться.
Типові помилки
Найпоширеніша: розробник додає margin-top: 40px до заголовка всередині section, а потім дивується чому весь section зсунувся вниз, а не заголовок. Це злиття батька з нащадком. Одна строчка виправляє: display: flow-root на батьківському елементі.
Ще часта ситуація: два компоненти з margin-bottom: 30px і margin-top: 30px відповідно. Розробник очікує 60px між ними, отримує 30px. Нічого не зламалось - злиття працює за специфікацією. Я бачив, як це викликало тривалий дебагінг при передачі дизайну, де відступи збігалися з Figma, але розробник очікував що маржини підсумовуватимуться.
Від'ємні відступи мають свої правила. Якщо один відступ -20px, а інший 30px, результат буде 10px (вони складаються, коли знаки різні). Два від'ємних відступи дають найбільший за модулем від'ємний.
Follow-up питання
Q: Чи зливаються відступи всередині flex або grid контейнера?
A: Ні. Обидва формати створюють новий контекст форматування (block formatting context), який вимикає злиття для прямих нащадків.
Q: Що відбувається, якщо один відступ від'ємний?
A: Від'ємний і позитивний відступи складаються. Тобто 30px і -10px дадуть 20px. Два від'ємних дають найбільший за модулем від'ємний.
Q: display: flow-root кращий за overflow: hidden для цього?
A: Для цієї конкретної задачі - так. overflow: hidden обрізає контент, що виходить за межі батька, що зазвичай небажано. display: flow-root створює контекст форматування без жодного обрізання.
Q: Чи можуть горизонтальні відступи зливатись?
A: Ні. Специфікація CSS описує злиття тільки для вертикальних відступів у нормальному потоці.
Приклади
Відступи між сусідами, які не підсумовуються
<article class="post">...</article>
<article class="post">...</article>.post {
margin-top: 32px;
margin-bottom: 32px;
}
/* Проміжок між двома постами: 32px, а не 64px */Кожен пост оголошує 32px з обох боків, але між будь-якими двома постами тільки 32px загалом, не 64px. Для передбачуваних відступів у карткових layouts краще використовувати gap на flex або grid контейнері.
Маржин нащадка, що вистрибує з батька
.hero {
background: #f0f0f0;
/* Без padding або border */
}
.hero h1 {
margin-top: 48px;
}<section class="hero">
<h1>Вітаємо</h1>
</section>margin-top у h1 зливається з margin-top section. Весь section зсувається вниз на 48px, а не тільки заголовок. Додати padding-top: 1px до .hero або виставити display: flow-root - і відступ залишиться всередині.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.