Веб-разработка
July 4, 2022

Препроцессор Less — Тренажёр HTML Academy

Разобравшись с вёрсткой переходим к инструментам её ускорения. Мы уже знаем базовые вещи PHP, который формально препроцессор HTML, теперь же займемся CSS и одним из его препроцессоров Less.

Я, кстати, уже писал немножечко про один другой препроцессор CSS — в статье про мобильное приложение для самообучение Webref, там нас познакомили с Sass и его упрощённым вариантом SCSS. Спойлер — отличия от Less всё-таки есть, особенно в синтаксисе, хотя по сути мало что меняется.

О препроцессорах

Препроцессор позволяет подойти к языку разметки или стилей как к языку программирования — с переменными, условиями, циклами и так далее. Это касается и PHP для HTML, который мы разбирали уже достаточно давно, и это же касается Less или других вариантов для CSS.

Почему эти языки называются препроцессорными, я уже как-то давно писал в самом начале про PHP, но если вкратце — это языки которые обрабатывает сервер и трансформирует его в понятный для браузера HTML и CSS.

В случае с Less мы по сути «программируем» стили, уменьшаем количество повторов и, тем самым, упрощаем себе работу. Меньше повторов, меньше времени на написание, больше логики и наглядности. Хотя по началу может показаться с точностью да наоборот.

Возможности Less

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

Переменные

Первое, что можно сделать в Less — это объявить переменную:

@main-color: red;
@cintainer-width: 150px;

Один раз объявив переменную, мы можем использовать её в любом месте кода, при этом, поменяв значение переменной — оно поменяется везде. Таким образом, сильно упрощается работа с большим объемом одинаковых параметров.

.card-container {
  background-color: @main-color;
  width: @container-width; 
}

.description {
  color: @main-color;
}

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

С переменными Less работают стандартные правила области видимости — переменные объявленные вне правил являются глобальными и могут применятся везде, при этом они могут переопределяться на локальные значение:

.card-container {
  @container-width: 100px;
  
  background-color: @main-color; /* будет red -глобальное значение */
  width: @container-width; /* будет 100px - локальное значение */
}

.description {
  @main-color: blue;
  
  color: @main-color; /* будет blue - локальное значение */
}

Математические операции

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

Например, самый простой вариант — в Less работают математические операции:

@cintainer-width: 150px;
@big-container-width: @container-width + 100; /* 250px */

@par-font-size: 16px;
@header-font-soze: @par-font-size * 1.5; /* 16 * 1.5 = 24 */

@cell-wudth: 50%;
@mini-cell-width: @cell-width / 2; /* 50% / 2 = 25% */

Два основных нюанса при написании математических операций в Less:

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

С числовыми значениями вполне всё очевидно, а вот с цветом не совсем — с ним тоже возможны математические операции, но операторы в любом случае будут работать с показателями RGB и влиять на все три цветовых показателя.

При этом неважно как мы зададим цвет — ключевым словом, HEX или как-то еще — операция будет с показателями RGB:

@rgb-color: rgb(110, 27, 255);
@keyword-color: red; /* эвкивалент rgb(255, 0, 0) */
@hex-color: #aa3399; /* эквивалент rgb(170, 51, 153) */

@new-rgb-color: @rgb-color + 20; /* = rgb(130, 47, 255) – 255 максимум */
@new-keyword-color: @keyword-color + 20; /* = rgb(255, 20, 20) */
@new-hex-color: @hex-color + 20; /* = rgb(190, 71, 173) */

Цветовые функции

Помимо математических операций в Less существуют и функции. Я уверен их достаточно много, но в тренажёре нас сначала знакомят в основном с цветовыми функциями, влияющими на тон, яркость и насыщенность.

Первая на очереди функция поворота цветового тона spin(), которая очень похожа на CSS-фильтр hue-rotate() и делает по сути тоже самое, но меняет именно значение конкретного цвета в свойстве, а не модифицирует уже отрисованный элемент.

Функция принимает два аргумента — цвет или переменную с цветом и числовое значение угла поворота, причём без указания единицы измерений deg:

@not-red: spin(red, 60); /* повернет на 60deg по часовой на диаграме */
@true-red: spin(@not-red, -60); /* повернёт обратно, против часовой */

.green-box {
  background-color: spin(@true-red, 120); /* 120deg от red - зелёный */
}

Следующие две функции отвечают за уменьшение и увеличение яркости цвета — darken() и lighten() соответственно. Функции тоже принимают два аргумента, первый — это цвет или переменная с цветом, а вторая значение увеличения яркости в процентах %:

@light-blue: lighten(blue, 50%);
@dark-blue: darken(blue, 50%);

Во втором аргументе указывается именно процент воздействия на цвет, а не положение на шкале яркости. То есть при 100% значении darken() получится чёрный цвет, а при 100% значении lighten() получится белый, при этом при значении 0% — цвет не изменится.

И последние две функции из тренажёров отвечающие за уменьшение и увеличение насыщенности цвета — desaturate() и saturate() соответственно. Синтаксис у них такой же как и у яркости:

@more-blue: saturate(blue, 30%);
@greyly-blue: desaturate(blue, 50%);
@grey: desaturate(blue, 100%); /* 100% desaturate = оттенки серого */

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

@dark-greyly-yellow: darken(desaturate(yellow, 50%), 30%);

Где всё это применимо? В тренажёре нам приводят в пример всплывающие подсказки и их стилизацию — мы задаём лишь один базовый цвет — фиолетовый, а всё остальное будь то цвет текста, фона и рамок даже разных сообщений мы выводим из базового используя вышеописанные функции.

Таким образом меняя всего один параметр — базовый цвет, мы можем поменять палитру всех элементов. Используя эти инструменты по сути можно создавать целые «темы» интерфейса и менять их одним переключателем!

Вложенные правила

Следующие возможности препроцессора связаны с CSS-правилами. Самое простое для понимания — в Less CSS-правила можно вкладывать друг в друга:

.card {
  color: @text-color;
  background-color: @bg-color;
  
  a {
    color: @link-color;
    text-decoration: none;
  }
}

Что в CSS преобразуется как:

.card {
  color: red; /* цвета условные */
  background-color: black;
}
.card a {
  color: blue;
  text-decoration: none;
}

Такая древовидная вложенность в Less избавляет код от дублей и делает его более структурным, читаемым и наглядным.

При работе с такой вложенной структурой в Less есть еще один механизм позволяющий обращаться непосредственно к родительскому селектору или к его подстроке:

.button {
  &-submit {
    color: @btn-text-color;
    background-color: @btn-bg-color;
    
    &:hover {
      background-color: lighten(@btn-bg-color, 30%);
    }
  }
  &-cancel {
    background-color: desaturate(@btn-bg-color, 50%);
  }
}

Будет преобразовано в CSS как:

.button-submit {
  color: white; /* цвета снова условные */
  background-color: red;
}

.button-submit:hover {
  background-color: lightred;
}

.button-cancel {
  background-color: #fa8072;
}

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

Также как и в примере, это используется при работе с псевдоклассами — иначе без подстановки & перед :hover конечный селектор в CSS выглядел бы как .button-submit :hover — именно с пробелом.

Примеси Less

Следующая важная особенность Less — примеси, возможность «примешивать» содержимое одного правила в другой. Если в Less записать:

.big { width: 500px; }

.white { color: white; }

.block {
  .big;
  .white;
}

то в CSS преобразуется как:

.big { width: 500px; }
.white { color: white; }
.block {
  width: 500px;
  color: white;
}

Это самый примитивный вариант, однако, примеси это целый новый уровень абстракции, позволяющий создавать по сути чуть ли не функции.

Примесь в её более расширенном понимании — это комплексное Less-правило, которое в итоге не выводится в конечный CSS, а применяется для структурного построения и которому можно даже задать аргументы.

На мой взгляд, примесь действительно похожа на функцию в обёртке CSS синтаксиса — выглядит она как обычный селектор со скобками:

.white() { color: white; } /* объявление примеси */
.text { .white(); }      /* применение примеси */

При этом в CSS примесь никак не выводится:

.text { color: white; }

Скобочки у примеси не просто так — на самом деле, в них можно передавать параметры, которые далее используются в примеси для её построения. Параметрам можно задать значение по умолчанию, в случае если при вызове примеси их не указали:

.colors (@color) { /* параметр без значения по умолчанию */
  color: darken(@color, 30%);
  background-color: lighten(desaturate(@color, 20%) 20%);
}

.offset(@padding: 10px; @margin: 20px;) { /* со значениями по умолчанию */
  padding: @padding;
  margin: @margin;
}

.block {

  &-1 {
    .colors(blue);
    .offset(5px; 10px); /* значения сопоставляются по порядку */
  }
  
  &-2 {
    .colors(green);
    .offset();
  }
}

Результат в CSS:

.block-1 {
  color: darkblue;
  background-color: lightskyblue;
  padding: 5px; /* значения при вызове примеси */
  margin: 10px;
}

.block-2 {
  color: darkgreen;
  background-color: lightseagreen;
  padding: 10px; /* значения по умолчанию */
  margin: 20px;
}

Таким образом мы можем один раз задать какой-то набор свойств в виде примеси и в дальнейшем вызывать его в любом месте передавая только параметры. Особенно хорошо можно прочувствовать полезность примесей если поработать со свойствами типа border-top-left-radius и написать их хотя бы несколько раз в чистом CSS.

Шаблон примеси

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

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

.set-color(@color) { /* примесь */
  background-color: @color;
}
.set-font-size(lighten; @color) { /* шаблон примеси */
  background-color: lighten(@color, 50%);
}

Самый тривиальный пример — шаблонизировать примеси для текста. Мы можем задать какой-то один базовый размер и от него плясать в зависимости от применяемой области.

@base-font-size: 16px;

.set-font-size(@size) {
  font-size: @size;
  line-height: @size * 1.5;
}

.set-font-size(small, @size) {
  font-size: @size / 2;
  line-height: @size / 2;
}

.set-font-size(big, @size) {
  font-size: @size * 2;
  line-height: @size * 2 * 1.2;
}

.promo-header {
  .set-font-size(big, @base-font-size);
}
.promo-description {
  .set-font-size(@base-font-size);
}
.promo-note {
  .set-font-size(small, @base-font-size)
}

Результат в CSS:

.promo-header {
  font-size: 32px; /* 16 * 2 = 32 */
  line-height: 38px; /* 16 * 2 * 1.2 = 32 * 1.2 = 38.4 */
}
.promo-description {
  font-size: 16px; 
  line-height: 24px; /* 16 * 1.5 = 24 */
}
.promo-note {
  font-size: 8px; /* 16 / 2 = 8 */
  line-height: 8px;
}

Таким образом, у нас появляется, своего рода, пространство имён для примесей — лучше для однотипных задач использовать примеси с шаблонами, чем несколько разных примесей с разными названиями.

У шаблонов есть дополнительная фишка — универсальный шаблон, который применяет указанные в нём параметры для всех одноименных примесей. Это полезно в случае если в каком-то наборе шаблонов примесей применяется один и тот же неизменяемый или одинаково изменяемый для всех шаблонов параметр.

Он обозначается зарезервированным именем _@ и на примере шаблонов для текста выше, мы можем, скажем, задать везде одинаковый font-weight:

.set-font-size(_@, @size) {
  font-weight: bold;
}

В итоге всем элементам, где были применены указанные примеси и их шаблоны будет подставлен этот параметр.

Примесь с условием

Вот мы и дошли до «программируемой» части Less — условные конструкции с примесями! Логика, правда, немного отличается от привычной конструкции в языках программирования — мы не задаём условия «if/else», в которых пишем что будет происходить в их случае.

Я немного порылся в англоязычной документации Less и там эта конструкция называется «Mixin Guards», то бишь «Охрана примеси». Суть, в общем-то, практически не меняется — мы ставим условие выполнения самой примеси с помощью ключевого слова when:

.set-font-size(@size) when (@size < 12px) {
  font-weigth: regular;
}

Примесь выше сработает только если передаваемое значение, в нашем случае это размер шрифта, окажется меньше 12px.

В условие в скобках можно помещать как параметры самой примеси, так и внешние факторы, например, соответствие каких-то внешних переменных, причем их соответствие проверяется в момент вызова примеси:

.set-font-color(@color) {
  color: @color;
}

.set-font-color(@color) when (@bg-color = black) {
  color: lighten(@color, 50%);
}

.first-text {
  .set-font-color(red); /* сработает безусловная примесь */
}

@bg-color: black; /* теперь условие корректно */

.second-text {
  .set-font-color(red); /* сработает условная примесь */
}

В итоге в CSS получится:

.first-text {
  color: red; /* безусловная примесь */
}

.second-text {
  color: lightred; /* примесь с условием */
}

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

.mixin(@param) when (isnumber(@param)) { … }

Функций проверки не так уж и много:

  • isnumber() — из примера выше, проверяет значение — любое числовое значение — просто число, пиксели, проценты и так далее, главное исчисляемое значение.
  • iscolor() — проверка значение — цвет, любой вариант записи — ключевое слово, HEX, RGB и так далее;
  • isstring() — проверка значение — строка, то есть значения свойств обёрнутые в кавычки "..." — различные названия шрифтов, например;
  • iskeyword() — проверка значение — ключевое слово, имеются ввиду ключевые слова в значениях свойств, кроме цветовых — right, center, middle и так далее;
  • isurl() — проверка значение — функция url(), то есть проверка на значение-ссылку.

Есть еще несколько дополнительных, проверяющих конкретные единицы измерения — ispixel(), ispercentage() и isem(). Там еще есть парочку, но про них нет смысла пока писать.

Вставки

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

В переменную мы можем записать по сути что угодно — число, строку, ключевое слово даже часть названия селектора. То есть, переменную не обязательно использовать только как носитель чего-то значимого с точки зрения CSS, это может быть и часть структуры Less.

Дальше чтобы значение этой переменной подставить в нужном месте используется фигурные скобки вокруг имени — @{var}:

@state: success;
@property: color;
@icon: "question";

.btn-@{state} { /* как часть селектора */
  background-color: green;
}
.btn-error {
  background-@{property}: red; /* как часть свойства */
}
.btn-help {
  background-image: url("/img/icons/@{icon}.png"); /* как часть url */
}

Результат в CSS:

.btn-success {
  background-color: green;
}
.btn-error {
  background-color: red;
}
.btn-help {
  background-image: url("/img/icons/question.png");
}

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

Задаётся такая строка с помощью кавычек и знака тильды ~"...", это позволяет записывать в строку, а как следствие в переменную или в значение целые выражения и даже свойства с подставляемым значением:

@base-size: 10;
@base-color: red;
@default-border: ~"@{base-size}px solid"; /* интерполяция */
/* почему-то подкрашено в другой цвет, но это тоже переменная */

.border-colored(@color) {
  border: @default-border @color;
  }

.element {
  .border-colored(@base-color);
  }

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

.element {
  border: 10px solid red;
}

Без интерполяции значения в экранированную строку переменная с числом не «склеивается» корректно с единицами измерений. То есть 10 и px не объединяются в одну сущность. Хотя, судя по англоязычной документации — это постепенно исправляется и много в каких случаях экранирование уже не нужно.

Циклы

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

Я не зря писал, что примеси похожи на функции и их как и функцию можно рекурсивно зациклить используя аргумент как счётчик:

.mixin(@n) {
  .mixin(@n + 1); /* приращение */
}
.mixin(1);

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

.mixin(@n) when (@n =< 3) { /* при @n > 3 примесь не выполняется */
   .mixin(@n + 1);
}
.mixin(1);

Вполне себе цикл, который можно использовать, например, для создания селекторов с одинаковым префиксом, но разными суффиксами, используя интерполяцию значения счётчика:

.mixin(@n) when (@n =< 3)
   .block-@{n} {
     ...
   }
   
   .mixin(@n + 1);
}
.mixin(1);

Что в CSS преобразуется как:

.block-3 {...}
.block-2 {...}
.block-1 {...}

Испытания

Решил вывести их отдельно для обеих частей блока. Все они по сути об одном, плюс я немного переформатировал повествование.

Первое испытание на цветовые функции — у нас уже есть некоторые области с цветами, а отталкиваясь от них нужно заполнить остальные:

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

Третье испытание модифицируется шаблонами примесей:

Четвёртое испытание полностью на понимание работы условных примесей — нужно расставить существующим примесям условия, чтобы они срабатывали только для определённых элементов:

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

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


Не самый простой был блок, но очень интересный! У меня, правда, по ходу прохождения возникло много вопросов — например, пресеты цветов можно явно использовать в динамическом изменении цветовой схемы. Как оно будет работать — нужно всё-таки инициировать обновление страницы, чтобы сработал препроцессор и пересобрал все? Или можно как-то с помощью JS это все настроить в динамике в моменте?

Еще я понял, что тут ну прям о-очень небольшая часть от Less, гуляя по документации обнаружил много интересных штук. Так что к Less я еще вернусь, но у же в рамках чего-то другого.

А вам большое спасибо за внимание! Желаю вам пережить эту жару с максимальным комфортом!

Ссылки на мои социальные сети, там анонсы постов, некоторые мои короткие записи, мыслишки и всякое на отвлечённые темы:

Telegram — Twitter — Instagram — VK

Заходите куда удобно вам и подписывайтесь! Еще раз спасибо за внимание!