Упорядочивание багов кроссбраузерности Flexbox

Перевод статьи Normalizing Cross-browser Flexbox Bugs с сайта philipwalton.com, c разрешения автора— Филипа Уолтона.

Обновлено: в качестве дополнения к этой статье я создал Github-репозиторий Flexbugs (перевод на css-live.ru): поддерживаемый сообществом список проблем с flexbox и кроссбраузерных обходных путей для них. Цель такова, что если вы разрабатываете сайт, используя flexbox, и при этом что-то работает не так, как вы ожидали, то здесь вы можете найти решение.

Давным-давно в сентябре 2013, при тестировании своего проекта «Решено при помощи Flexbox», я обнаружил баг в Internet Explorer 10 и 11, который не давал моему прижатому подвалу действительно прижаться к низу страницы. Я потратил некоторое время, пытаясь обойти эту проблему, но все мои попытки оказались тщетны.

Сначала я был по-настоящему раздражён. До появления flexbox было невозможно построить макет с прижатым подвалом одним лишь CSS, пока вы не знали точные размеры шапки и подвала [1]. Flexbox изменил это — наконец-то у нас есть приличные решения на CSS для сегодняшних современных и отзывчивых макетов.

После своего первого разочарования я в конце концов пришёл к выводу, что это не так уж и важно, и выпустил проект, несмотря на неправильное поведение. Т.е. с точки зрения прогрессивного улучшения моё решение было всё ещё довольно хорошим. Оно работало в Chrome, Firefox, Opera, и Safari, и, хотя оно не идеально работало в Internet Explorer, тем не менее оно не разваливалось совсем. Контент всё ещё был доступным, и мог некорректно отображаться только на странице с минимальным содержимым. На длинной странице это выглядело так же, как и в других браузерах.

Несколько недель назад я получил pull request на Github, в котором проблема прижатого подвала была решена при помощи @media-правила только для IE. Это заставило меня снова задуматься о проблеме, и я был полон решимости найти решение, которое не требует никаких хаков для конкретных браузеров.

Как выяснилось, решение было, и оно было возможным всё время! Я просто не копнул достаточно глубоко.

В этой статье я объясню решение, разберу по шагам, как я к нему пришел, и расскажу о браузерных багах, обнаруженных по пути. Также дам несколько рекомендаций о том, как писать более кроссбраузерный flexbox-код в будущем.

Итак, что за баги?

Спецификация flexbox ещё не завершена, так что, естественно, будут некоторые отставания между последними черновиками и браузерными реализациями. Эта статья не собирается указывать пальцем на тот или иной браузер как на отстающий, напротив, она нужна, чтобы помочь фронтенд-разработчикам делать то, что мы делаем лучше всего — управлять браузерными несоответствиями.

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

  • В IE 10-11 flex-элементы игнорируют высоту их родительского контейнера, если она установлена при помощи свойства min-height.
  • Chrome, Opera и Safari не хотят по умолчанию устанавливать минимальный размер flex-элементам по контенту.
  • IE 10-11 не учитывает безразмерные значения flex-basis в сокращённой записи flex.

Баг с min-height

В Internet Explorer 10 и 11 свойство min-height может быть использовано в размере для вертикальных flex-контейнеров, но в этом случае дочерние flex-элементы контейнера будет вести себя, как если бы они не знали размер их родителя — как если для них всех не была установлена высота.

Это является проблемой для моего демо-примера с прижатым подвалом, поскольку раскладки прижатого подвала обычно требуют объявления min-height в 100% (или 100vh), чтобы убедиться, что контентная область не должна становиться меньше, чем окно браузера.

Поскольку min-height не собирается работать, я вынужден найти другой путь.

Минимальный размер flex-элементов по контенту

Когда flex-элементы слишком велики, чтобы уместиться в свой контейнер то алгоритм flex-раскладки предписывает этим элементам пропорционально уменьшаться, в соответствии с их свойством flex-shrink. Но вопреки тому, что позволяет большинство браузеров, им не предполагается уменьшаться неограниченно. Они не должны становиться меньше, чем указывают их свойства минимальной высоты и ширины, а если эти свойства не заданы, то их минимальным размером должен быть минимальный размер их содержимого по умолчанию.

В соответствии с текущей спецификацией flexbox:

По умолчанию, flex-элементы не будут уменьшаться сверх минимального размера их содержимого (длина самого длинного слова или элемента с фиксированным размером). Чтобы это изменить, установите свойство min-width или min-height.

Chrome, Opera и Safari в настоящее время игнорируют это правило и позволяют flex-элементам уменьшиться до нуля. Как результат, вы получаете переполнение контента.

Безразмерный flex-basis

До выхода IE10 спецификация flexbox заявляла, что при использовании сокращённой записи flex для предпочтительного размера flexbox-элементов требуется единица измерения:

Если <предпочтительным размером> является «0», то, чтобы избежать двусмысленности, он должен быть указан с единицей измерения (напр. «0px»); безразмерный ноль будет интерпретироваться как flex-grow или flex-shrink, либо как синтактическая ошибка.

В спецификации это уже не так, но IE 10-11 всё ещё принимают это за правду. Если вы используете объявление flex: 1 0 0 в одном из этих браузеров, это будет ошибкой и целое правило (включая любые свойства flex’oв) будет проигнорировано.

Найденное альтернативное решение для прижатого подвала

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

Вот разметка, которую я использовал для макета прижатого футера:

<body class="Site">

  <header class="Site-header"></header>

  <main class="Site-content"></main>

  <footer class="Site-footer"></footer>

</body>

А вот CSS, прижимающий подвал в любом браузере, соблюдающем спецификацию:

.Site {

  display: flex;

  flex-direction: column;

  min-height: 100vh;

}

.Site-content {

  flex: 1;

}

Теперь, как уже упоминалось, этот CSS должен работать, но из-за существующих багов и факта, что не все браузеры полностью следуют спецификации, он не работает.

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

Решение, которое зависит от багов вообще не решение.

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

  • работать в любых браузерах [2]
  • продолжать работать даже после того, как браузеры исправят поломанное поведение
  • не полагаться на хак для определённого браузера

Использование height вместо min-height

Если вы разрабатывали сайты во времена Internet Explorer 6, то возможно помните, что этот браузер никогда не поддерживал свойство min-height (или min-width). Однако, он обрабатывал height:100% также, как другие браузеры обрабатывали min-height:100%, поэтому все оригинальные решения прижатого подвала советовали устанавливать height:100% в таблице стилей только для IE 6.

Зная об этом, я первым делом попробовал применить height:100vh вместо min-height:100vh в качестве обходного пути. Это действительно сработало в IE, но не работало в Chrome, поэтому я сразу же отказался от этого решения.

Как оказалось, мне следовало прочитать спецификацию более подробно, а не ограничиться предположением, что Chrome был прав, а IE ошибался.

В CSS вы как правило предпочитаете использовать min-height вместо height, чтобы уберечь себя от устрашающего переполнения. Когда контента слишком много, явная высота будет означать либо обрезку или переполнение содержимого, либо появление скролла. И во многих ситуациях все эти варианты плохи. Тем не менее, когда вы имеете дело с элементом body (поскольку вы ведь прижимаете подвал), скролл не является такой уж большой проблемой. Это фактически то, что вы ожидаете. Поэтому, если вы определили явную высоту 100vh для body и контента слишком много, то конечный результат должен быть тем же самым.

Тогда возникает вопрос: почему это не работает в Chrome?

Баги с определением минимального размера

Ранее я упоминал, что некоторые браузеры ошибочно позволяют flex-элементам становиться меньше чем их минимальный размер контента по умолчанию, что в результате приводит к переполнению содержимого. Вот почему при тестировании в Chrome замена min-height для height не работала.

Как должно быть: такие элементы, как шапка, подвал и контент должны уменьшиться до дефолтного минимального размера по контенту (но не меньше). Если контента у этих элементов (всех вместе) больше, чем может вместиться в экран, то элемент body должен переполняться с появлением скролла, как это обычно происходит. Шапка, подвал и контент должны отображаться нормально, один над другим без переполнения.

Как получилось на самом деле: Chrome позволил элементам шапки, подвала и контента уменьшиться до такой степени, что они стали меньше, чем их минимальный размер содержимого по умолчанию. Как результат, переполнение произошло не у элемента body, а у самих элементов шапки, подвала и контента. И поскольку по умолчанию значение overflow этих элементов visible, их контент стал перекрывать друг друга. Переполнение контентной части перекрыло подвал, который был зафиксирован внизу страницы.

К счастью, есть лёгкое решение этой проблемы.

Спецификация flexbox определяет начальное значение flex-shrink как «1», но говорит, что элементы не должны быть меньше их минимального размера содержимого по умолчанию. Можно добиться практически точно такого же поведения, используя значение 0 для flex-shrink вместо 1[3]  по умолчанию. Если элемент уже принимает размер по содержимому, и ему не заданы значения width, height или flex-basis, то при установке flex-shrink:0 он отобразится точно так же — но избежит этого бага.

Избегайте безразмерного flex-basis

Баг с безразмерным flex-basis обходится явно проще всех из этих трех, но вот отследить его, столкнувшись с ним на практике, возможно, сложнее всего

Моим изначальным решением проблемы прижатого подвала было применение объявления flex:1 для главного элемента в контенте. Поскольку значение 1 для свойства flex — сокращённая запись для 1 1 0px [4], и поскольку я знал, что мне не нужно какое-либо сжатие, то вместо сокращённой записи я решил применить 1 0 0px.

Это работало просто замечательно, пока я не протестировал это решение в IE.

Проблема оказалась в сочетании этого бага с моим CSS-минимизатором: минимизатор конвертирует 1 0 0px в 1 0 0 (который имеет безразмерное значение flex-basis), поэтому IE 10-11 проигнорировали это объявление целиком.

Как только я обнаружил корень проблемы, исправление было простым. Либо установить явное значение flex-basis, либо использовать 0% в сокращённой записи flex. Заметьте, что использование 0% лучше чем 0px, поскольку большинство минимизаторов не трогают процентные значения по другим причинам.

Собираем всё воедино

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

  • свойство min-height для вертикальных flex-контейнеров не применяется к их дочерним flex-элементам в IE 10-11. По возможности используйте height вместо min-height.
  • Chrome, Opera и Safari не учитывают минимальный размер по контенту flex-элементов по умолчанию. Установите flex-shrink в 0 (вместо значения 1 по умолчанию), чтобы избежать нежелательного уменьшения.
  • Не используйте безразмерные значения flex-basis в сокращённой записи flex, т.к. в IE 10-11 будет ошибка. Также используйте 0% вместо 0px, т.к. минимизаторы часто конвертируют 0px в 0 (что является безразмерным и создаст такую же проблему)

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

  • оно работает во всех браузерах.
  • оно соответствует спецификации, поэтому должно продолжать работать, когда баги будут исправлены.
  • оно не содержит никаких хаков для конкретных браузеров.

Я добавил комментарии в CSS, чтобы пояснить, какие части являются обходными путями:

/*

 * 1. Избегайте бага с `min-height` в IE 10-11 

 * 2. Установите `flex-shrink` в `0`, 
чтобы не дать этим элементам в Chrome, Opera и Safari 
стать меньше чем их минимальный размер контента по умолчанию. 

*/ 

.Site {

  display: flex;

  flex-direction: column;

  height: 100vh; /* 1 */

}

.Site-header,

.Site-footer {

  flex-shrink: 0; /* 2 */

}

.Site-content {

  flex: 1 0 auto; /* 2 */

}

Также смотрите это новое решение в действии, проверьте обновления проекта «Решено при помощи Flexbox: демо-пример прижатого подвала»

Примечание:

  1. Технически, если вам не нужна поддержка IE 7 и выше, то вы можете построить макет прижатого подвала при помощи display:table, которое позволяет  не учитывать высоту шапки/подвала. Хотя очевидно, что это скорее хак, которому случайно повезло оказаться кроссбраузерным, а не современное прогрессивное решение.
  2. Когда я говорю «все браузеры», то имею ввиду все браузеры, которые реализуют версию спецификации flexbox, датированную мартом 2012 или новее. Другими словами, это должно работать во всех браузерах, которые утверждают, что они поддерживают современный flexbox.
  3. Применение flex-basis:0 решает подавляющее большинство проблем, связанных с этим багом, но не все из них. Если вы хотите, чтобы flex-элементы становились меньше, но не меньше чем размер контента по умолчанию, это решение не будет работать.
  4. Во flexbox-спецификации от марта 2014 года обновлены значения сокращённой записи flex:1 с 1 1 0px на 1 1 0%.

P.S. Это тоже может быть интересно:

4 Комментарии

  1. Vladimir

    Где-то с осени использую свой шаблон с прижатым резиновым футером, тоже на flex(хотя с флекс пока еще плохо дружу). Правда я еще припелил туда старые версии flex, чтоб в «старых» браузерах работало, а для ИЕ сделал через display:table.
    Спасибо за статью. Допилю еще свой шаблон.

  2. Паки

    Все бы ничего с этим футером, но в мобильных браузерах такое решение не канает. Адресная строка при скролле странички верх-вниз прячется/появляется, соответственно, vh автоматом перерасчитывается. В результате чего футер прыгает, как мандавошка, и смотрится феерически.

    1. Alex

      Как можно решить данную проблему? Реально дергается верстка ((

      1. Паки

        Нужно к каждому экрану в стилях просчитать высоту футера. От нее отнять столько-то процентов урл-бара.

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.