10 березня 2026 р.
Сучасна веб-верстка майже повністю тримається на CSS Flexbox, Grid та технологіях трансформації елементів. Однак браузерні рушії (Blink, WebKit, Gecko) мають специфічні відмінності та особливості обчислення розмірів, які періодично призводять до виникнення візуальних багів. Елементи можуть раптово зміщуватися, контент виходити за межі екрана, а правила z-index відмовлятися працювати в очікуваному порядку.
Бездумне підбирання CSS-властивостей в інспекторі браузера - це неефективний шлях. У цьому посібнику ми проаналізуємо логіку рендерингу, що стоїть за п'ятьма найпоширенішими проблемами верстки, та розберемо професійні методи їх швидкого усунення.
Поява небажаного горизонтального скролу на мобільному макеті є однією з найпоширеніших проблем адаптивної верстки. Це відбувається, коли сумарна ширина дочірніх елементів перевищує ширину в'юпорту. Оскільки шукати винуватця вручну у великому дереві DOM складно, ми можемо автоматизувати цей процес за допомогою консолі браузера.
Відкрийте інструменти розробника, перейдіть на вкладку Console та запустіть наступний JavaScript-код:
// Знайти всі елементи, які виходять за межі ширини тіла документа
document.querySelectorAll('*').forEach(el => {
const rect = el.getBoundingClientRect();
if (rect.right > window.innerWidth) {
console.log('Елемент переповнення:', el, `Права координата: ${rect.right}px`);
}
});
Цей скрипт обходить усі вузли DOM-дерева та виводить у консоль ті елементи, які виступають за межі екрана. При наведенні курсору на виведені вузли в консолі браузер підсвітить їх безпосередньо на сторінці.
*, *::before, *::after {
box-sizing: border-box;
}
img, video {
max-width: 100%;
height: auto;
}
.container {
width: 100%;
max-width: 1200px;
margin: 0 auto;
}
min-width: autoПоширена проблема з Flexbox виникає, коли флекс-елемент містить довге слово, нерозривну URL-адресу або великий блок коду. Замість того щоб перенести текст на новий рядок або зменшитися, елемент розширюється і виходить за межі батьківського контейнера, ламаючи всю сітку.
Згідно зі специфікацією W3C Flexbox, мінімальна ширина флекс-елемента за замовчуванням дорівнює не 0, а min-width: auto. Браузерні двигуни розраховують цей розмір на основі мінімального розміру контенту дочірніх елементів. Якщо дочірній елемент містить рядок тексту, який не можна перенести (наприклад, довге посилання), флекс-елемент відмовиться стискатися менше довжини цього рядка, ігноруючи ваші правила розподілу простору.
Переповнення Flexbox через довгий текст
+--------------------------------------------------------+
| Батьківський блок Flex (ширина: 400px) |
+------------------------------------+-------------------+
| Елемент A (ширина: 100px) | Елемент B |
| | (текст вилазить) |
| | "https://example.com/very/long/url..."
+------------------------------------+-------------------+
<-- Переповнення тут!
Щоб змусити флекс-елемент ігнорувати мінімальну ширину контенту та стискатися під розмір контейнера, встановіть для нього явне обмеження мінімальної ширини в 0:
.flex-child-container {
min-width: 0; /* Дозволяє елементу стискатися менше за його контент */
overflow: hidden; /* Або text-overflow: ellipsis для обрізання тексту */
}
Для вертикальних макетів діють такі ж правила: елементи мають значення за замовчуванням min-height: auto, яке можна скинути за допомогою min-height: 0.
minmax()CSS Grid надає неймовірні можливості контролю сітки, але розробники часто стикаються з розширенням колонок при інтеграції динамічних текстових елементів.
Наприклад, розглянемо таку структуру сітки:
.grid-container {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
}
Якщо один із елементів сітки містить довгий блок коду в тегу <pre> або широку таблицю, колонка сітки розшириться далеко за межі пропорції 1fr, виштовхнувши сусідні колонки за рамки екрана.
Одиниця виміру 1fr розподіляє вільний простір, що залишився. Але при розрахунках браузер спершу перевіряє мінімальний розмір контенту. Якщо цей мінімальний розмір більше за 300px, V8 вибере саме його, щоб уникнути обрізання інформації.
Ви можете контролювати поведінку колонок сітки, додавши обмеження мінімальної ширини для елементів:
.grid-item {
min-width: 0; /* Запобігає неконтрольованому розширенню картки */
}
Щоб побачити межі колонок, відкрийте Chrome DevTools, знайдіть контейнер сітки та натисніть на позначку Grid в панелі елементів. Це ввімкне наочну візуалізацію сітки безпосередньо на сторінці.
Типова ситуація з z-index: ви задаєте для спливаючого вікна z-index: 99999, але воно все одно ховається під шапкою сайту, яка має z-index: 2.
Контекст накладання (Stacking Context) - це тривимірна ієрархія шарів, яку формує браузер під час рендерингу. Як тільки елемент створює новий контекст накладання, всі його дочірні елементи рендеряться виключно всередині цього локального шару. Вони не можуть порівнюватися за рівнем z-index з елементами з інших шарів.
Новий контекст накладання створюється у таких випадках:
position: relative або absolute разом із z-index, який не дорівнює auto.position: fixed або sticky.opacity менше 1.transform, filter, perspective або clip-path.will-change з будь-яким значенням, що впливає на рендеринг. Ієрархія контекстів накладання
Базовий контекст (Viewport)
├── Контекст A (z-index: 1)
│ └── Елемент A1 (z-index: 9999) <-- Заблокований всередині Контексту A!
│
└── Контекст B (z-index: 2)
└── Елемент B1 (z-index: 1) <-- Відображається поверх A1!
Оскільки Контекст B знаходиться вище за Контекст A, всі дочірні об'єкти Контексту B відображатимуться поверх об'єктів Контексту A, незалежно від того, які локальні значення z-index ви задасте для елементів на кшталт A1.
У браузері Safari (WebKit) є додаткова особливість: елементи з підтримкою апаратного прискорення (transform: translate3d(...)) можуть пробивати стандартні контексти накладання. Щоб вирішити цю проблему, додайте transform-style: flat до батьківських контейнерів або примусово оновіть шар для потрібного елемента:
.target-element {
transform: translate3d(0, 0, 0);
will-change: transform; /* Змушує Safari перерахувати індекси шарів */
}
100vh в iOSПри створенні інтерфейсів на весь екран використання height: 100vh на мобільних пристроях часто призводить до неприємного багу. Нижня частина вашого екрана ховається під динамічною навігаційною панеллю браузера Safari чи Chrome на iOS, закриваючи важливі кнопки.
Специфікація W3C визначає висоту в'юпорту (vh) як статичну величину. Розробники мобільних браузерів вирішили зробити 100vh рівним висоті екрана при повністю прихованій панелі навігації. Коли користувач заходить на сайт і панель відображається, вона просто перекриває нижню частину сторінки.
vh проти svh/dvh на мобільних пристроях
+-------------------------------+ <-- Верхня межа екрана
| |
| |
| Контент сторінки |
| |
| |
+-------------------------------+ <-- 100svh (Нижня межа при відкритій панелі)
| Панель навігації браузера |
+-------------------------------+ <-- 100vh / 100lvh (Межа при прихованій панелі)
Специфікація CSS CSS Values and Units Module Level 4 додала нові одиниці виміру для розв'язання цієї проблеми:
svh (Small Viewport Height): Висота в'юпорту при відкритих панелях браузера (найбезпечніший вибір для інтерфейсів).lvh (Large Viewport Height): Висота при повністю прихованих панелях.dvh (Dynamic Viewport Height): Динамічна висота, яка змінюється в реальному часі, коли користувач гортає сторінку і панель приховується чи з'являється.Для підтримки старих пристроїв використовуйте такий каскад стилів:
.full-screen-container {
height: 100vh; /* Для старих браузерів */
height: 100dvh; /* Динамічне підлаштування для сучасних мобільних браузерів */
}
Чи стикалися ви з тим, що при розміщенні трьох колонок шириною рівно 33.33% третя колонка несподівано переносилася на новий рядок, або між сусідніми кольоровими блоками з'являлася тонка напівпрозора лінія? Цей баг викликаний субпіксельними округленнями.
Фізичний екран складається з цілих пікселів. Але координати макету в CSS часто є дробовими числами (наприклад, 350.67px). При виведенні цих значень на екран браузер змушений округлювати їх до цілих значень.
Різні браузерні рушії роблять це по-різному:
.adjacent-block {
margin-right: -1px; /* Створює мінімальний нахлест для маскування щілини */
}
calc(), відніміть мікроскопічну частку пікселя, щоб уникнути перенесення:
.column {
width: calc(33.33% - 0.1px);
}
Розуміння логіки роботи браузерних рушіїв при обчисленні розмірів та шарів дозволить вам швидко знаходити джерела багів та створювати надійну і стабільну верстку.