Строим с flexbox

В данной обучающей статье мы разберем некоторые реальные и некогда действительно трудновыполнимые моменты, которые сейчас легко решаются благодаря использованию flexbox.



Введение


Некоторое время назад я написал ознакомительную статью про flexbox на сайте SitePoint. В данной статье я постарался выяснить и ответить на вопрос:

А готовы ли мы использовать flexbox?

В прошлый раз я говорил, что вам следует очень аккуратно использовать данную возможность, но настаивал на том, что нужно обязательно познакомиться с ее синтаксисом и внедрять ее в современные проекты. Сейчас я рад, что могу дать положительный ответ на свой вопрос:

Да, мы готовы использовать flexbox!

Если вы еще не знакомы с flexbox, то вот небольшое пояснение. CSS модуль построения гибкой разметки — это модуль, позволяющий легко размещать блоки на экране, в пределах выделенного пространства. Это большое достижение по сравнению с классической блочной моделью, т.к. flexbox позволяет вообще не использовать свойство float. Блоки могут быть объединены в строки или разбиты на колонки. Для каждого flexbox-элемента может быть задан определенный порядок. А также можно сразу управлять выравниванием, отступами и размерами этих элементов.

Пожалуй, что в данной статье мне не удастся рассмотреть каждый аспект данного модуля, поэтому я предлагаю вам познакомиться с ним поподробнее, перейдя на сайт MDN. А в данной обучающей статье мы рассмотрим распространенные проблемы с разметкой и увидим, как легко и быстро их можно решить. Каждый шаблон разметки, о котором я расскажу, будет отзывчивым, что еще больше подчеркнет ту легкость, с которой можно создавать разметку с помощью flexbox. Вот что мы рассмотрим:

  • Простая сеточная система
  • Разметка трехколоночного сайта
  • Резиновая навигация с изменяющейся шириной поля поиска
  • Два разных варианта вертикального выравнивания

Хорошо, давайте приступать!

Простая сеточная система


Сегодня сеточные системы присутствуют в большинстве разметок проектов, и классическое поведение блочной модели в CSS вынудило нас прибегнуть к использованию плавающих или inline-block элементов, каждый из которых имеет собственные недостатки. Flexbox позволяет нам легко создавать действительно классную и масштабируемую сеточную систему благодаря написанию всего нескольких строчек в CSS. Давайте рассмотрим это поподробнее.

Представьте, что у нас есть следующая простая разметка:

<div class="grid">
  <div class="grid__row">
    <div class="grid__item">1div>
    <div class="grid__item">2div>
    ...
  div>
div>

При традиционном подходе создания сетки нам бы пришлось каким-то образом определить, сколько элементов может помещаться в одну строку, а затем установить ширину для каждого элемента сетки. А с помощью flexbox мы можем добавлять в строку столько элементов, сколько захотим, и их ширина сама будет подстраиваться под общую ширину строки. Другими словами, у нас может быть следующая разметка, и при этом нам не нужно волноваться об указании количества элементов в каждой строке в CSS:

<div class="grid">
  <div class="grid__row">
    <div class="grid__item">1div>
    <div class="grid__item">2div>
  div>
  <div class="grid__row">
    <div class="grid__item">1div>
    <div class="grid__item">2div>
    <div class="grid__item">3div>
  div>
  <div class="grid__row">
    <div class="grid__item">1div>
    <div class="grid__item">2div>
    <div class="grid__item">3div>
    <div class="grid__item">4div>
  div>
div>

А теперь давайте посмотрим на CSS код. Некоторые свойства я использовал чисто в эстетических целях (например, рамки и внутренние отступы), а в остальном все очень просто:

.grid {
  border: solid 1px #e7e7e7;
}

.grid__row {
  display: flex;
}

.grid__item {
  flex: 1;
  padding: 12px;
  border: solid 1px #e7e7e7;
}

Вот как-то так! И наша сетка уже готова к использованию. Добавляя display: flex к контейнерам .grid__row, мы создаем, так называемый, flex-контейнер, а каждый дочерний элемент в контейнере сразу становится flex-элементом. Применяя свойство flex: 1 к flex-элементам, мы заставляем их занимать равное количество места относительно общей ширины контейнера. Теперь у вас может быть сколько угодно строк в сетке, а в каждой из них может быть свое собственное количество элементов. И это будет простая полностью отзывчивая сеточная система без всякого дополнительного CSS.

А что можно сказать насчет breakpoint-ов и колоночной разметки? Если мы хотим, чтобы элементы сетки выстраивались в колонки, а не в строки, то мы можем просто объявить свойство flex-direction: column для контейнеров с классом .grid__row. В этом случае мы можем создать очень простую отзывчивую сетку, внеся некоторые изменения. И наша разметка будет выглядеть вот так:

<div class="grid">
  <div class="grid__row grid__row--sm">
    <div class="grid__item">1div>
    ...
  div>
  <div class="grid__row grid__row--md">
    <div class="grid__item">1div>
    ...
  div>
  <div class="grid__row grid__row--lg">
    <div class="grid__item">1div>
    ...
  div>
div>

А наш CSS вот так:

.grid {
  border: solid 1px #e7e7e7;
}

.grid__row {
  display: flex;
  flex-direction: column;
}

.grid__item {
  flex: 1;
  padding: 12px;
  border: solid 1px #e7e7e7;
}

@media all and ( min-width: 480px ) {

  .grid__row--sm {
    flex-direction: row;
  }

}

@media all and ( min-width: 720px ) {

  .grid__row--md {
    flex-direction: row;
  }

}

@media all and ( min-width: 960px ) {

  .grid__row--lg {
    flex-direction: row;
  }

}

И вуа-ля. Супер-простая отзывчивая сеточная система размером всего в несколько CSS строк. Эта система настолько непробиваемая, что вы даже можете вкладывать сетки друг в друга, не заботясь о последствиях:

<div class="grid">
  <div class="grid__row grid__row--sm">
    <div class="grid__item">
      <div class="grid">
        <div class="grid__row grid__row--lg">
          <div class="grid__item">Nested 1div>
          ...
        div>
      div>
    div>
    <div class="grid__item">2div>
  div>
  <div class="grid__row grid__row--md">
    <div class="grid__item">1div>
    ...
  div>
div>

Посмотрите на это в действии.

Разметка трехколоночного сайта


Разметка трехколоночного сайта является довольно известной в веб-дизайне, и даже в эпоху веб-приложений и прочего веселья, данный вариант разметки по-прежнему играет важную роль в вебе — такая разметка постоянно используется на сайтах, у которых много контента. Еще в 2006 году трехколоночная разметка была замечательно разобрана и описана на сайте A List Apart. В ней используются float элементы, отрицательные margin и значения min-width, чтобы совмещенные размеры элементов не конфликтовали друг с другом и не ломали разметку. И все это, с учетом нынешней необходимости в создании отзывчивой разметки, означало использование большого количества вычислений, отмены обтекания и других трюков, чтобы это все работало правильно. И если вы решили поменять ширину боковой колонки, то нужно было заново производить все вычисления.

Flexbox позволяет избавиться от существенной головной боли, т.к. мы можем определить колоночную или строчную разметку, а также явно указать порядок следования элементов в CSS, даже если они расположены в другом порядке в нашей разметке. Вот типичный пример создания трехколоночной разметки:

<body class="holy-grail">
  <header class="holy-grail__header">header>
  <main class="holy-grail__body">
    <div class="holy-grail__content">div>
    <div class="holy-grail__sidebar holy-grail__sidebar--first">div>
    <div class="holy-grail__sidebar holy-grail__sidebar--second">div>
  div>
  <footer class="holy-grail__footer">footer>
body>

В моем демо-примере разметка находится внутри документа, поэтому в ней нет тегов body или main, как это показано выше. Но в данном случае, нас интересуют имена классов и сами разделы нашей разметки, а не непосредственно сами элементы. В частности, обратите внимание на классы, которые используются для двух боковых колонок, и на тот порядок, в котором они следуют в разметке. Давайте последовательно рассмотрим, что здесь происходит:

  1. У нас есть родительский контейнер, .holy-grail, а в нем у нас находится три flex-элемента. Это элементы с классами .holy-grail__header, holy-grail__body и holy-grail__footer.
  2. Данные три элемента расположены друг над другом и занимают 100% от ширины экрана. Таким образом, для flex-контейнера должно быть задано колоночное направление.
  3. Тело нашей разметки, .holy-grail__body, является внутренним flex-контейнером. Его дочерние flex-элементы должны иметь колоночную разметку на небольших экранах и строчную разметку на широких экранах.

Учитывая все вышесказанное, давайте построим разметку:

.holy-grail {
  display: flex;
  flex-direction: column;
}

.holy-grail__header,
.holy-grail__footer {
  flex: 0 0 100%;
}

.holy-grail__body {
  display: flex;
}

.holy-grail__sidebar {
  /* ничего на небольших экранах */
}

.holy-grail__sidebar--first {
  order: 1;
}

.holy-grail__sidebar--second {
  order: 3;
}

.holy-grail__content {
  order: 2;
}

@media all and ( min-width: 720px ) {

  .holy-grail__body {
    flex-direction: row;
  }

  .holy-grail__sidebar {
    flex: 0 0 180px;
  }

  .holy-grail__content {
    flex: 1;
  }

}

@media all and ( min-width: 960px ) {

  .holy-grail__sidebar {
    flex: 0 0 240px;
  }

}

Это действительно настолько просто! Как я уже упоминал, мы изначально определяем два flex-контейнера (для небольших экранов). Для первого breakpoint-а мы изменяем flex-direction для обертки колонок на строчное, а боковым колонкам задаем ширину 180 px, используя сокращенную запись — свойство flex. Данная запись позволит нам ограничить значения для свойств flex-grow и flex-shrink, а также явно указать ширину. Для контента используется свойство flex: 1, чтобы он заполнял доступное пространство. Расположение flex-элементов в нужном порядке также оказалось пустяком благодаря свойству order. Пожалуй, остались только дополнительные стили, которые вы захотите добавить с эстетической точки зрения…, а в остальном это действительно просто и эффективно. А упомянул ли я о том, что по умолчанию flexbox создает колонки одинаковой высоты? Взгляните на этот демо-пример.

Резиновая навигация с изменяющейся шириной поля поиска


В нашем следующем примере мы будем создавать нечто веселое, включающее красивый переход. Мы создадим резиновую навигацию, которая растягивается на всю ширину, а в ней мы разместим поле поиска, которое будет плавно растягиваться в состоянии фокуса. Используя силу flexbox, мы сможем добавить столько пунктов меню, сколько захотим, не меняя при этом CSS. Я буду использовать некоторые классы для достижения нужного результата. В качестве небольшого бонуса я собираюсь сделать нашу навигацию полностью отзывчивой, добавив для нее кнопку-переключатель! Вот как будет выглядеть HTML:

<nav class="flexy-nav">
  <button id="flexy-nav__toggle" class="flexy-nav__toggle">Toggle Navbutton>
  <ul id="flexy-nav__items" class="flexy-nav__items">
    <li class="flexy-nav__item"><a href="#" class="flexy-nav__link">Item 1a>li>
    <li class="flexy-nav__item"><a href="#" class="flexy-nav__link">Item 2a>li>
    <li class="flexy-nav__item"><a href="#" class="flexy-nav__link">Item 3a>li>
    <li class="flexy-nav__item"><a href="#" class="flexy-nav__link">Item 4a>li>
  ul>
  <form action="" class="flexy-nav__form">
    <input class="flexy-nav__search" type="text" placeholder="Type search terms and hit enter...">
  form>
nav>

Давайте разберем данную разметку, прежде чем мы перейдем к CSS. У нас есть основной flex-контейнер с классом .flexy-nav. Кнопка используется в качестве переключателя, в ненумерованном списке содержатся пункты основного меню, а в форме содержится поле поиска. Для небольших экранов нам нужно использовать колоночную разметку для всех трех элементов, а также нам нужно, чтобы каждый пункт меню располагался в отдельной колонке. На широких экранах нам нужно спрятать кнопку-переключатель, выстроить элементы списка в строку и задать для формы фиксированную ширину. Элементы списка будут равномерно распределены среди оставшегося пространства. Когда мы переключаемся на текстовое поле (поле поиска), нам бы хотелось, чтобы оно плавно растягивалось по ширине, а все элементы списка, наоборот, плавно сужались. Вот CSS:

/* сброс стилей */

input,
button {
  font: inherit;
  border-radius: none;
  box-shadow: none;
  appearance: none;
}

button {
  cursor: pointer;
}

/* контейнер навигации */

.flexy-nav {
  display: flex;
  flex-direction: column;
}

/* пункты меню */

.flexy-nav__items {
  display: none;
  flex: 1;
  flex-direction: column;
  list-style: none;
  margin: 0 0 4px 0;
  padding: 4px;
  text-align: center;
}

.flexy-nav__items--visible {
  display: flex;
}

.flexy-nav__item {
  background-color: #f1f1f1;
  border-bottom: solid 1px #e7e7e7;
}

.flexy-nav__item:last-child {
  border-bottom: 0;
}

.flexy-nav__link {
  padding: 8px;
  display: block;
}

/* переключатель меню */

.flexy-nav__toggle {
  margin: 0 0 4px 0;
  padding: 4px;
  color: #fff;
  background-color: #f07850;
  border: none;
}

.flexy-nav__toggle:hover,
.flexy-nav__toggle:focus {
  outline: none;
  background-color: #c93f11;
}

/* форма для поля поиска в навигации */

.flexy-nav__form {
  height: 48px;
}

.flexy-nav__search {
  display: block;
  margin: 0;
  padding: 0 4px;
  width: 100%;
  height: 48px;
  color: #6d6d6d;
  background-color: #fff;
  border: solid 2px #e7e7e7;
}

.flexy-nav__search:focus {
  outline: none;
  border: solid 2px #6d6d6d;
}

/* медиа-запросы */

@media all and (min-width: 768px) {
  .flexy-nav {
    flex-direction: row;
  }

  .flexy-nav__items {
    display: flex;
    flex-direction: row;
    margin: 0;
    padding: 0;
    height: 48px;
  }

  .flexy-nav__item {
    flex: 1;
    margin-right: 4px;
    border-bottom: none;
  }

  .flexy-nav__link {
    padding: 0;
    line-height: 48px;
  }

  .flexy-nav__toggle {
    display: none;
  }

  .flexy-nav__form {
    flex: none;
  }

  .flexy-nav__search {
    width: 240px;
    transition: width 0.3s;
  }

  .flexy-nav__search:focus {
    width: 360px;
  }
}

Как и обещал, вот, до смешного простой, кусочек JavaScript кода, который позволит нам показывать/скрывать навигацию на небольших экранах:

(function() {
  var toggle = document.querySelector("#flexy-nav__toggle");
  var nav = document.querySelector("#flexy-nav__items");
  toggle.addEventListener("click", function(e) {
    e.preventDefault();
    nav.classList.contains("flexy-nav__items--visible") ? nav.classList.remove("flexy-nav__items--visible") : nav.classList.add("flexy-nav__items--visible");
  });
})();

Настолько все просто. Мы только что создали красивую и масштабируемую резиновую навигацию с помощью flexbox, а также встроили плавный переход при изменении ширины у поля поиска. Мы можем сколько угодно добавлять или удалять ссылки, а также «на лету» изменять размеры поля поиска. И при этом функциональность нашего меню нисколько не пострадает. Ах, вот они прелести flexbox. Не забудьте посмотреть соответствующий демо-пример.

Вертикальное выравнивание


Давайте признаем тот факт, что вертикальное выравнивание в традиционном CSS просто никуда не годится. Inline-block элементы могут иногда с этим помочь, есть также хаки с абсолютным позиционированием, а еще есть устаревшие табличные разметки (которые на данный момент неприемлемы для многих случаев с семантической точки зрения). У всех этих способов есть свои особенности, и они точно потребуют от вас дополнительных «танцев с бубнов», чтобы все работало, как надо.

Flexboxс легкостью возьмет это на себя. Мы рассмотрим два примера вертикального выравнивания:

  1. Сначала мы рассмотрим создание, так называемого, «медиа-объекта», в котором используется пользовательский аватар (расположенный слева) и имя пользователя + некоторая информация (расположенные справа). Мы будем использовать flexbox, чтобы изображение и тело медиа-объекта были идеально выровнены по вертикали.
  2. Затем мы просто рассмотрим вертикальное (горизонтальное) выравнивание элемента фиксированной ширины и переменной высоты внутри контейнера. Элемент будет оставаться расположенным точно по центру, несмотря на увеличение высоты.

Давайте начнем с первого примера. Как уже было сказано, нам нужно расположить пользовательский аватар слева, а описание — справа. И неважно насколько длинным или коротким будет описание. Нам нужно, чтобы оно всегда было идеально выровнено с аватаром. Вот стандартная разметка:

<div class="user">
  <div class="user__avatar">div>
  <div class="user__description">
    <h2 class="user__username">John Doeh2>
    <p class="user__excerpt">I'm John Doe...p>
  div>
div>

<div class="user">
  <div class="user__avatar">div>
  <div class="user__description">
    <h2 class="user__username">Harry Potterh2>
    <p class="user__excerpt">I'm Harry Potter...with a really long description...p>
  div>
div>

Прежде чем мы перейдем к CSS, обратите внимание на то, что мы будем использовать незнакомое до этого свойство. Это свойство align-items, и оно позволяет нам выравнивать элементы вдоль, так называемой, flex-линии в перпендикулярном направлении. Другими словами, если наша flex-линия расположена горизонтально, то мы можем выравнивать наши элементы в направлении, которое перпендикулярно данной линии. В нашем случае нам нужно выровнять элементы по центру, поэтому мы будем использовать значение align-items: center. Вот CSS:

.user {
  display: flex;
  align-items: center;
}

.user:last-child {
  margin-bottom: 0;
}

.user__avatar {
  flex: 0 0 96px;
  width: 96px;
  height: 96px;
  background-color: #e7e7e7;
}

.user__description {
  flex: 1;
  margin-left: 24px;
  padding: 12px;
  border: solid 1px #e7e7e7;
}

Вот так просто. Вы можете оформить текст, как вам захочется, сделать описания очень длинными или изменить размеры аватара. Это не имеет значения, функциональность останется прежней. Оцените данную возможность в действии.

Давайте перейдем ко второму примеру. На этот раз представьте, что у нас есть баннер, расположенный в самой верхней части разметки. И мы хотим разместить внутри баннера какой-то заголовок. На маленьких экранах высота баннера будет равна 180 px, и она будет изменяться еще дважды, до значения 480 px. И при всех изменениях высоты баннера нам нужно, чтобы текст внутри был расположен идеально по центру (как по горизонтали, так и по вертикали). Вот стандартная разметка:

<div class="banner">
  <div class="banner__content">
    <h2 class="banner__title">Symmetrical Perfectionh2>
    <span class="banner__sub">A beautiful sight, achieved with flexbox.span>
  div>
div>

На этот раз мы задействовали также свойство justify-content, которое позволит нам распределить пространство вокруг элементов вдоль flex-линии. А вот CSS:

.
banner {
  display: flex;
  align-items: center;
  justify-content: space-around;
  height: 180px;
  background-color: #e7e7e7;
}

.banner__content {
  text-align: center;
}

.banner__title,
.banner__sub {
  margin: 0;
  padding: 0;
  line-height: 1.5;
}

@media all and ( min-width: 480px ) {

  .banner {
    height: 240px;
  }

}

@media all and ( min-width: 768px ) {

  .banner {
    height: 360px;
  }

}

@media all and ( min-width: 960px ) {

  .banner {
    height: 480px;
  }

}

Неважно, насколько «высоким» будет баннер. Контент всегда будет идеально отцентрирован по горизонтали и вертикали. Вот в этом и заключается мощь flexbox. Не забудьте посмотреть демо-пример.

Поддержка и вендорные префиксы


Вы должны знать ваш рынок и аудиторию… это ключевой момент. Flexbox поддерживается во всех современных браузерах, включая IE10 и выше. Если вы занимаетесь созданием современных веб-приложений, то flexbox — это мощный инструмент, и я очень рекомендую его использовать. Если вы создаете или переделываете веб-сайт, то проверьте статистику посещаемости, чтобы узнать вашу аудиторию. В настоящее время есть вероятность того, что около 99% вашей аудитории будет использовать современные браузеры.

Что касается вендорных префиксов, то у flexbox их много. Это очень неразумно всерьез использовать flexbox и при этом вручную прописывать все вендорные префиксы. Лично я использую сборщик проектов (Gulp) для автоматической простановки префиксов.

Заключение


Вот и все! На этом я заканчиваю данную статью. Если вы хотите найти реальный сайт, на котором используется flexbox, то можете даже и не начинать поиски. На новом сайте callmenick.com flexbox используется практически везде! Спасибо, что прочли статью до конца. Не забудьте, что вы можете посмотреть демо-примеры, а также скачать исходники по нижеприведенным ссылкам. Если у вас есть какие-либо вопросы, замечания или пожелания, оставьте их, пожалуйста, в комментариях.

Ссылка на оригинал статьи: http://callmenick.com/post/flexbox-examples

© Habrahabr.ru