Название проекта вытесняет меню за край. Добавляешь flex-shrink: 1, но ничего не меняется. А после flex-direction: column знакомое justify-content: center вдруг начинает двигать элементы вверх и вниз.
Flexbox — способ расположить элементы вдоль одной главной оси и распределить между ними место. Чтобы управлять раскладкой, нужно различать три вещи: направление осей, перенос на новые линии и размеры самих элементов. Разберём их на шапке с названием проекта и навигацией.
Главная и поперечная оси: куда выравнивает Flexbox
display: flex делает элемент flex-контейнером, а его непосредственных потомков — flex-элементами. Вложенный абзац или ссылка внутри потомка не становится участником внешней раскладки.
Главную ось задаёт flex-direction. Поперечная проходит перпендикулярно ей. В обычном русском тексте с writing-mode: horizontal-tb и direction: ltr это выглядит так:
| Направление | Главная ось | Поперечная ось |
|---|---|---|
row | Слева направо | Сверху вниз |
column | Сверху вниз | Слева направо |
justify-content распределяет свободное место по главной оси, а align-items выравнивает элементы по поперечной внутри каждой линии. Поэтому запоминать «justify — горизонталь» неудобно: после переключения на column правило перестанет работать.
Ось меняется, назначение свойства остаётся
row главная ось горизонтальна, при column — вертикальна в указанных условиях письма. Свойства выравнивания следуют за осями, а не за сторонами экрана.При direction: rtl начало строки окажется справа; вертикальный writing-mode тоже меняет картину. row-reverse и column-reverse разворачивают соответствующее направление. Подробности — в объяснении осей Flexbox на MDN.
Разворачивать визуальный порядок ради адаптива стоит осторожно: DOM-порядок при этом сохраняется, и переход клавишей Tab может не совпасть с тем, что видит человек. В нашем примере перестановок нет.
Готовый пример: адаптивная шапка на Flexbox
В песочнице HTML и CSS замени содержимое index.html первым блоком, а styles.css — вторым. Получится шапка из трёх частей: короткое имя сайта, название проекта и меню. Ссылки ведут к разделам этого же примера.
<header class="site-header">
<a class="brand" href="#about">web</a>
<div class="heading">
<p class="eyebrow">Личный проект</p>
<p class="project-name">Библиотека материалов для первой самостоятельной страницы</p>
</div>
<nav class="nav" aria-label="Разделы страницы">
<a href="#about">Описание</a>
<a href="#practice">Практика</a>
</nav>
</header>
<main>
<h1 id="about">Библиотека материалов для первой самостоятельной страницы</h1>
<p>Здесь собираются заметки о вёрстке и примеры кода.</p>
<h2 id="practice">Практика</h2>
<p>Измени ширину предпросмотра и посмотри, куда переместится меню.</p>
</main>
:root {
--page: #15171a;
--text: #f2f4f8;
--border: #505967;
--accent: #7cc4ff;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
padding: 1rem;
font-family: system-ui, sans-serif;
line-height: 1.5;
color: var(--text);
background: var(--page);
}
.site-header {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1rem;
padding: 1rem;
border: 1px solid var(--border);
}
.brand {
flex: 0 0 auto;
font-weight: 700;
}
.heading {
flex: 1 1 18rem;
min-width: 0;
}
.eyebrow,
.project-name {
margin: 0;
}
.eyebrow {
font-size: 0.875rem;
}
.project-name {
font-weight: 700;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.nav {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
max-width: 100%;
}
.nav a {
padding: 0.5rem 0.75rem;
border: 1px solid var(--border);
overflow-wrap: anywhere;
}
a {
color: var(--accent);
text-underline-offset: 0.2em;
}
a:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 4px;
}
main {
max-width: 68ch;
overflow-wrap: anywhere;
}
Внешняя шапка раскладывает три элемента. Меню .nav создаёт свой flex-контейнер и управляет только двумя ссылками внутри. Свойства gap и flex-wrap у этих контейнеров независимы. В ссылках разрешён перенос через overflow-wrap: anywhere: даже длинное слово без пробелов сможет поместиться в узком меню.
Название в шапке сокращается многоточием, а ниже в h1 остаётся полным. Так короткая шапка не лишает читателя названия страницы. Внешних ресурсов и JavaScript в примере нет.
В песочнице результат меняется при вводе. Кнопка «Проверить» относится к исходному заданию на центрирование карточки; эту шапку исследуй в предпросмотре, включая переключение на мобильную ширину.
Перенос, gap и свободное место — разные задачи
Без flex-wrap: wrap контейнер держит элементы на одной линии: начальное значение — nowrap. Сжатие может помочь им поместиться, но само по себе не создаёт новую строку.
wrap разрешает дополнительные линии. В нашей шапке браузер собирает их с учётом размеров элементов, ограничений и промежутков. Затем каждая линия отдельно распределяет своё место. Перенос происходит до окончательного гибкого изменения размеров; это не правило «сначала ужать всё до предела, потом перенести».
| Свойство | За что отвечает | Что увидеть в примере |
|---|---|---|
flex-wrap: wrap | Разрешает несколько линий | На узкой ширине части шапки переходят ниже |
gap: 1rem | Задаёт промежутки между элементами и линиями | Части шапки не прилипают друг к другу |
align-items: center | Выравнивает элементы поперёк своей линии | Меню стоит по центру относительно блока из двух строк |
justify-content | Распределяет оставшееся место вдоль линии | После роста .heading свободного остатка может не быть |
В примере flex-grow у .heading забирает свободную ширину. Поэтому добавление justify-content: space-between к шапке может ничего не изменить: распределять уже нечего. Если нужно проверить это свойство, временно поставь .heading { flex-grow: 0; }.
gap не добавляет отступ по внешнему краю: для этого у шапки есть padding. А space-between может сделать расстояния больше заданного gap, когда свободное место осталось.
При переносе не путай два свойства: align-items выравнивает элементы внутри линий, align-content распределяет сами линии по поперечной оси. В нашей шапке высота определяется содержимым, поэтому отдельно распределять линии по высоте незачем. Механика переноса описана в справке MDN о flex-wrap.
flex-grow, flex-shrink и flex-basis: читать три числа
У названия проекта стоит сокращённая запись:
.heading {
flex: 1 1 18rem;
}
Её порядок — рост, сжатие, базовый размер:
flex-grow: 1разрешает получать долю свободного места. У.headingэто единственный растущий элемент, поэтому на своей линии он забирает остаток.flex-shrink: 1разрешает уменьшаться, когда места не хватает. Минимальные размеры при этом продолжают действовать.flex-basis: 18remзадаёт стартовый размер по главной оси. Это не обещание конечной ширины: результат меняют рост, сжатие и ограничения.
У короткого имени сайта flex: 0 0 auto: оно не растёт и не сжимается, а базовый размер зависит от содержимого, поскольку отдельная ширина не задана. Значения сокращения собраны в справке MDN о flex.
flex-grow: 2 не означает «элемент вдвое шире». Когда два элемента делят положительный остаток с коэффициентами 1 и 2, второй получает вдвое больше добавочного места. Их исходные размеры могут различаться.
Со сжатием есть ещё одна деталь. При одинаковом flex-shrink: 1 более крупная база отдаёт больше пикселей. Выделим отдельный расчёт для двух элементов «Название» и «Меню»: контейнер 500px, базы 200px и 400px, gap: 20px. Перенос выключен, отступов и границ нет, min-width: 0, верхние ограничения размера не заданы.
Не хватает 200 + 400 + 20 − 500 = 120px. Отношение баз — 1:2, значит элементы отдадут 40px и 80px. Итог: 160 + 320 + 20 = 500px. Это расчёт по алгоритму Flexbox, а не замер шапки выше.
Одинаковый shrink не означает одинаковую потерю ширины
flex-shrink × базовый размер. При равных коэффициентах блок с базой 400px отдаёт вдвое больше пикселей, чем блок с базой 200px. Промежуток остаётся 20px.Почему длинному тексту нужен min-width: 0
Вернёмся к шапке. У .project-name стоит white-space: nowrap: название должно оставаться в одну строку. Родитель .heading участвует в Flexbox, и его начальный min-width: auto может удерживать минимум по содержимому. Неразрывная строка делает этот минимум слишком большим.
flex-shrink: 1 разрешает сжатие, но не отменяет минимальный размер. Поэтому мы явно ставим min-width: 0 на .heading — flex-элемент, который должен уступить место. На одном только вложенном абзаце это не снимет ограничение родителя. Поведение auto описано в справке MDN о min-width.
Дальше работают свойства самого текста:
| Свойство | Зачем оно здесь |
|---|---|
min-width: 0 у .heading | Разрешает сузить flex-элемент ниже его автоматического минимума |
white-space: nowrap у .project-name | Сохраняет название одной строкой |
overflow: hidden у .project-name | Скрывает не поместившуюся часть строки |
text-overflow: ellipsis у .project-name | Обозначает скрытый хвост многоточием |
Если по смыслу название надо показывать целиком прямо в шапке, замени три правила обрезки у .project-name на overflow-wrap: anywhere. Обычный текст будет переноситься по словам, длинное слово без пробелов тоже сможет разорваться. Не оставляй одновременно white-space: nowrap: он запрещает перенос, который ты пытаешься разрешить.
Не расставляй min-width: 0 всем элементам наугад. Сначала найди flex-элемент, чей минимум мешает сжатию. Для вертикальной раскладки похожую роль иногда играет min-height: 0.
Когда вместо Flexbox нужен Grid
У нашей шапки элементы разной ширины, а меню может перейти на другую линию. Flexbox подходит: каждая линия распределяет своё место независимо.
У каталога другое требование: карточки второго ряда должны стоять под колонками первого. Для общей системы строк и столбцов удобнее Grid. Это разобрано на адаптивной сетке карточек CSS Grid. В одной странице оба способа могут работать вместе: Grid снаружи карточек, Flexbox внутри каждой.
Четыре изменения, которые помогут разобраться
Перед каждой правкой возвращай исходный CSS и сначала предсказывай результат:
- Убери
min-width: 0у.heading. На узкой ширине проверь, мешает ли полное название сжать этот блок. Верни правило и сравни. - Замени
flex-wrap: wrapнаnowrapу.site-header. Части шапки останутся на одной линии, а названию достанется меньше места. Настройка.navпри этом не меняется: ссылки внутри неё всё ещё могут переноситься. - Поставь
.heading { flex-grow: 0; }и добавь шапкеjustify-content: space-between. На широкой строке появится свободное место для распределения между элементами. Затем верни рост и посмотри, кто теперь использует этот остаток. - Замени текст ссылки «Практика» на
СамостоятельныеЭкспериментыСВёрсткой. На узкой ширине слово должно переноситься внутри ссылки благодаряoverflow-wrap: anywhere. В отличие от названия в шапке, текст меню остаётся виден целиком.
Если после этих опытов хочется потренировать выбор свойств без оформления страницы, открой игру по Flexbox и Grid. Здесь же попробуй сохранить название целиком: разреши перенос текста и объясни, какие правила обрезки стали не нужны.