Кроссбраузерный Flexbox

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

Заголовок с фиксированной высотой и контейнер с полосой прокрутки

Рассмотрим заголовок Header с фиксированной высотой и контейнер с полосой прокрутки Controls/scroll:Container, расположенный под этим заголовком.

Ожидаемый результат

  • HTML
    <div class="demo-Flex">
       <div class="demo-Flex_header">
          <div>Header</div>
          <Controls.scroll:Container class="demo-Flex_scroll">
             <div>scrollContent</div>
             ...
             <div>scrollContent</div>
          </Controls.scroll:Container>
       </div>
    </div>
  • CSS
    .demo-Flex {
       display: flex;
       flex-direction: column;
       height: 100%;
    }
    .demo-Flex_header {
       height: 40px;
    }
    .demo-Flex_scroll {
       height: 100%;
    }

Применив код, продемонстрированный выше, ожидается получение контейнера с заголовком следующего вида:

Реальный результат

Итак, в результате для IE10, Chrome, FF, Safari высота контейнера не уменьшилась под оставшееся пространство, и стала равна высоте родительского контейнера.

В Edge, Chrome, FF, Safari высота заголовка стала меньше. Свойство height: 40px; не было выполнено, реально высота заголовка равна высоте его содержимого.

Анализ верстки. Проблема сжимающегося заголовка

Рассмотрим свойство flex-shrink.

Согласно спецификации, первоначальное значение свойства равно 1 для всех браузеров, исключая IE10, который использует 0 вместо 1. Это объясняет изменение высоты заголовка во всех браузерах, кроме IE10.

Решение

Таким образом, проблема сжимающегося заголовка решается, если для заголовка установить flex-shrink: 0; соответственно, чтобы мог уменьшаться контейнер Controls/scroll:Container, нужно установить для него flex-shrink: 1.

Следовательно, код css-файла должен измениться следующим образом:

  • CSS
    .demo-Flex {
       display: flex;
       flex-direction: column;
       height: 100%;
    }
    .demo-Flex_header {
       height: 40px;
       flex-shrink: 0;
    }
    .demo-Flex_scroll {
       height: 100%;
       flex-shrink: 1;
    }

Ожидаемый результат будет получен в браузерах IE10, Edge.

Анализ верстки. Проблема не сжимающегося контейнера

Проблему не сжимающегося контейнера позволяет избежать использование контрола Controls/scroll:Container.

Далее рассмотрим, каким образом эта проблема решается.

Рассмотрим значения свойств min-height и min-width.

Согласно спецификации для flex элементов:

  • значения свойств min-height/min-width равно auto, если значение overflow равно visible;
  • значения свойств min-height/min-width равно 0, если значение overflow отлично от visible.

Auto — новое значение в спецификации для свойств min-height/min-width по умолчанию.

Если значение min-height/min-width равно auto, элемент не может быть меньше размера своего содержимого вдоль главной оси.

В IE10 для flex-элементов значение свойств min-height/min-width равно 0px, для остальных браузеров это значение равно auto. Это объясняет получение ожидаемого результата в браузерах IE10, Edge.

Решение

Таким образом, проблема решается одним из двух способов:

  • CSS
    /* вариант 1 */
    .demo-Flex {
       display: flex;
       flex-direction: column;
       height: 100%;
    }
    .demo-Flex_header {
       height: 40px;
       flex-shrink: 0;
    }
    .demo-Flex_scroll {
       height: 100%;
       flex-shrink: 1;
       overflow: hidden;
    }
  • CSS
    /* вариант 2, поддерживающий явную логику */
    .demo-Flex {
       display: flex;
       flex-direction: column;
       height: 100%;
    }
    .demo-Flex_header {
       height: 40px;
       flex-shrink: 0;
    }
    .demo-Flex_scroll {
       height: 100%;
       flex-shrink: 1;
       min-height: 0;
    }

Рекомендуется использовать второй вариант.

Расположение блоков по горизонтали

Следующий типовой пример аналогичен первому, только в горизонтальной ориентации.

Это размещение нескольких блоков по горизонтали. Например, размещение двух блоков, Block 1 и Block 2, с требованием, чтобы Block 1 имел фиксированную ширину, а Block 2 занимал все оставшееся пространство.

Для корректного решения достаточно поменять flex-direction, а также height заменить на width:

  • CSS
    /* для вертикальной ориентации */
    .demo-Flex {
       display: flex;
       flex-direction: column;
       height: 100%;
    }
    .demo-Flex_header {
       height: 40px;
       flex-shrink: 0;
    }
    .demo-Flex_scroll {
       height: 100%;
       flex-shrink: 1;
       min-height: 0;
    }
  • CSS
    /* для горизонтальной ориентации */
    .demo-Flex {
       display: flex;
       flex-direction: row;
       width: 100%;
    }
    .demo-Flex_header {
       width: 40px;
       flex-shrink: 0;
    }
    .demo-Flex_scroll {
       width: 100%;
       flex-shrink: 1;
       min-width: 0;
    }

Несколько фиксированных блоков

Рассмотрим вариант, в котором используется два блока (Header и Footer) с фиксированной высотой и контейнер с полосой прокрутки Controls/scroll:Container, занимающий все оставшееся пространство.

Корректное решение

  • HTML
    <div class="demo-Flex">
       <div class="demo-Flex_header">...</div>
       <Controls.scroll:Container class="demo-Flex_scroll">
          <div>scrollContent</div>
          ...
          <div>scrollContent</div>
       </Controls.scroll:Container>
       <div class="demo-Flex_footer">...</div>
    </div>
  • CSS
    .demo-Flex {
       display: flex;
       flex-direction: column;
       height: 100%;
    }
    .demo-Flex_header, .demo-Flex_footer {
       height: 40px;
       flex-shrink: 0;
    }
    .demo-Flex_scroll {
       height: 100%;
       flex-shrink: 1;
       min-height: 0;
    }

Результат

Проблема использования calc()

При использовании calc() могут возникать следующие проблемы:

  • Изменение дизайна влечёт за собой необходимость пересчёта всех отступов.
  • Возникает зависимость блоков друг от друга.
  • Сложно поддерживать вёрстку.

Корректное использование этой css-функции требует понимания условий, при которых ее можно использовать, а при которых — нельзя.

В качестве примера рассмотрим типовой реестр с использованием панели массовых операций Controls/operations:Panel.

Корректное решение

  • HTML
    <div class="demo-Flex">
       <div class="demo-Flex_header">
          <div>Header</div>
       </div>
       <Controls.operations:Panel />
       <Controls.scroll:Container class="demo-Flex_scroll">
          <div>scrollContent</div>
          ...
          <div>scrollContent</div>
       </Controls.scroll:Container>
    </div>
  • CSS
    .demo-Flex {
       display: flex;
       flex-direction: column;
       height: 100%;
    }
    .demo-Flex_header {
       height: 40px;
       flex-shrink: 0;
    }
    .demo-Flex_scroll {
       height: 100%;
       flex-shrink: 1;
       min-height: 0;
    }

Результат:

Как известно, панель массовых операций умеет скрываться и показываться. Соответственно, контент, расположенный под панелью, должен увеличиваться и уменьшаться по высоте. В нашем примере в качестве такого контента выбран наиболее часто используемый контейнер Controls/scroll:Container; контейнер должен занимать все оставшееся пространство.

До использования технологии Flexbox значения соответствующих свойств CSS рассчитывались с помощью calc(). На контейнер, содержащий Header, Controls/operations:Panel и Controls/scroll:Container, устанавливался класс, который задавал корректное значение для calc().
При добавлении новых блоков (например, Footer под Controls/scroll:Container) такое решение приводило к ошибкам, приходилось менять значение для calc().

С использованием технологии Flexbox в типовых раскладках, подобных проблем стало намного меньше. Можно менять раскладки, добавляя произвольные блоки, не заботясь о возможных дополнительных проблемах.

Использование calc() теперь актуально в решениях маленьких локальных задач, в которых, с почти стопроцентной вероятностью, не будет меняться макет.

Решение ошибок верстки

Страница скроллится не до конца

На самом деле, это не страница скроллится не до конца, а контейнер выходит за пределы страницы.

Для нахождения проблемного места в подобных случаях используем следующий алгоритм.

Алгоритм поиска ошибки

Обход нашего DOM-дерева начинаем с body.

  1. Смотрим, чему равна высота родительского элемента.
  2. Суммируем высоты дочерних элементов, которые лежат в потоке верстки.
  3. Если высота родительского блока не равна сумме высот дочерних, проблемным блоком является один из дочерних. Чаще всего это элемент, у которого не фиксированная высота.
  4. Если высота родительского блока равна сумме высот дочерних, берем дочерний элемент, у которого высота не фиксированная и переходим к пункту 1.

Как решать проблему у найденного блока, было рассмотрено выше.

Ошибка flexbox на iPad. Не работает прокрутка.

Разберем проблему с высотой элементов в Safari на iOS, из-за которой в реестрах не работает прокрутка.

Типовая верстка реестров в наших продуктах выглядит следующим образом:

<div class="first">
  <div class="head"></div>
  <div class="second">
    <div class="scrollContainer">
      <div class="listViewItem"></div>
      ...
      <div class="listViewItem"></div>
    </div>
  </div>
</div>

Из верстки следует, что высота блока с классом second должна быть 100% минус высота блока с классом head.

Перед нами появляется 2 пути решения задачи: calc и flexbox. Проблемы использования calc описаны в статье, но для данной задачи он не подойдет.

Используя flexbox, мы получим следующие стили:

.first {
   display: flex;
   height: 100%;
   flex-direction: column;
}
.second {
   min-height: 0;
}

Подробнее о min-height: 0; читайте в статье.

В итоге мы получили решение, которое правильно работает в Chrome, но не работает в Safari. В Safari мы получили, что блок с классом second имеет правильную высоту, но блок с классом scrollContainer не смог понять, откуда ему брать высоту, и просто поставил себе высоту по контенту.

Для того, чтобы ваши реестры работали в Safari так же как в Chrome, нужно на всех блоках, которые находятся выше scrollContainer, использовать стиль height: 100%.

Итоги

  1. Для верстки реестров лучше использовать технологию Flexbox, для маленьких локальных задач можно использовать calc().
  2. Нужно всегда указывать свойство flex-shrink, чтобы обеспечить одинаковое поведение в современных браузерах и в IE.
  3. Нужно использовать min-height / min-width для flex-блоков, с overflow равным visible.

Использование предустановленных CSS-классов для вёрстки FlexBox

Используйте следующие классы для разметки при помощи FlexBox:

ws-flexbox
ws-inline-flexbox
ws-flex-row
ws-flex-column
ws-flex-row-reverse
ws-flex-column-reverse
ws-flex-first
ws-flex-unordered
ws-flex-grow-0 ... ws-flex-grow-12
ws-flex-shrink-0 ... ws-flex-shrink-12

ws-flex-wrap
ws-flex-nowrap
ws-flex-wrap-reverse

ws-justify-content-start
ws-justify-content-end
ws-justify-content-center
ws-justify-content-between
ws-justify-content-around

ws-align-items-start
ws-align-items-end
ws-align-items-center
ws-align-items-baseline
ws-align-items-stretch

ws-align-content-start
ws-align-content-end
ws-align-content-center
ws-align-content-between
ws-align-content-around
ws-align-content-stretch

ws-align-self-auto
ws-align-self-start
ws-align-self-end
ws-align-self-center
ws-align-self-baseline
ws-align-self-stretch
ws-ellipsis