Введение

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

Начать я предлагаю с небольшого напоминания о том, как делать не нужно, даже если очень хочется. Посмотрите на фреймворк Alesya, который разработал товарищ, обучающий Less других людей. Постарайтесь внимательно посмотреть на файл ./core/functions.less. Хотя нет, не переходите по ссылке, просто посмотрите на скриншот небольшой части этого файла. Когда первый раз увидел — я просто выпал в осадок.

Фреймворк Alesya

Оправдание — циклы очень медленные, как следствие, скорость трансляции Less в CSS снижается, а время трансляции увеличивается. Pentium 4? Без обид, но это расстраивает.

Приведу отрывок из книги «HTML5 для веб-дизайнеров» за авторством Джереми Кит, который не так давно стал героем пабликов в ВК:

Предположим, что живет на свете злой урод, который ненавидит веб и всех пользователей Интернета. Этому господину наверняка плевать, что встраивать на страницу аудиофайл, который начинает проигрываться автоматически, невероятно грубо и глупо. С помощью атрибута autoplay можно удовлетворить его глубоко порочные желания.

<audio src="witchitalineman.mp3" autoplay></audio>

Если вы когда-нибудь будете так использовать атрибут autoplay, знайте: я вас найду.

Jeremy Keith

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

Расширение селекторов

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

Имеем типичный для наших дней прерыватель потока:

.clearfix {
  &:before,
  &:after {
    content: " ";
    display: table;
  }
  &:after {
    clear: both;
  }
}

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

Например, так:

.navbar {
  &:extend(.clearfix all);
  ...
}

.navbar-collapse {
  &:extend(.clearfix all);
  ...
}

.ad {
  &:extend(.clearfix all);
  ...
}

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

.clearfix:before,
.clearfix:after,
.navbar:before,
.navbar:after,
.navbar-collapse:before,
.navbar-collapse:after,
.ad:before,
.ad:after {
  content: " ";
  display: table;
}

.clearfix:after,
.navbar:after,
.navbar-collapse:after,
.ad:after {
  clear: both;
}

Конечно, злоупотреблять этим не стоит. Честно говоря, советую использовать расширение селекторов только для clearfix и в случаях, когда действует правило: «Ну очень надо, ничего поделать не могу, иначе будет хуже». Не стоит использовать расширение селекторов тогда, когда происходят махинации со стилями элементов. Наиболее частая и глупая ошибка:

.class {
  color: #fff;
  background: #fff;
}

.new-class {
  &:extend(.class);
}

// .class,
// .new-class {
//   color: #fff;
//   background: #fff;
// }

Так делать нельзя. Это глупо. Лучше использовать .class как примесь:

.class {
  color: #fff;
  background: #fff;
}
 
.new-class {
  .class;
}

// .class {
//   color: #fff;
//   background: #fff;
// }
// .new-class {
//   color: #fff;
//   background: #fff;
// }

Запомните это!

Параметры импорта

Пожалуйста, обратите внимание на раздел документации «параметры директивы импорта».

Я приведу лишь краткое описание самых важных из этих параметров:

reference

Позволяет использовать .less файлы, но не выводить их содержимого без явного вызова. Очень полезный параметр, если вы пользуетесь Bootstrap как библиотекой, а не фреймворком, то есть:

@import (reference) "bower_components/bootstrap/less/bootstrap";

.my-alert {
  .alert;
}

less

Использование этого параметра даёт возможность подключать файлы не .less расширения. Параметр не обязательный, так как и без него всё будет работать, но, тем не менее, если вы любите порядок — пригодится. Этот параметр можно применять при подключении файлов с расширением, отличным от .less, к примеру, .variables, .mixins.

Применять нужно так:

@import (keyword) "filename";

С подробным описанием и примерами можно ознакомиться на страницах документации. Вот так вот вежливо я посылаю тебя, мой читатель, читать документацию. Незабываемые ощущения :)

Условные конструкции

В Less есть условные конструкции. Да, они не полноценные, если сравнивать их с другими препроцессорами, но всё таки их наличие не может не радовать. Мне они кажутся даже удобнее, чем @if в Sass, хотя практической разницы никакой нет.

.mixin(@variable) {
  & when (@variable = 1) {
    content: "TRUE"
  }

  & when not (@variable = 1) {
    content: "FALSE"
  }
}

.class-test {
  .mixin(1);
  .mixin(2);
}

В итоге мы получим следующий CSS:

.class-test {
  content: "TRUE";
  content: "FALSE";
}

Что же только что произошло? Магия?

На самом деле — нет. Ключевое слово when тут заменяет привычный во всех языках программирования if и имеет всего два логических элемента: not — отрицание, and — просто and и всё, больше ничего нет. Особо крутого с этим сделать не получится, но я использую условный оператор для того, чтобы не генерировался лишний код. Одной из таких ситуаций, например, является свойство border-radius в генераторе стилей кнопки. Зачем лишний раз будет прописываться какое-либо свойство при генерации, если его значение не играет роли (0)? — Правильно, не нужно.

Интерполяция переменных

Есть в Less интересная конструкция, когда значение одной переменной мы получаем в зависимости от других.

// Variables
@clr-grey-100:                         #fafafa;
@clr-grey-200:                         #f5f5f5;
@clr-red-100:                          #ffebee;
@clr-red-200:                          #ffcdd2;

.mixin(@color, @temperature: 500) {
  color: ~"@{clr-@{color}-@{temperature}}";
  background: e("@{clr-@{color}-@{temperature}}");
}

.class-test {
  .mixin(grey, 100);

  &-test {
    .mixin(red, 200);
  }
}

В этом примере вызывается примесь, в которой параметрами выступают цвет (@color) и его температура (@temperature). Далее мы собираем все переменные в одну, то есть, проще говоря, конструируем новую переменную и получаем её значение. Процесс, когда строка, условно говоря, превращается в вызов переменной и есть интерполяция переменных.

Заметьте, что в этом примере мы используем фигурные скобки после собачки — это очень важно.

На выходе:

.class-test {
  color: #fafafa;
  background: #fafafa;
}

.class-test-test {
  color: #ffcdd2;
  background: #ffcdd2;
}

Кстати, такого я найти в Sass и Stylus не смог. Может подскажет внимательный читатель? :)

Циклы

Многие приверженцы препроцессоров Sass и Stylus давят на то, что в Less нет циклов, но они есть, хотя и не такие как у всех. Вот только пользоваться ими неудобно.

Допустим, что у нас есть список цветов:

@list-red:                             #ffebee, #ffcdd2, #ef9a9a, #e57373, #ef5350,
                                       #f44336, #e53935, #d32f2f, #c62828, #b71c1c;

Для примера, давайте создадим для каждого цвета свой класс:

.color-generator(@index: 1, @color) when (@index <= length(@list-red)) {
  // Получаем цвет по индексу
  @item: extract(@list-red, @index);

  // Конструируем класс на основе полученных данных
  .clr-@{color}-@{index} {
    color: @item;
  }

  // Рекурсия
  .color-generator(@index + 1, @color);
}

.color-generator(@color: red);

Получим симпатичный, но не особо нужный список классов:

.clr-red-1 { color: #ffebee; }
.clr-red-2 { color: #ffcdd2; }
.clr-red-3 { color: #ef9a9a; }
.clr-red-4 { color: #e57373; }
.clr-red-5 { color: #ef5350; }
.clr-red-6 { color: #f44336; }
.clr-red-7 { color: #e53935; }
.clr-red-8 { color: #d32f2f; }
.clr-red-9 { color: #c62828; }
.clr-red-10 { color: #b71c1c; }

Пожалуй, это удобно на первых парах. Однако, это удобство исчезает, когда захочется сделать что-то внушительное, например, сетку Bootstrap. И это у них ещё все префиксы прописаны вручную и в удобном для них виде. Представляете махину, в которой префиксы колонок задаются через переменные? А если ещё и аббревиатуры колонок будут в переменных?

С циклами у Less не всё так хорошо, как у Sass или Stylus. Но честно ответьте себе на вопрос: часто ли вам нужны циклы?

More or Less

Многие разработчики, что уже знакомы с Less, используют библиотеку примесей Less Hat, которая следит за префиксами вашего CSS кода. Но я специально не упоминаю эту замечательную библиотеку, так как её необходимость уходит на второй план, если вы работаете с Autoprefixer.

Библиотека «More or Less» добавляет недостающий функционал Less. К сожалению, документация предполагает следующий принцип работы: Хочешь пользоваться моей библиотекой? — Посмотри на простейший пример, а все более-менее сложные варианты использования узнай разобравшись в коде. Это не порядок. Поэтому рассмотрим все самые интересные функции на житейских и не очень примерах.

Полноценные условные конструкции

Немногим раньше мы говорили, что условные конструкции в Less, мягко говоря, не очень. Не хватает else. Именно такой функционал предлагает нам «More or Less». Для работы с библиотекой необходимо всего лишь её подключить и запомнить несколько простых конструкций.

Здесь и в других примерах статьи, я буду подключать все функции по отдельности, используя Bower. Поэтому, оставлю команду для установки пакета:

bower i --save more-or-less

Итак, у нас есть вот такой код:

@import "bower_components/more-or-less/less/fn/_if.less";

.class-test-if {
  .if(isnumber(2), {
      .-then() {
        background-color: #fff;
      }
      .-else() {
        background-color: #000;
      }
  });
}

На выходе:

.class-test-if {
  background-color: #fff;
}

Поменяем 1 на string:

.class-test-if {
  background-color: #000;
}

Замечательно. Но как сравнить, например, переменную с каким-то конкретным значением, а не типом? — Почти так же легко.

.class-test-if {
  @variable: col;

  .if(@variable eq col, {
    .-then() {
      content: "true";
    }
    .-else() {
      content: "false";
    }
  });
}

Всё работает как часики:

.class-test-if {
  content: "true";
}

Кстати, вы заметили ключевое слово, которое придётся запомнить? — Не беда! Ничего сложного тут нет:

  • ls (less, <) — меньше;
  • lte (less-than or equal to, <=) — меньше или равно;
  • gt (greater, >) — больше;
  • gte (greater-than or equal to, >=) — больше или равно;
  • eq (equality, =) — равно;

Полноценный цикл for

Наиболее удобным в этой библиотеке получился стандартный во всех ЯП цикл for. Давайте подключим функцию библиотеки и рассмотрим подробнее пример.

@import "bower_components/more-or-less/less/fn/_for.less";

.class-test-for {
  // Просто переменная, можно и без неё
  @count: 4;

  // Цикл
  .for(@count);
  .-each(@index) {
    &-@{index} {
        width: @index * (100% / @count);
    }
  }
}

Заметили, что синтаксис куда приятнее, чем у оригинального цикла, который мы рассматривали в начале статьи?

В этом примере генерируется простейшая сетка, без всяких фишек. Код после трансляции в CSS:

 .class-test-for-1 { width: 25%; }
 .class-test-for-2 { width: 50%; }
 .class-test-for-3 { width: 75%; }
 .class-test-for-4 { width: 100%; }

Хочу обратить ваше внимание, что автор библиотеки предлагает вот такой стиль записи циклов:

.for(@n);.-each(@i) {
  ...
}

Судя по всему, этот вариант записи пытается имитировать стандартную форму записи for в других ЯП. Должен признать, что выглядит это ужасно, поэтому советую использовать вариант из этой статьи.

Конкатенация элементов массива

Для объединения всех элементов массива (списка) в одну строку используется функция join, принимающая аргументами массив, который необходимо преобразовать в строку, и разделитель.

К слову, этот разделитель называется glue — строковый аргумент, с помощью которого будут соединены в строку все элементы массива.

.class-test-join {
  // Список (массив)
  @animals: 'cat', 'tiger', 'lion';

  // Конкатенация элементов списка (массива)
  .join(@animals, " ");

  content: '@{string}';
}

На выходе получим вот такую вот строку:

.class-test-join .animals:after {
  content: 'cat tiger lion';
}

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

Цикл repeat

Ещё одно сомнительное изобретение, которое не может найти в моей голове практического применения.

.class-test-repeat {
  .repeat('.test', ' + ', 5);
  @{string} {
    float: right;
  }
}

Какой-то глуповатый пример из документации:

.class-test-repeat .test + .test + .test + .test + .test {
  float: right;
}

Выводы

К сожалению, используя «More or Less» сетку Bootstrap построить опять таки сложно. В любом случае не обойтись без when (@index <= ...), чтобы создать рекурсию и собрать классы в список для дальнейшей работы. Очень уж не хватает возможности создавать массив.

После «More or Less» стало куда приятнее работать с циклом for и условием if. Остальные функции созданы для того, чтобы просто быть и показать возможности — применение им найти сложно, но можно.

Если есть острая необходимость в полноценной поддержке циклов, условий и, например, удобств в виде подключения папок или нормальных массивов, то следует присматриваться к Sass или Stylus.

Ссылки

А как же без дополнительного материала? Думали, что я вот так просто вас отпущу?

  • Курс по основам Less от HTML Academy.
  • Старые — новые фишки Less на Habrahabr.
  • Статья о том, что можно вытворять с примесями на SitePoint.

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