10 особенностей CSS Flexible Box Layout, о которых полезно знать веб-разработчику

Пост опубликован в блогах iXBT.com, его автор не имеет отношения к редакции iXBT.com

CSS Flexible Box Layout — это модуль для выравнивания элементов и распределения пространства между ними. В сети полно кратких шпаргалок, помогающих понять, как управлять элементами внутри flex-контейнера. Однако технология полна нюансов, знание которых сэкономит вам время при работе с реальными проектами.


Тайный способ выравнивания элементов: используем margin со значением auto
  • Когда контент ломает сетку: секрет min-width
  • Кто главнее: flex-basis или width?
  • Особенности работы со слоями
  • Особенности абсолютного позиционирования
  • Схлопывание внешних отступов отменяется
  • Чем отличаются align-content, align-items и align-self
  • Структурные псевдоклассы против свойства order
  • Разница между flex и inline-flex
  • Flex-контейнеры игнорируют псевдоэлементы first-letter и first-line
  • Тайный способ выравнивания элементов: используем margin со значением auto

    Как быть, если первые два элемента внутри flex-контейнера нужно выровнять по левому краю, а последний — сместить к правому? Веб-разработчики, которые не знают, как решить эту задачу за счет установки значения для всего одного свойства, начинают создавать лишние теги HTML. Но решение было заложено в спецификации: нужно добавить для последнего элемента свойство margin-left со значением auto.

    HTML:

    <div class="flex-container">
      <div class="element">1</div>
      <div class="element">2</div>
      <div class="element">3</div>
    </div>
    

    CSS:

    html {
      box-sizing: border-box;
    }
    
    *, *:before, *:after {
      box-sizing: border-box;
    }
    
    .flex-container {
      display: flex;
      gap: 20px;
      padding: 20px;
      background-color: #ccc;
    }
    
    .element {
      display: flex;
      flex-direction: column;
      justify-content: center;
      width: 120px;
      height: 120px;
      background-color: #555555;
      border: 2px solid #fff;
      font-size: 60px;
      line-height: 1.0;
      font-weight: bold;
      color: #fff;
      text-align: center; 
    }
    
    .element:last-child {
      margin-left: auto;
    }
    
    Автор: 1024x768

    У flex-элементов автоматические отступы имеют приоритет над выравниванием. Благодаря этому в нашем примере последний элемент занимает крайнее правое положение.

    Когда контент ломает сетку: секрет min-width

    Допустим, у нас есть flex-контейнер с двумя колонками: одна фиксированной ширины, а вторая — «резиновая», в которой находится заголовок с длинным словом и настроенными переносами.

    HTML:


    <div class="page">
      <div class="sidebar"></div>
      <div class="content">
        <h1>Самосовершенствование</h1>
      </div>
    </div>
    

    CSS:

    .page {
      display: flex;
      padding: 20px;
      background-color: #ccc;
    }
    
    .sidebar {
      flex-shrink: 0;
      width: 160px;
      background-color: #9bce90;
    }
    
    .content {
      flex: 1;
      padding: 20px;
      background-color: #eee;
    }
    
    .content h1 {
      font-size: 46px;
      line-height: 1.2;
      overflow-wrap: break-word;
    }
    
    
    Автор: 1024x768

    Видим, что область с контентом «уехала» за пределы flex-контейнера, а переносы не сработали. Почему так произошло? Дело в том, что согласно спецификации, flex-элементы получили по умолчанию значение auto для свойства min-width. И, в отличие от обычных блоков, они ведут себя иначе: минимальный размер элемента по главной оси определяется размером его содержимого. Обнулим значение свойства min-width и элемент перестанет выходить за пределы flex-контейнера.

    .content {
      flex: 1;
      min-width: 0;
      padding: 20px;
      background-color: #eee;
    }
    
    Автор: 1024x768

    Кто главнее: flex-basis или width?

    В предыдущем примере мы определили ширину левой колонки с помощью свойства width. А что будет, если задать также значение для свойства flex-basis?

    .sidebar {
      flex-shrink: 0;
      flex-basis: 360px;
      width: 160px;
      background-color: #9bce90;
    }
    
    Автор: 1024x768

    Как видим, значение flex-basis перебило значение with и ширина колонки стала равной 360 пикселям. А что будет, если определить еще и свойство max-width?

    .sidebar {
      flex-shrink: 0;
      flex-basis: 360px;
      width: 160px;
      max-width: 100px;
      background-color: #9bce90;
    }
    
    Автор: 1024x768

    Max-width победил всех и ширина колонки стала равной 100 пикселям. И, конечно же, стоит сказать о том, что flex-basis всегда работает вдоль главной оси flex-контейнера. Чтобы в этом убедиться, изменим у flex-контейнера значение свойства flex-direction:

    .page {
      display: flex;
      flex-direction: column;
      padding: 20px;
      background-color: #ccc;
    }
    
    Автор: 1024x768

    Особенности работы со слоями

    Свойство z-index способно поднять flex-элемент над соседями даже при position со значением static.


    HTML:

    <div class="gallery">
      <img src="1.jpg" class="item">
      <img src="2.jpg" class="item">
      <img src="3.jpg" class="item">
    </div>
    

    CSS:

    .gallery {
      display: flex;
      padding: 20px;
      background-color: #ccc;
    }
    
    .item {
      width: 260px;
      height: 260px;
      margin-right: -50px;
      border: 2px solid #fff;
    }
    
    .item:hover {
      z-index: 10;
      box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.5);
    }
    
    Курсор мыши был наведен на второй flex-элемент, который «всплыл» несмотря на то, что у свойства position было значение static
    Автор: 1024x768

    Стоит отметить, что эта особенность существует только у прямых потомков flex-контейнера и если вам нужно выстроить слои у вложенных в него элементов, то придется задать значение relative свойству position, иначе значение свойства z-index будет проигнорировано.

    Особенности абсолютного позиционирования

    Рассмотрим особенности поведения элемента с абсолютным позиционированием, расположенного внутри flex-контейнера. Создадим контейнер, зададим ему фиксированную высоту и установим значением relative для свойства position. Внутри контейнера расположим три элемента и выровняем их по центру как по горизонтали, так и по вертикали.

    HTML:

    <div class="flex-container">
      <div class="element">1</div>
      <div class="element">2</div>
      <div class="element">3</div>
    </div>
    

    CSS:

    .flex-container {
      position: relative;
      height: 260px;
      display: flex;
      justify-content: center;
      align-items: center;
      gap: 20px;
      padding: 20px;
      background-color: #ccc;
    }
    
    Автор: 1024x768

    Теперь добавим для последнего элемента свойство position со значением absolute и, для наглядности, изменим у него цвет фона:

    .element:last-child {
      position: absolute;
      background-color: #843b3e;
    }
    
    Автор: 1024x768

    Видим, что элемент выпал из общего потока, но, вместо того чтобы переместиться в левый верхний угол flex-контейнера, для которого задано свойство position со значением relative, он расположился по центру. Это произошло из-за того, что на элемент по-прежнему влияют значения свойств justify-content и align-items, определяя его начальное положение. Однако, если ему задать координаты с помощью любого из свойств left, right, top или bottom, но он переместится так, как это сделал бы обычный элемент с абсолютным позиционированием.

    .element:last-child {
      position: absolute;
      right: 20px;
      top: 20px;
      background-color: #843b3d;
    }
    
    Автор: 1024x768

    Схлопывание внешних отступов отменяется

    Внутри flex-контейнера создадим два элемента. Первому зададим нижний внешний отступ, а второму — верхний.

    HTML:

    <div class="flex-container">
      <div class="text">Первый элемент</div>
      <div class="text">Второй элемент</div>
    </div>
    

    CSS:

    .flex-container {
      display: flex;
      flex-wrap: wrap;
      padding: 20px;
      background-color: #ccc;
    }
    
    .text {
      display: flex;
      flex-direction: column;
      justify-content: center;
      background-color: #555555;
      width: 100%;
      height: 120px;
      border: 2px solid #fff;
      font-size: 60px;
      line-height: 1.0;
      font-weight: bold;
      color: #fff;
      text-align: center;
    }
    
    .text:first-child {
      margin-bottom: 60px;
    }
    
    .text:last-child {
      margin-top: 60px;
    }
    
    Автор: 1024x768

    Отступы не схлопнулись, как это бывает у обычных блочных элементов, а наоборот сложились и суммарное расстояние между блоками составило 120 пикселей.

    Чем отличаются align-content, align-items и align-self

    Новички часто путают эти свойства. Разберемся, чем они отличаются на примерах. Создадим flex-контейнер с элементами, включим перенос элементов на другую строку, зададим контейнеру фиксированную высоту и пропишем значение center для свойства align-content. Первому и последнему элементу увеличим высоту.

    HTML:

    <div class="flex-container">
      <div class="element">1</div>
      <div class="element">2</div>
      <div class="element">3</div>
      <div class="element">4</div>
      <div class="element">5</div>
      <div class="element">6</div>
      <div class="element">7</div>
      <div class="element">8</div>
    </div>
    

    CSS:

    .flex-container {
      display: flex;
      flex-wrap: wrap;
      gap: 20px;
      padding: 20px;
      background-color: #ccc;
      height: 500px;
      align-content: center;
    }
    
    .element:first-child {
      height: 140px;
      background-color: #843b3d;
    }
    
    .element:last-child {
      height: 140px;
      background-color: #34622a;
    }
    
    Автор: 1024x768

    Оба ряда были отцентрированы по вертикали как единое целое. Теперь заменим свойство align-content на align-items:

    .flex-container {
      display: flex;
      flex-wrap: wrap;
      gap: 20px;
      padding: 20px;
      background-color: #ccc;
      height: 500px;
      align-items: center;
    }
    
    Автор: 1024x768

    Flex-контейнер как бы разделился на две половины и элементы каждой выстроились своими центрами вдоль осей. И теперь пропишем одному из элементов значение flex-start для свойства align-self:

    .element:nth-child(3) {
      align-self: flex-start;
      background-color: #33548c;
    }
    
    Автор: 1024x768

    Элемент получил собственное выравнивание и сместился к верхнему краю flex-контейнера.

    Структурные псевдоклассы против свойства order

    Создадим flex-контейнер с пятью элементами. У первого, третьего и пятого элементов изменим цвет фона.

    HTML:

    <div class="flex-container">
      <div class="element">1</div>
      <div class="element">2</div>
      <div class="element">3</div>
      <div class="element">4</div>
      <div class="element">5</div>
    </div>
    

    CSS:

    .flex-container {
      display: flex;
      flex-wrap: wrap;
      gap: 20px;
      padding: 20px;
      background-color: #ccc;
    }
    
    .element:first-child {
      background-color: #843b3d;
    }
    
    .element:nth-child(3) {
      background-color: #34622a;
    }
    
    .element:last-child {
      background-color: #33548c;
    }
    
    Автор: 1024x768

    Теперь определим значения свойства order для второго и четвертого элементов:

    .element:nth-child(2) {
      order: 1;
    }
    
    .element:nth-child(4) {
      order: 2;
    }
    
    Автор: 1024x768

    Цвета элементов остались привязаны к своей структуре в HTML: структурные псевдоклассы ориентируются на порядок тегов в разметке, а не на свойство order. Что делать, если нужно менять порядок элементов на экранах разных устройств, но при этом всегда применять стили к тому, который отображается на экране последним? Для решения этой задачи придется задействовать JavaScript.

    Разница между flex и inline-flex

    Для flex-контейнера можно задать свойству display не только значение flex, но и inline-flex. Выясним, на что это повлияет. Сначала попробуем вариант со значением flex. Создадим два контейнера с элементами и, для наглядности, раскрасим контейнеры в разные цвета.

    HTML:

    <div class="example">
      <div class="flex-container flex-container_color_1">
        <div class="element">1</div>
        <div class="element">2</div>
        <div class="element">3</div>
      </div>
      <div class="flex-container flex-container_color_2">
        <div class="element">4</div>
      </div>
    </div>
    

    CSS:

    .flex-container {
      display: flex;
      gap: 20px;
      padding: 20px;
      background-color: #ccc;
    }
    
    .flex-container_color_1 {
      background-color: #f8a4a3;
    }
    
    .flex-container_color_2 {
      background-color: #9bce90;
    }
    
    Автор: 1024x768

    Оба контейнера ведут себя как обычные блочные элементы: растягиваются во всю ширину вышестоящего блока и располагаются друг под другом. Изменим значение свойства display на inline-flex:

    .flex-container {
      display: inline-flex;
      gap: 20px;
      padding: 20px;
      background-color: #ccc;
    }
    
    Автор: 1024x768

    Теперь контейнеры выстроились в одну линию как и положено блочно-строчным элементам, а также начали обладать их типичной особенностью: если в коде HTML между элементами есть пробел или перенос строки (как раз наш случай), то тогда и пробельный символ отобразится на экране. Исправить это можно задав у вышестоящего блока значение 0 для свойства font-size. Поскольку это свойство унаследуют вложенные элементы, у них значение шрифта обязательно должно быть переопределено.

    .example {
      font-size: 0;
    }
    
    Автор: 1024x768

    Flex-контейнеры игнорируют псевдоэлементы first-letter и first-line

    Создадим flex-контейнер с текстом. Попробуем увеличить первую букву, а первую строку сделать жирной.

    HTML:

    <div class="flex-container">
      Первая буква должна быть большой, а текст в первой строке должен быть жирным.
    </div>
    

    CSS:

    .flex-container {
      display: flex;
      padding: 22px;
      background-color: #ccc;
      font-size: 30px;
      line-height: 1.5;
    }
    
    .flex-container::first-letter {
      font-size: 60px;
    }
    
    .flex-container::first-line {
      font-weight: bold;
    }
    
    Автор: 1024x768

    Видим, что внутри flex-контейнера псевдоэлементы first-letter и first-line не работают. Чтобы это исправить, поместим текст внутри какого-нибудь тега.

    HTML:

    <div class="flex-container>
      <p>Первая буква должна быть большой, а текст в первой строке должен быть жирным.</p>
    </div>
    

    CSS:

    .flex-container > p:last-child {
      margin-bottom: 0;
    }
    
    .flex-container > p::first-letter {
      font-size: 60px;
    }
    
    .flex-container > p::first-line {
      font-weight: bold;
    }
    
    Автор: 1024x768