Центрування за допомогою Sass

333

Від автора: всім відомо, що центрування в CSS досить стомлюючий процес. На цю тему вже складено маса жартів, наприклад, «нам вдалося запустити людину на місяць, але ми не вміємо робити вертикальне вирівнювання в CSS.

Однак центрування в CSS дійсно трохи заплутано, особливо вертикальне, і мені здається, всі ці жарти злегка недоречні. Насправді в CSS існує безліч способів центрування контенту, просто їх треба знати.

У цій статті не буде пояснень, як працюють ці методи, будуть показані способи, як обернути ці методи в Sass mixin’и, щоб легше їх використовувати. Якщо ви не дуже орієнтуєтесь в центруванні в CSS, я порекомендую вам пару статей, щоб ви були підковані в цій справі.

Як центрувати об’єкти в CSS

Центрування в CSS: Повний гайд

Розібралися? Тоді почнемо.

Про що стаття?

В першу чергу ми зосередимося на центруванні елементів, що знаходяться всередині своїх батьків, т. к. це найпоширеніший випадок застосування абсолютного центрування (модальні вікна, контент в секціях тощо). Якщо запитати про CSS центруванні, то в якості стандартного відповіді ви отримаєте ще одне питання: ти знаєш розміри елемента? Причина, по якій ми чуємо це, є те, що якщо ви не знаєте розмірів елемента, то краще всього буде покластися на CSS трансформації. Даний метод підтримується не всіма браузерами, але він дуже гнучкий. А якщо ви не знаєте властивості CSS transform або вам відомі ширина і висота елемента, легше буде працювати зі значеннями зовнішнього відступу margin.

Отже, наш mixin повинен робити наступне: позиціонувати верхній лівий кут елемента абсолютно в центрі батька, потім зрушити блок вліво і вгору на половину значень ширини і висоти відповідно і виконати супутні трансформації в залежності від того, які розміри пройшли в наш mixin.

Немає розмірів: використовуємо трансформації; є розміри: використовуємо зовнішні відступи. Повинно вийти приблизно так:

/**
* Задаємо відносне позиціонування, щоб абсолютно орієнтувати
*дочірній елемент
*/
.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);
}

Після компіляції це буде виглядати так:

§.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.

/// Горизонтальне, вертикальне або абсолютна центрування елемента всередині /// батьків
/// Якщо відомі розміри, цей mixin буде використовувати значення margin /// на основі розмірів елемента.
/// Інакше mixin буде використовувати CSS трансформації, які підтримуються
/// не всіма браузерами, але більш гнучкі, оскільки вони не залежать від розмірів.
///
/// @author Hugo Giraudel
///
/// @param {Length | null} $width [null] – width Element
/// @param {Length | null} $height [null] – height Element
///
@mixin center($width: null, $height: null) { .. }

Продовжуємо. У будь-якому випадку mixin повинен абсолютно позиціонувати елемент, тому можна почати з наступного.

@mixin center($width: null, $height: null) {
position: absolute;
top: 50%;
left: 50%;
// нижче ще трохи магії…
}

Необхідно добре продумати код. Давайте зупинимося поки на цьому і проаналізуємо різні опції:

Центрування за допомогою Sass

На основі таблиці:

@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.

@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, клас або ще щось.

@mixin center-children {
display: flex;
justify-content: center;
align-items: center;
}

З вендорными префіксами (через mixin або Autoprefixer) даний метод повинен працювати в більшості браузерів.

.parent {
@include center-children;
}

Результат, як ви можете уявити, буде такий:

.parent {
display: flex;
justify-content: center;
align-items: center;
}

Заключні думки

Нам був необхідний невеликий mixin для легкого центрування елементів всередині батьків; наш приклад працює, і працює добре. Наш mixin не тільки досить розумний, щоб працювати в не залежності від того, чи має елемент конкретні розміри чи ні, але також він надає нам доброзичливий і інтуїтивний API, що вкрай важливо.

Дивлячись на код, будь-хто зрозуміє, що рядок @include center підключає допоміжний код зі своєю логікою, який центрує елемент щодо батьків. Тим не менше, необхідно пам’ятати, що для роботи нашого коду перший батьки (або будь-який батько в DOM) зобов’язаний мати властивість position відмінне від static!

Погратися з кодом можна на SassMeister: http://sassmeister.com/gist/550809f5aa00b73d932c.