Верстка адаптивної мобільної версії сайту (Responsive Design)
Адаптивна верстка — не додавання max-width: 100% до зображень. Це проектування інтерфейсу, який працює коректно при ширині від 320px до 2560px+, з різними щільностями пікселів, різними методами введення (торкання vs мишка) та різними умовами мережі.
Mobile-first: чому важливий порядок
Mobile-first — базові стилі пишуться для мобільних, розширюються через min-width медіазапити. Альтернатива — Desktop-first з max-width — важче підтримувати через нарощування перевизначення стилів.
/* Mobile-first: базові стилі для мобільних */
.card-grid {
display: grid;
grid-template-columns: 1fr; /* 1 колонка на мобільних */
gap: 16px;
}
/* Розширюємо для планшета */
@media (min-width: 640px) {
.card-grid {
grid-template-columns: repeat(2, 1fr);
gap: 20px;
}
}
/* Розширюємо для десктопа */
@media (min-width: 1024px) {
.card-grid {
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
}
Точки зупинки (breakpoints)
Точки зупинки мають визначатися контентом, а не пристроями. Немає сенсу орієнтуватися на конкретні моделі iPhone — у кожного нового покоління інші розміри.
Типова система:
| Імя | Значення | Контекст |
|---|---|---|
xs |
320px | Мінімальний підтримуваний розмір |
sm |
640px | Великі телефони, маленькі планшети |
md |
768px | Планшети в портретній орієнтації |
lg |
1024px | Планшети в альбомній, маленькі ноутбуки |
xl |
1280px | Ноутбуки, десктопи |
2xl |
1536px | Широкі екрани |
У CSS-змінних або Sass:
:root {
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
}
Viewport та одиниці вимірювання
<!-- Обов'язковий мета-тег -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
Без цього тега браузер на мобільному рендерить сторінку як 980px широку та масштабує її — медіазапити не працюють як очікується.
dvh замість vh для мобільних:
/* vh не враховує адресну строку браузера на мобільних */
.hero { min-height: 100vh; } /* Може бути обрізано */
/* dvh — динамічна висота viewport */
.hero { min-height: 100dvh; } /* Правильно на мобільних */
svh / lvh — small viewport height (з прихованою адресною строкою) та large viewport height (з видимою). dvh змінюється при скролі, svh/lvh — фіксовані крайні значення.
Адаптивна типографіка
/* Плинна типографіка без медіазапитів */
:root {
--text-base: clamp(1rem, 0.85rem + 0.75vw, 1.125rem);
--text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.375rem);
--text-xl: clamp(1.375rem, 1rem + 1.875vw, 2rem);
--text-2xl: clamp(1.75rem, 1.25rem + 2.5vw, 2.75rem);
--text-3xl: clamp(2rem, 1.25rem + 3.75vw, 3.5rem);
}
body { font-size: var(--text-base); }
h1 { font-size: var(--text-3xl); }
h2 { font-size: var(--text-2xl); }
clamp(min, preferred, max) — шрифт масштабується плавно між min та max залежно від ширини екрана.
Зона торкання (Touch targets)
На мобільних кликабельна область повинна бути не менше 44×44px (Apple HIG) або 48×48px (Google Material). Візуальний розмір може бути менший:
.icon-btn {
width: 24px;
height: 24px;
position: relative;
cursor: pointer;
}
/* Збільшуємо зону касання не змінюючи візуал */
.icon-btn::before {
content: '';
position: absolute;
inset: -12px; /* Розширюємо на 12px в кожну сторону = 48×48px зона */
}
Або через padding:
.nav__link {
display: inline-flex;
align-items: center;
padding: 12px 16px; /* Достатня зона касання */
min-height: 44px;
}
Адаптивні зображення
<!-- art direction: різні кадри для різних розмірів -->
<picture>
<source
media="(min-width: 1024px)"
srcset="hero-desktop.webp 1440w, hero-desktop-2x.webp 2880w"
sizes="100vw"
>
<source
media="(min-width: 640px)"
srcset="hero-tablet.webp 768w, hero-tablet-2x.webp 1536w"
>
<img
src="hero-mobile.webp"
srcset="hero-mobile.webp 390w, hero-mobile-2x.webp 780w"
alt="Hero image"
width="390"
height="520"
loading="eager"
fetchpriority="high"
>
</picture>
Для всіх інших зображень — srcset + sizes:
<img
src="product-400.webp"
srcset="product-400.webp 400w, product-800.webp 800w, product-1200.webp 1200w"
sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw"
alt="Назва товару"
width="400"
height="400"
loading="lazy"
>
sizes говорить браузеру, яку долю екрана займе зображення при даній ширині вікна — це дозволяє вибрати правильний файл до завантаження CSS.
Адаптивна навігація
Hamburger-меню для мобільних:
const Header: React.FC = () => {
const [menuOpen, setMenuOpen] = useState(false);
return (
<header className="header">
<a href="/" className="header__logo">Logo</a>
{/* Десктопна навігація */}
<nav className="header__nav header__nav--desktop" aria-label="Основна навігація">
<NavLinks />
</nav>
{/* Мобільний тоггл */}
<button
className="header__burger"
aria-label={menuOpen ? 'Закрити меню' : 'Відкрити меню'}
aria-expanded={menuOpen}
aria-controls="mobile-menu"
onClick={() => setMenuOpen(o => !o)}
>
<BurgerIcon open={menuOpen} />
</button>
{/* Мобільне меню */}
<nav
id="mobile-menu"
className={`header__nav header__nav--mobile ${menuOpen ? 'is-open' : ''}`}
aria-hidden={!menuOpen}
>
<NavLinks onClose={() => setMenuOpen(false)} />
</nav>
</header>
);
};
.header__nav--desktop {
display: none;
}
.header__burger {
display: flex;
}
@media (min-width: 1024px) {
.header__nav--desktop { display: flex; }
.header__burger { display: none; }
.header__nav--mobile { display: none !important; }
}
.header__nav--mobile {
position: fixed;
inset: 0;
background: var(--color-bg-primary);
transform: translateX(100%);
transition: transform 300ms ease;
}
.header__nav--mobile.is-open {
transform: translateX(0);
}
Продуктивність на мобільних
Мобільні мережі повільніші за десктопні. Критично:
-
Lazy loading для всього що ниже сгиба:
loading="lazy"на зображеннях -
Font subsetting: для кирилиці достатньо
latin + cyrillicпідмножини, не потрібен весь шрифт -
CSS containment:
contain: layout paintна секціях для прискорення рендерингу - Preload тільки критичних ресурсів першого екрана
<link rel="preload" href="hero-mobile.webp" as="image"
imagesrcset="hero-mobile.webp 390w, hero-mobile-2x.webp 780w"
imagesizes="100vw"
media="(max-width: 639px)">
Терміни
| Тип роботи | Час |
|---|---|
| Адаптація готової десктопної верстки під мобільні | 1–2 дні |
| Mobile-first верстка landing page | 2–3 дні |
| Mobile-first верстка корпоративного сайту | 4–6 днів |







