От автора: всем известно, что центрирование в CSS достаточно утомительный процесс. На эту тему уже сложено масса шуток, например, «нам удалось запустить человека на луну, но мы не умеем делать вертикальное выравнивание в CSS».
Однако центрирование в CSS действительно немного запутано, особенно вертикальное, и мне кажется, все эти шутки слегка неуместны. На самом деле в CSS существует множество способов центрирования контента, просто их нужно знать.
В этой статье не будет объяснений, как работают эти методы, будут показаны способы, как обернуть эти методы в Sass mixin’ы, чтобы легче их использовать. Если вы не сильно ориентируетесь в центрировании в CSS, я порекомендую вам пару статей, чтобы вы были подкованы в этом деле.
Разобрались? Тогда начнем.
О чем статья?
В первую очередь мы сосредоточимся на центрировании элементов, находящихся внутри своих родителей, т.к. это самый распространенный случай применения абсолютного центрирования (модальные окна, контент в секциях и т.д.). Если спросить о CSS центрировании, то в качестве стандартного ответа вы получите еще один вопрос: ты знаешь размеры элемента? Причина, по которой мы слышим это, является то, что если вы не знаете размеров элемента, то лучше всего будет положиться на CSS трансформации. Данный метод поддерживается не всеми браузерами, но он очень гибок. А если вы не знаете свойства CSS transform или вам известны ширина и высота элемента, легче будет работать со значениями внешнего отступа margin.
Итак, наш mixin должен делать следующее: позиционировать верхний левый угол элемента абсолютно в центре родителя, затем сдвинуть блок влево и вверх на половину значений ширины и высоты соответственно и выполнить сопутствующие трансформации в зависимости от того какие размеры прошли в наш mixin.
Нет размеров: используем трансформации; есть размеры: используем внешние отступы. Должно получиться примерно так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
/** * Задаем относительное позиционирование, чтобы абсолютно спозиционировать *дочерний элемент */ .parent { position: relative; } /** * Абсолютно позиционируем дочерний элемент * Размеры не прошли в mixin, значит используем трансформации CSS */ .child-with-unknown-dimensions { @include center; } /** * Абсолютно позиционируем дочерний элемент * В mixin прошло значение ширины, используем margin для * горизонтальной оси и CSS трансформации для вертикальной оси */ .child-with-known-width { @include center(400px); } /** * Абсолютно позиционируем дочерний элемент * В mixin прошло значение высоты, используем margin для * вертикальной оси и CSS трансформации для горизонтальной оси */ .child-with-known-height { @include center($height: 400px); } /** * Абсолютно позиционируем дочерний элемент * В mixin прошло значение ширины, используем margin для * горизонтальной и вертикальной осей */ .child-with-known-dimensions { @include center(400px, 400px); } |
После компиляции это будет выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
§.parent { position: relative; } .child-with-unknown-dimensions { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .child-with-known-width { position: absolute; top: 50%; left: 50%; margin-left: -200px; width: 400px; transform: translateY(-50%); } .child-with-known-height { position: absolute; top: 50%; left: 50%; transform: translateX(-50%); margin-top: -200px; height: 400px; } .child-with-known-dimensions { position: absolute; top: 50%; left: 50%; margin-left: -200px; width: 400px; margin-top: -200px; height: 400px; } |
Отлично, код выше выглядит немного большим, но помните, что мы его использовали только для демонстрации того, чего необходимо достичь. Скорее всего, вы вряд ли будете использовать все случаи в нашем примере.
Создание mixin’а
Итак, продолжим. Из предыдущих кусков кода мы уже знаем нашу сигнатуру: она содержит два опциональных параметра, $width and $height.
1 2 3 4 5 6 7 8 9 10 11 |
/// Горизонтальное, вертикальное или абсолютное центрирование элемента внутри /// родителя /// Если известны размеры, этот mixin будет использовать значения margin /// на основе размеров элемента. /// Иначе mixin будет использовать CSS трансформации, которые поддерживаются /// не всеми браузерами, но более гибкие, т.к. они не зависят от размеров. /// /// @author Hugo Giraudel /// /// @param {Length | null} $width [null] - Element width /// @param {Length | null} $height [null] - Element height /// @mixin center($width: null, $height: null) { .. } |
Продолжаем. В любом случае mixin должен абсолютно позиционировать элемент, поэтому можно начать со следующего.
1 2 3 4 5 6 7 |
@mixin center($width: null, $height: null) { position: absolute; top: 50%; left: 50%; // ниже еще немного магии... } |
Необходимо хорошо продумать код. Давайте остановимся пока на этом и проанализируем различные опции:
На основе таблицы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@mixin center($width: null, $height: null) { position: absolute; top: 50%; left: 50%; @if not $width and not $height { // используем `translate` } @else if $width and $height { // используем `margin` } @else if not $height { // используем `margin-left` и `translateY` } @else { // используем `margin-top` и `translateX` } } |
После того, как мы получили каркас нашего mixin’а, осталось заполнить его нужным CSS.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
@mixin center($width: null, $height: null) { position: absolute; top: 50%; left: 50%; @if not $width and not $height { transform: translate(-50%, -50%); } @else if $width and $height { width: $width; height: $height; margin: -($width / 2) #{0 0} -($height / 2); } @else if not $height { width: $width; margin-left: -($width / 2); transform: translateY(-50%); } @else { height: $height; margin-top: -($height / 2); transform: translateX(-50%); } } |
Обратите внимание: #{0 0} – маленькая хитрость для предотвращения слегка агрессивного сворачивания из Sass, которое привело бы к отступам: margin: mt 0 ml вместо margin: mt 0 0 ml. Пока что все отлично.
Идем дальше
Существует пара способов расширить наш mixin, например, подключение @supports правила для проверки поддержки CSS трансформаций или для предложения (или разрешения) на использование библиотеки Modernizr со стилями отображения. Все это в зависимости от поддержки CSS трансформаций. Также можно сделать еще больше агрессивных проверок на правильность значений ширины и высоты.
Быть может, вы спросите, а стоило ли так усложнять наш mixin. Mixin сам по себе уже имеет сложность организации циклов шестого уровня, что достаточно облегчает Sass. Код по-прежнему нормальный, но, добавив пару строк, мы еще больше увеличиваем цикломатическую сложность.
А что насчет Flexbox?
Я уверен, некоторые из вас, читателей, прыгая на кресло, думаете, как бы применить Flexbox внутри родителя для центрирования элементов. И в самом деле, возможно, такое решение самое простое, если вы можете его себе позволить.
Основное различие между нашим примером и Flexbox в том, что последний работает с родителем, а наш способ работает с дочерними элементами (при условии, что любой из предков имеет свойство position отличное от static).
Для центрирования дочернего (их) элемента (ов) относительно родителя, вам необходимо напечатать пару-тройку свойств. Можете написать mixin, placeholder, класс или еще что-то.
1 2 3 4 5 |
@mixin center-children { display: flex; justify-content: center; align-items: center; } |
С вендорными префиксами (через mixin или Autoprefixer) данный метод должен работать в большинстве браузеров.
1 2 3 |
.parent { @include center-children; } |
Результат, как вы можете представить, будет таков:
1 2 3 4 5 |
.parent { display: flex; justify-content: center; align-items: center; } |
Заключительные мысли
Нам был необходим небольшой mixin для легкого центрирования элементов внутри родителя; наш пример работает, и работает хорошо. Наш mixin не только достаточно умен, чтобы работать в не зависимости от того, имеет ли элемент конкретные размеры или нет, но также он предоставляет нам дружелюбный и интуитивный API, что крайне важно.
Глядя на код, любой поймет, что строка @include center подключает вспомогательный код со своей логикой, который центрирует элемент относительно родителя. Тем не менее, необходимо помнить, что для работы нашего кода первый родитель (или любой родитель в DOM) обязан иметь свойство position отличное от static!
Поиграться с кодом можно на SassMeister: //sassmeister.com/gist/550809f5aa00b73d932c.
Автор: Hugo Giraudel
Источник: //www.sitepoint.com/
Редакция: Команда webformyself.