Казалось бы, какой пост может быть о CSS Flexbox в 2019 году? Верстальщики уже несколько лет активно используют данную технологию, и все тайны должны быть разгаданы.
Однако, недавно у меня возникло стойкое ощущение, что нужно поделиться одним нетривиальным и, на мой взгляд, полезным приёмом, связанным с flexbox. Написать пост побудил тот факт, что ни один знакомый (из учеников, верстальщиков и просто людей, близких к web), не смог решить задачку, связанную с flexbox, хотя на это нужно всего 4-6 строк.
Итак, сначала постановка задачи, затем предыстория и, наконец, решение.
Постановка задачи
- На большом экране два элемента расположены горизонтально, при адаптивке на телефонах – вертикально.
- При этом на больших экранах они должны прижиматься к краям (как при justify-content: space-between), а на маленьких — стоять по центру (justify-content: center).
Казалось бы, что тут решать!? Но есть ещё одно условие: надо сделать это перестроение в автоматическом режиме – без media-запросов.
Зачем, спросите вы? К чему эти фокусы с запретом на media-запросы, зачем flexbox ради flexbox?
А всё дело в том, что ширина контента может быть динамической. Например, заголовок и дата новости стоят рядом. Формат даты чётко определен, а заголовки могут очень сильно отличаться по длине. Хотелось бы, чтобы перенос шёл только для тех постов, где элементам стало тесно находиться на одной строке.
И здесь, ведь, media-запрос ничем не поможет, так как ширина экрана, на которой происходит перестроение, зависит от длины текста. Обычно в таком случае media-запрос ставят с запасом, ориентируясь на максимально длинный заголовок. Для коротких заголовков это смотрится так себе.
Ещё один яркий пример: логотип и меню в шапке. Ширина лого известна, а вот с меню — проблема. Ведь часто мы делаем вёрстку на формально заполненном шаблоне, а в реальности меню будет формироваться из админки сайта. Сколько пунктов – никто не знает, следовательно, не ясно, на какой ширине делать перестроение.
Понятно, что на реальном сайте меню всё равно будет превращено в кнопку для мобильной версии. Но на какой ширине делать данное превращение, если мы не знаем количества пунктов? Если мы опоздаем с этим превращением, хотелось бы, чтобы меню на некоторое время слетело вниз красиво, а не абы как.
Зачастую при решении таких задач люди не напрягаются. Либо делают media-запрос заранее, с огромным запасом до точки соударения, либо довольствуются центровкой только одного элемента из двух.
Способ 1 просто неудобен, способ 2 будет более-менее нормально смотреться для примера с заголовками и датами, но будет просто ужасен для логотипа и меню.
Итак, постановка задачи. Как на flexbox без media-запросов организовать идеальное поведение элементов: визуально расположить их по краям большого экрана и отцентровать при соударении? (Ну или почти идеальное, из-за использования text-align и flex-grow вылезут небольшие ограничения, которые легко решаются с помощью дополнительной обёртки)
Предыстория
Подобного поведения элементов я захотел добиться сразу же, как несколько лет назад познакомился с flexbox. Однако, эксперименты и чтение мануалов не дало результата. В итоге был сделан вывод, что только один элемент сможет при перескоке стать по центру, а другой останется с краю.
Для достижения идеального поведения всегда чего-то не хватало. Flex-grow поможет на одном экране, навредит на другом. Margin: auto и text-align помогают, но и их не хватает для достижения результата.
В итоге я, как думают и большинство верстальщиков, решил, что flex просто не умеют так делать. И успешно забыл данную задачу, научившись обходиться другими средствами и приёмами при вёрстке.
Озарение пришло летом 2019, когда, разбирая домашнее задание на курсе по вёрстке, я увидел совершенно не по делу применённый flex-grow: 0.5. В том примере жадность 0.5 не сильно помогала решить задачу, однако, я сразу почувствовал, что вот он – секретный ингредиент, которого всегда не хватало. Не то, чтобы я не знал, что grow может быть дробным. Просто в голову не приходило, что 0.5 может помочь достичь идеальной расстановки! Теперь это кажется очевидным, ведь flex-grow: 0.5 – это захват ровно половины свободного пространства…
В общем, увидев flex-grow: 0.5, я за 10 минут полностью решил задачу расстановки элементов, которую с 2014 года считал нерешаемой. Увидев, что код получился очень простой и лаконичный, я решил, что изобрёл давно известный велосипед. Но всё-таки несколько дней назад я решил проверить, насколько хорошо он известен верстальщикам и запустил интерактив на своём youtube-канале по веб-разработке с 45 тыс. подписчиков.
Задал задачку, стал ждать. И её не решил никто из подписчиков. Не решили её и знакомые верстальщики. Крепло ощущение, что народ просто не знает, как идеально расположить элементы с помощью flex. Так и созрел данный пост. Ведь теперь мне кажется, что огромное количество верстальщиков считают, что так, как было описано в постановке задачи, расставить элементы без media-запросов просто нельзя.
Решение, которое мы рассмотрим ниже является вполне универсальным. Иногда оно требует создания дополнительной обёртки для центровки текста или правильного отображения фона. Но зачастую удаётся обойтись всего 6 строками CSS-кода без изменения структуры.
Базовое решение
За основу возьмём небольшой кусочек HTML. Это пример, который мы видели на самой первой gif-картинке. Дата и заголовок прижаты к краям, а при перескоке должны становиться по центру.
<div class="blog">
<div class="blogItem">
<div class="blogItem__cat">Article posted in: PHP Programming</div>
<div class="blogItem__dt">21.10.2019</div>
</div>
<div class="blogItem">
<div class="blogItem__cat">Article posted in: Javascript</div>
<div class="blogItem__dt">22.10.2019</div>
</div>
<div class="blogItem">
<div class="blogItem__cat">Article posted in: wft my programm don't work</div>
<div class="blogItem__dt">23.10.2019</div>
</div>
</div>
Опустим неинтересные стили, связанные с фонами, рамками и т.п. Главная часть CSS:
.blogItem {
display: flex;
flex-wrap: wrap;
}
.blogItem__cat {
flex-grow: 0.5;
margin-left: auto;
}
.blogItem__dt {
flex-grow: 0.5;
text-align: right;
}
Когда видишь этот код первый раз, может сложиться ощущение, что его написал не человек, а рандомный бредогенератор CSS-свойств! Поэтому нам обязательно нужно осознать, как это работает!
Предлагаю вам сначала внимательно изучить картинку, и только после этого переходить к текстовому описанию, расположенному под ней.
Изначально flex-элементы стоят рядом и получают размер по своему контенту. flex-grow: 0.5 отдаёт каждому элементу по половине свободного пространства, т.е. свободного пространства нет, когда они стоят рядом. Это безумно важно, потому что margin-left: auto вообще не работает, пока элементы помещаются на одной строке.
Первый элемент сразу стоит идеально, содержимое второго нужно прибить к правому краю с помощью text-align: right. В итоге мы получили аналог justify-content: space-between, вообще не написав justify-content.
Но ещё большие чудеса начинаются при перескоке элементов на отдельные строки. Margin-left auto отталкивает первый элемент на максимально возможное значение от левого края. Ну-ка, ну-ка, сколько там у нас пространства осталось? 100% минус размер элемента минус половина свободного от grow = половина свободного! Вот такая центровка!
Второй элемент стоит у левого края и занимает пространство, равное своему базовому размеру, плюс половина свободного. В это трудно поверить, но text-align: right отправит элемент идеально по центру!
Если кто-то до сих пор не верит, вот ссылка на работающий пример. В ней хорошо видно, что элементы перескакивают в нужный момент времени и стоят идеально по центру.
Ключом к созданию такого решения является flex-grow: 0.5, без которого не удаётся отключить margin-[left-right] auto на больших экранах. Ещё один фантастический факт – justify-content вообще не используется. Тотальный парадокс ситуации заключается в том, что любая попытка выравнивания контента с помощью justify-content сломает нашу схему.
Абсолютно такими же свойствами мы можем поставить рядом и более крупные и сложные элементы, например, меню и логотип – ссылка на песочницу. Ещё раз подчёркиваю, что меню всё равно придётся превращать в кнопку, но теперь мы гораздо меньше переживаем по поводу того, что оно слетит вниз раньше нужного. Ведь даже слетев, меню смотрится очень хорошо!
Улучшенное решение
В комментариях к интерактиву на youtube достаточно быстро заметили, что текст расположен правильно только до тех пор, пока на маленьком экране заголовок помещается в одну строку. На новых строках текст прижат к левому краю.
Если бы заголовок изначально был втором элементом (располагался справа от даты), то тогда на него действовал бы text-align: right, из-за которого текст смотрелся бы ещё более странно. Кстати, и дате, и заголовку мы не можем поставить фон из-за flex-grow 0.5.
Однако это всё легко решается созданием дочернего элемента со свойствами
some-selector {
display: inline-block;
text-align: center
}
За счёт строчно-блочности такой элемент получается ровно по размеру контента, и, что очень важно, text-align родителя ставит его на нужное место. А уже внутри элемента используем тот text-align, который нам нужен, чаще всего center.
Важно, что работу этого внутреннего text-align мы вообще не ощущаем до тех пор, пока во flex-элементе всего одна строка.
Кстати, inline-flex тоже подойдёт!
Примеры кода
- идеальная центровка
- нормальный фон (просто для примера, смотрится так себе фон для заголовка)
Данная схема повышает универсальность решения, но нужна далеко не всегда. Например, меню сложно представить сделанным в две строки, поэтому и обёртка нас не спасёт – надо превращать меню в кнопку.
Дополнительные мысли
Отступы
Сейчас элементы перед тем, как соскочить на отдельные строки, подходят вплотную друг к другу. Как же добавить между ними отступ, не отодвинув их от краёв контейнера?
Эту задачу легко решает классический приём: padding — элементам и отрицательный margin – родителю. Готовый пример.
Зеркальность
Разумеется, любой из наших примеров можно отзеркалить за счёт wrap-reverse, flex-direction, margin-right: auto и т.п. Например, меню выше логотипа смотрится хорошо.
Заключение
Данный приём является просто интересным решением определённой задачи. Разумеется, не надо отказываться от media-запросов и верить во flexbox без media. Но в рассмотренных примерах, на мой взгляд, данное решение представляется очень интересным.
Также есть подробный видеоразбор данного приёма. Однако, я не был уверен, что в первой статье на хабре стоит давать ссылку на youtube-канал, поэтому постарался добавить в пост побольше картинок, в том числе gif.
Надеюсь, рассмотренная схема работы с flex-элементами окажется полезной и облегчит жизнь верстальщиков при решении определённых задач!
Автор: dmitry-lavrik