Циклические конструкции
В Less нет стандартных циклов for
или while
. Однако, текущих возможностей формирования циклических конструкций хватает для всего круга задач, связанного с вёрсткой.
Как я уже говорил ранее, циклы строятся на принципе защиты примесей с помощью условного оператора (when
). Таким образом, нужно понимать, что все циклы в Less построены на принципе рекурсий или, как напоминает нам JavaScript, цикле с предусловием while
(выполнение условия проверяется перед тем, как войти в тело цикла).
Давайте подробно разберём пример, код которого формирует некоторое количество классов, жутко напоминающих простую сетку. Вот код с небольшими комментариями очевидных вещей:
@column-name: col; // Префикс класса сетки
@column-count: 4; // Количество классов
.generate-column(@i: 1) when (@i =< @column-count) {
.@{column-name}-@{i} {
width: @i * (100% / @column-count);
}
.generate-column(@i + 1);
}
.generate-column();
Основную работу выполняет примесь generate-column
, принимающая на вход параметр i и защищённая с помощью ключевого слова when
. Отсюда следует, что вся магия циклов в Less заключается в условной конструкции и рекурсивном вызове.
Так как примесь принимает на вход параметр i, который по сути является итерационной переменной, а также у примеси указано условие, при котором она будет работать, получается, что мы создаём простой вариант рекурсии. Она будет существовать до тех пор, пока значение переменной @i
не сравняется с таковым у @column-count
или не будет больше него.
Заглянем внутрь примеси. Здесь формируется имя селектора по правилу: префикс + номер итерации. Уже затем для этого селектора вычисляется значение ширины, как: 100% ширины, делённое на количество колонок и умноженное на шаг итерации. Обратите внимание на строку .generate-column(@i + 1)
. Эта команда просит компилятор вызвать эту же примесь, но с увеличенным на единицу шагом итерации. Весь процесс повторяется вновь, но уже с увеличенным на единицу значением переменной @i
. В этом и заключается магия.
Для наглядности процесса попробуем вместе с компилятором пройтись пару итераций:
/* Итерация 1 | @i: 1 | Новое значение @i: 2 */
.col-1 { width: 25%; }
/* Итерация 2 | @i: 2 | Новое значение @i: 3 */
.col-1 { width: 25%; }
.col-2 { width: 50%; }
/* Итерация 3 | @i: 3 | Новое значение @i: 4 */
.col-1 { width: 25%; }
.col-2 { width: 50%; }
.col-3 { width: 75%; }
/* Итерация 4 | @i: 4 | Новое значение @i: 5 */
.col-1 { width: 25%; }
.col-2 { width: 50%; }
.col-3 { width: 75%; }
.col-4 { width: 100%; }
После того, как шаг итерации достигает максимально допустимого значения, происходит выход из рекурсии и компилятор продолжает бороздить красоты нашего кода. В нашем случае этот момент наступает на четвёртом шаге рекурсии, когда новое значение переменной @i
равно 5, а максимальное число шагов итераций, заданное переменной @column-count
, определено как 4.
Таким образом строятся и более сложные циклы, в которых участвуют несколько счётчиков, примесей или даже списков.
Обработка списка
Рассмотрим ещё один пример, где в цикле генерируются вспомогательные классы на основе списка. Этот пример вы уже встречали в главе 4, когда речь шла про списки (массивы).
Напомню его устрашающий код, который тогда мог повергнуть неподготовленного читателя в ужас:
// Настройки
@column-name: col;
@column-count: 4;
@column-prefix: xs, sm, md, lg;
// Генератор селекторов
.generate-class(@indexCount, @indexPrefix: 1) when (@indexPrefix =< length(@column-prefix)) {
// Получаем элемент списка
@prefix: extract(@column-prefix, @indexPrefix);
// Формируем селектор
.@{column-name}-@{prefix}-@{indexCount} {
width: @indexCount * (100% / @column-count);
}
// Порождаем следующую итерацию
.generate-class(@indexCount, @indexPrefix + 1);
}
// Генератор сетки
.make-grid(@indexCount: 1) when (@indexCount =< @column-count) {
// Вызываем генератор селекторов
.generate-class(@indexCount);
// Порождаем следующую итерацию
.make-grid(@indexCount + 1);
}
// Вызываем генератор сетки
.make-grid();
Теперь будем разбираться с ним вплоть до мелочей. Начнём с того, что перепишем весь этот код с Less на JavaScript. Зачем? — скоро все поймёте.
Понимание деталей
Если абстрагироваться от Less к JavaScript, то здесь объявлены две функции, которые, как кирпичики лего, позволяют строить лего-замок. Каждая функция выполняет свою простейшую задачу и, на практике, все функции можно было бы объединить в одну, конечно, при условии, что вам нравится запутанный код.
Для понимания происходящего, проведём параллель между обычным JavaScript-кодом и примесями в Less.
Настройки
В коде, приведённом ниже, объявляется объект columnOptions
, хранящий настройки генератора сетки, такие как:
- Name — Имя столбца
- Count — Количество столбцов
- Prefix — Массив префиксов
// Настройки
var columnOptions = {
name: 'col',
count: 4,
prefix: ['xs', 'sm', 'md', 'lg']
};
Если сравнивать с Less, то там настройки хранятся в переменных. Так, например, в JavaScript свойство объекта Prefix соответствует переменной @column-prefix
в Less.
// Настройки
@column-name: col;
@column-count: 4;
@column-prefix: xs, sm, md, lg;
Вызов
Так как генерировать сетку на JavaScript никто не будет, то и вывод результатов будет осуществляться в консоль:
// Вызываем генератор сетки
console.log(makeGrid(columnOptions));
Разумеется, что в Less примесь вызывается стандартным образом:
// Вызываем генератор сетки
.make-grid();
Итак, переходим вместе с кодом в примесь make-grid и отслеживаем работу дальше.
Генератор сетки
Код ниже представляет собой полный эквивалент примеси make-grid, переписанной на классический ванильный JavaScript.
Переменная consoleReturn
представляет собой массив объектов, содержащих в себе пары ключей:
- Name — Имя текущего префикса (xs, sm..).
- Selector — Объект, содержащий информацию о колонке (имя и ширина), который возвращает функция
generateSelector
.
Далее в цикле производится проход по всем, указанным в настройках префиксам и на каждой итерации инициируется вызов функции, генерирующей информацию о селекторе.
Соответственно, вся полученная информация возвращается пользователю в консоль для анализа.
// Генератор сетки
var makeGrid = function(o) {
var consoleReturn = [];
for (var index = 0; index < o.count; index++) {
consoleReturn.push({
name: o.prefix[index],
selector: generateSelector(o, index)
});
}
return consoleReturn;
};
Теперь снова посмотрим на нашу оригинальную примесь make-grid и разберёмся с тем, что там происходит:
// Генератор сетки
.make-grid(@indexCount: 1) when (@indexCount =< @column-count) {
// Вызываем генератор селекторов
.generate-class(@indexCount);
// Порождаем следующую итерацию
.make-grid(@indexCount + 1);
}
Если исключить тот факт, что в Less данные возвращаются автоматически и в рекурсивно вызываемой примеси generate-class
, то можно с уверенностью сказать, что эта примесь работает так же, как и функция, написанная на JavaScript.
Вместо цикла for
здесь используется рекурсия, которая работает до тех пор, пока итерационная переменная @indexCount
меньше, либо равна количеству колонок, указанному в настройках.
На каждой итерации вызывается примесь генератора классов сетки, в которую передаётся переменная, указывающая номер итерации. Далее вызывается текущая примесь со значением итерационной переменной, увеличенной на единицу, чтобы породить рекурсию. То есть принцип действия полностью соответствует примеру, рассмотренному в начале этой части.
Проводя аналогию с функцией на JavaScript, можно выделить следующие наблюдения:
- Аналогом цикла
for
в Less является строка.make-grid(@indexCount + 1)
- Аналогом выражения цикла
for
в Less является условиеwhen
или, как его принято называть — выражение защиты примесей.
Перейдём к рассмотрению примеси, генерирующей селекторы.
Генератор селекторов
Ниже представлен эквивалент примеси generate-class на языке JavaScript, с той лишь разницей, что создавать рекурсию в JavaScript — кощунство, поэтому тут применяется стандартный цикл for
.
// Функция генерации селекторов
var generateSelector = function(o, indexCount) {
var selectors = [];
for (var index = 0; index < o.prefix.length; index++) {
var name = o.name + '-' + o.prefix[indexCount] + '-' + index;
var width = index * (100 / o.count) + '%';
selectors.push({
name: name,
width: width
});
}
return selectors;
};
Эта функция принимает на вход объект настроек и номер текущей итерации. Массив selectors
будет содержать имя и ширину селектора для всех префиксов из массива. Получается, что в нём будет храниться эквивалент CSS-кода:
.col-xs-1 {
width: 25%;
}
.col-sm-1 {
width: 25%;
}
Углубляться в то, как он будет представлен не имеет смысла, так как главное здесь — понимать суть. В итоге, из этой функции будет возвращаться массив колонок одного префикса.
Снова обратим внимание на оригинальную less-примесь и проанализируем её действие:
// Генератор селекторов
.generate-class(@indexCount, @indexPrefix: 1) when (@indexPrefix =< length(@column-prefix)) {
// Получаем элемент списка
@prefix: extract(@column-prefix, @indexPrefix);
// Формируем селектор
.@{column-name}-@{prefix}-@{indexCount} {
width: @indexCount * (100% / @column-count);
}
// Порождаем следующую итерацию
.generate-class(@indexCount, @indexPrefix + 1);
}
Как уже отмечалось выше, when
играет роль выражения цикла, а последняя в примеси строка порождает новую итерацию этой же примеси, что эквивалентно новой итерации стандартного цикла for
.
В отличии от JavaScript кода, примесь генерирует селектор и его свойства, а также возвращает их в виде CSS-кода по правилу:
Имя колонки + текущий префикс + номер итерации
Ширина колонки вычисляется тем же методом, что и в первом примере, описанном в этой части.
Таким образом, примесь будет рекурсивно выполняться до тех пор, пока не будут созданы селекторы для всех префиксов, указанных в переменной @column-prefix
.
Результаты
После того, как компилятор пройдёт все итерации для каждой примеси, он вернёт сгенерированные селекторы на то место, где была вызвана управляющая примесь make-grid.
Если не изменять определённые ранее настройки, результат будет следующим:
.col-xs-1 { width: 25%; }
.col-sm-1 { width: 25%; }
.col-md-1 { width: 25%; }
.col-lg-1 { width: 25%; }
...
.col-xs-4 { width: 100%; }
.col-sm-4 { width: 100%; }
.col-md-4 { width: 100%; }
.col-lg-4 { width: 100%; }
Вывод JavaScript функций имеет приблизительно такой же вид с поправкой на то, что там данные не предоставляют ценности для CSS.
Выводы
Полностью рассмотрев два примера оценить всю природу циклических конструкций в Less нельзя. Зато можно понять их базовую сущность. Со временем вы научитесь составлять и более сложные конструкции, если это потребует поставленная задача. Да, циклы в Less имеют слегка непривычный вид для JavaScript-разработчиков, но разобравшись с ними раз и навсегда, проблем в дальнейшем не предвидится.