Нетривиальная расстановка элементов на flexbox без media-запросов

в 6:54, , рубрики: css, css3, flexbox, html

Казалось бы, какой пост может быть о CSS Flexbox в 2019 году? Верстальщики уже несколько лет активно используют данную технологию, и все тайны должны быть разгаданы.

Однако, недавно у меня возникло стойкое ощущение, что нужно поделиться одним нетривиальным и, на мой взгляд, полезным приёмом, связанным с flexbox. Написать пост побудил тот факт, что ни один знакомый (из учеников, верстальщиков и просто людей, близких к web), не смог решить задачку, связанную с flexbox, хотя на это нужно всего 4-6 строк.

Итак, сначала постановка задачи, затем предыстория и, наконец, решение.

Постановка задачи

  1. На большом экране два элемента расположены горизонтально, при адаптивке на телефонах – вертикально.
  2. При этом на больших экранах они должны прижиматься к краям (как при justify-content: space-between), а на маленьких — стоять по центру (justify-content: center).

Казалось бы, что тут решать!? Но есть ещё одно условие: надо сделать это перестроение в автоматическом режиме – без media-запросов.

Зачем, спросите вы? К чему эти фокусы с запретом на media-запросы, зачем flexbox ради flexbox?

А всё дело в том, что ширина контента может быть динамической. Например, заголовок и дата новости стоят рядом. Формат даты чётко определен, а заголовки могут очень сильно отличаться по длине. Хотелось бы, чтобы перенос шёл только для тех постов, где элементам стало тесно находиться на одной строке.

Нетривиальная расстановка элементов на flexbox без media-запросов - 1

И здесь, ведь, media-запрос ничем не поможет, так как ширина экрана, на которой происходит перестроение, зависит от длины текста. Обычно в таком случае media-запрос ставят с запасом, ориентируясь на максимально длинный заголовок. Для коротких заголовков это смотрится так себе.

Ещё один яркий пример: логотип и меню в шапке. Ширина лого известна, а вот с меню — проблема. Ведь часто мы делаем вёрстку на формально заполненном шаблоне, а в реальности меню будет формироваться из админки сайта. Сколько пунктов – никто не знает, следовательно, не ясно, на какой ширине делать перестроение.

Нетривиальная расстановка элементов на flexbox без media-запросов - 2

Понятно, что на реальном сайте меню всё равно будет превращено в кнопку для мобильной версии. Но на какой ширине делать данное превращение, если мы не знаем количества пунктов? Если мы опоздаем с этим превращением, хотелось бы, чтобы меню на некоторое время слетело вниз красиво, а не абы как.

Зачастую при решении таких задач люди не напрягаются. Либо делают 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-свойств! Поэтому нам обязательно нужно осознать, как это работает!

Предлагаю вам сначала внимательно изучить картинку, и только после этого переходить к текстовому описанию, расположенному под ней.

Нетривиальная расстановка элементов на flexbox без media-запросов - 3

Изначально 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 достаточно быстро заметили, что текст расположен правильно только до тех пор, пока на маленьком экране заголовок помещается в одну строку. На новых строках текст прижат к левому краю.

Нетривиальная расстановка элементов на flexbox без media-запросов - 4

Если бы заголовок изначально был втором элементом (располагался справа от даты), то тогда на него действовал бы 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

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js