Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Циклы SASS: Программирование

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

Перед тем, как перейти непосредственно к написанию и пониманию циклов, введём основной пример, на котором мы и будем изучать все возможные типы циклов в препроцессоре SASS. Представим, что у нас есть список, внутри которого находится 5 элементов: cat, dog, whale, bird, fish. Для каждого из элементов списка нужно составить класс следующего вида:

.icon-cat {
  height: 32px;
  width: 32px;

  background: url("../icon/svg/cat.svg");
}

Какими путями мы сейчас можем пойти? Первым, что приходит в голову, является использование миксинов. Это действительно хороший вариант, который полностью удовлетворяет нашим запросам:

$icons: "cat", "dog", "whale", "bird", "fish";

@mixin icon-32($icon-name) {
  .icon-#{$icon-name} {
    height: 32px;
    width: 32px;

    background: url("../icon/svg/#{$icon-name}.svg");
  }
}

Теперь, для генерации иконок нам надо выполнить данный миксин для каждого элемента списка. То есть пять раз:

@include icon-32(nth($icons, 1));
@include icon-32(nth($icons, 2));
@include icon-32(nth($icons, 3));
@include icon-32(nth($icons, 4));
@include icon-32(nth($icons, 5));

Иконки готовы — вы великолепны! Можно откинуться на спинку стула и отдохнуть. Это приемлемый вариант, если иконок у нас пять. А что, если в проекте у нас 20 иконок? Или даже 100? Может вы создаете свою библиотеку с иконками. В таком случае использование метода, который представлен выше — не лучший путь. Он даст нужный результат, но количество строк, которые нужно будет скопировать, достаточно велико. Каким же образом можно облегчить себе жизнь? Для этого в SASS существуют циклы. И с одним из них мы познакомимся прямо сейчас!

Цикл for

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

Для записи такого цикла используется конструкция @for, у которой необходимо указать счётчик. Счётчик — простой механизм, в котором задаётся начальное и конечное значение. Каждый раз, исполнив блок кода, цикл вначале увеличивает счётчик на единицу, а потом проверяет, не вышло ли значение за границу. Если нет, то выполняем блок кода ещё раз. Каждый такой проход по блоку кода называется итерацией

Счётчик в цикле @for может быть записан одним из двух видов. Примеры ниже сделаны для счётчика от 1 до 5:

  • from 1 to 5. Счётчик с ключевым словом to считается до конечного числа, не включая конечное число. В данном варианте блок кода исполнится 4 раза.
  • from 1 through 5. Счётчик с ключевым словом through считается до конечного числа, включая конечное число. Цикл с таким счётчиком исполнит блок кода 5 раз.

Все значения счётчика записываются в переменную, имя которой вы указываете сразу после ключевого слова @for. Полная запись цикла выглядит следующим образом:

@for $i from 1 to 5 {
  // Блок кода, который будет исполнен 4 раза.
}

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

@for $i from 1 to 5 {
  $side: $i * 10;

  .square-#{$side} {
    display: block;

    width: #{$side}px;
    height: #{$side}px
  }
}

Счётчик $i был использован для того, чтобы высчитать необходимую сторону квадрата. В результате компиляции получится следующий CSS код:

.square-10 {
  display: block;
  width: 10px;
  height: 10px;
}

.square-20 {
  display: block;
  width: 20px;
  height: 20px;
}

.square-30 {
  display: block;
  width: 30px;
  height: 30px;
}

.square-40 {
  display: block;
  width: 40px;
  height: 40px;
}

Обратите внимание, что мы использовали to в условии счётчика, поэтому сгенерировалось 4 класса.

Теперь решение первоначальной задачи становится немного проще. Можно воспользоваться циклом @for и сгенерировать классы столько раз, сколько нам это необходимо.

$icons: "cat", "dog", "whale", "bird", "fish";

@mixin icon-32($icon-name) {
  .icon-#{$icon-name} {
    height: 32px;
    width: 32px;

    background: url("../icon/svg/#{$icon-name}.svg");
  }
}

@for $i from 1 through 5 {
  @include icon-32(nth($icons, $i));
}

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

Но есть способ отдать подсчёт самому SASS — можно воспользоваться функцией length() и получить количество элементов в списке. В таком случае наш код может выглядеть следующим образом:

$icons: "cat", "dog", "whale", "bird", "fish";
$icons-length: length($icons);

@mixin icon-32($icon-name) {
  .icon-#{$icon-name} {
    height: 32px;
    width: 32px;

    background: url("../icon/svg/#{$icon-name}.svg");
  }
}

@for $i from 1 through $icons-length {
  @include icon-32(nth($icons, $i));
}

Цикл each

Вторым способом решения задачи по генерации классов с иконками является цикл @each. Этот цикл перебирает значения списка или ассоциативного массива и для каждого значения выполняет блок кода. При использовании данного цикла нам не нужно знать размер списка и создавать счётчики. Мы работаем непосредственно с каждым значением.

Цикл @each идеально подходит именно для задачи генерации классов на основе какого-либо списка или ассоциативного массива. Для того, чтобы им воспользоваться, нужно использовать ключевое слово @each, после которого указывается переменная, в которой будет храниться текущее значение из списка. После этого указывается список, из которого эти значения берутся.

$list: 1, 2, 3;
// Или $list: (1 2 3)

@each $number in $list {
  // блок кода
}

Данный цикл можно прочитать следующим образом: Для каждого числа в списке выполнить и далее блок кода. Прекрасный способ обхода списка, при котором нам не нужно заводить отдельные переменные или обращаться к списку с помощью nth(), как было сделано в варианте с циклом @for.

Перенесём нашу задачу по генерации иконок, заменив цикл @for на цикл @each.

$icons: ("cat" "dog" "whale" "bird" "fish");

@mixin icon-32($icon-name) {
  .icon-#{$icon-name} {
    height: 32px;
    width: 32px;

    background: url("../icon/svg/#{$icon-name}.svg");
  }
}

@each $icon in $icons {
  @include icon-32($icon);
}

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


Совет: всегда обращайте внимание на то, какую операцию вы делаете. Если вам нужно просто выполнить блок кода определённое количество раз, то используйте цикл @for. При работе со списками/массивами и при использовании их значений используйте цикл @each. В большинстве случаев такое разделение поможет сделать ваш код чище


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

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

$icons: (
  "cat": (32, 64, 128),
  "dog": (32, 64, 128),
  "whale": (32, 64),
  "bird": (16, 32, 64, 128),
  "fish": (32, 64)
);

@mixin icons($icons) {
  @each $icon-name, $icon-size in $icons {
    @each $size in $icon-size {
      .icon-#{$size}-#{$icon-name} {
        height: #{$size}px;
        width: #{$size}px;

        background: url("../icon/svg/#{$size}/#{$icon-name}.svg");
      }
    }
  }
}

@include icons($icons);

После компиляции мы получим следующий CSS код для иконок с изображением кошки:

.icon-32-cat {
  height: 32px;
  width: 32px;
  background: url("../icon/svg/32/cat.svg");
}

.icon-64-cat {
  height: 64px;
  width: 64px;
  background: url("../icon/svg/64/cat.svg");
}

.icon-128-cat {
  height: 128px;
  width: 128px;
  background: url("../icon/svg/128/cat.svg");
}

Остальные классы будут сгенерированы по аналогии.

В этом примере вы можете увидеть, что мы можем вкладывать циклы друг в друга. Причем эти циклы необязательно должны быть одного типа. Мы можем вложить @each внутри @for и наоборот. В каждом из этих случаев всё будет работать корректно. Главное, что переменные из внешнего цикла так же попадают и во внутренний. Именно поэтому внутри второго цикла @each можно было использовать переменную $icon, которая создается в цикле до этого.

Циклы часто совмещаются со списками и массивами. Например, на каждом шаге итерации обрабатывать список и изменять его. Представим, что нам нужно добавить единицу измерения к каждому элементу списка (10 15 20 25). Единица измерения задаётся в качестве аргумента функции add-unit.

$margins: (10 15 20 25);

@function add-unit($list, $unit) {
  // Какой-то код
}

.aside {
  // Ожидаем margin: 10px 15px 20px 25px;
  margin: add-unit($margins, "px");
}

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

$margins: (10 15 20 25);

@function add-unit($list, $unit) {
  // Создаём пустой список с результатом
  $result: ();

  @each $item in $list {
    // На каждом шаге итерации добавляем
    // в список $result значение из списка
    // и единицу измерения

    $result: append($result, #{$item}#{$unit});
  }

  // Возвращаем результат работы функции
  @return $result;
}

.aside {
  margin: add-unit($margins, "px");
}

Цикл while

Последним видом циклов, который существует в SASS, является цикл @while. Из его названия можно сделать вывод, что он работает по принципу пока выполняется условие.

Для цикла @while необходимо указать условие. Пока условие выполняется — цикл отрабатывается. В качестве условия выступают такие же конструкции, как и с условными конструкциями @if/@else.

$count: 1;

@while $count < 5 {
  .font-size-#{$count} {
    font-size: #{$count}em;
  }

  $count: $count + 1;
}

После компиляции получится следующий CSS код:

.font-size-1 {
  font-size: 1em;
}

.font-size-2 {
  font-size: 2em;
}

.font-size-3 {
  font-size: 3em;
}

.font-size-4 {
  font-size: 4em;
}

Цикл @while сложен тем, что в нём очень просто забыть поработать с условием и уйти в бесконечный цикл. Если вы забудете указать $count: $count + 1;, то цикл не закончится никогда. Всегда следите за тем, чтобы условие менялось после каждого выполнения блока кода.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»