Знакомимся с 'тихими' placeholder

Reading time ~7 minutes

Статья посвящена вопросу “тихих” placeholder’ов в препроцессоре Sass. Что это такое и в чем преимущество их использования.

Оригинал статьи размещен здесь - Understanding placeholder selectors.

Препроцессор Sass предоставляет несколько способов создания одного фрагмента кода, который будет многократно использоваться внутри CSS-кода.

Например, можно воспользоваться миксинами (mixins) для вставки группы CSS-свойств (или CSS-правил) в CSS-коде.

Или же использовать директиву @extend для расширения набора CSS-свойств одного HTML-элемента за счет CSS-свойств другого HTML-элемента.

В Sass версии 3.2 введена новая концепция под названием placeholder, которая делает использование директивы @extend еще более эффективным способом.

Но прежде чем мы перейдем к рассмотрению этого нововведенния, давайте остановимся на моменте, каким образом работает расширение (@extend) CSS-свойств в Sass.

Как работает @extend

Директива @extend в препроцессоре Sass позволяет CSS-селекторам с легкостью обмениваться между собой своими CSS-свойствами. Лучше всего вышесказанное можно проиллюстрировать на живом примере:

.icon {
  transition: background-color ease .2s;
  margin: 0 .5em;
}

.error-icon {
  @extend .icon;
  /* здесь - специфичные стили класса .error-icon */
}

.info-icon {
  @extend .icon;
  /* здесь - специфичные стили класса .info-icon */
}

Результатом компиляции этого SCSS-кода в CSS-код будет следующий фрагмент:

.icon, .error-icon, .info-icon {
  transition: background-color ease .2s;
  margin: 0 .5em;
}

.error-icon {
  /* здесь - специфичные стили класса .error-icon */
}

.info-icon {
  /* здесь - специфичные стили класса .info-icon */
}

Рассмотрим “механизм” показанного выше примера более детально. В нем директива @extend играет ключевую роль. С помощью нее селекторы .error-icon и .info-icon наследуют свойства селектора .icon. При изменении CSS-свойств селектора .icon автоматически будут меняться свойства селекторов .error-icon и .info-icon, так как они наследуют определенный набор CSS-свойств у селектора .icon. Довольно изящный подход, не правда ли?

А вот теперь наступает интересный момент. Что, если элемент с классом .icon не планируется использовать и он даже не будет присутствовать в HTML-разметке? Но CSS-свойства этого элемента нам нужны для стилизации элементов .error-icon и .info-icon.

Получается, что результирующий CSS-код будет неоправданно раздут из-за того, что в нем присутствует “лишний” элемент, который напрямую никогда не будет использован.

И тут наступает момент для выхода на сцену героя этой статьи - селектора placeholder (его еще называют “тихим” placeholder‘ом):

Тихий placeholder

Знакомимся с селектором placeholder

Селекторы placeholder были введены в Sass как раз для того, чтобы решать вышеназванную проблему. Синтаксис placeholder очень похож на синтаксис обычных CSS-классов, только вместо точки (.) перед именем ставиться символ процента %.

Селекторы placeholder имеют одну специфичную для них особенность - они никак не проявляют себя в скомпилированном CSS-коде. Можно сказать по другому - вы никогда не найдете селекторов placeholder в результирующем CSS-коде (поэтому они и носят такое название - “тихие” placeholder). В скомпилированном CSS-коде будут только селекторы, которые используют “тихие” placeholder‘ы, но никак не сами “тихие” placeholder‘ы.

Вернемся назад, к нашему начальному примеру. Заменим в нем имя класса .icon на имя “тихого” placeholder’а - %icon:

%icon {
  transition: background-color ease .2s;
  margin: 0 .5em;
}

.error-icon {
  @extend %icon;
  /* здесь - специфичные стили класса .error-icon */
}

.info-icon {
  @extend %icon;
  /* здесь - специфичные стили класса .info-icon */
}

В результате скомпилированный CSS-код будет выглядеть таким образом:

.error-icon, .info-icon {
  transition: background-color ease .2s;
  margin: 0 .5em;
}

.error-icon {
  /* здесь - специфичные стили класса .error-icon */
}

.info-icon {
  /* здесь - специфичные стили класса .info-icon */
}

Обратите внимание на важный момент - класс .icon теперь не присутствует в результирующем CSS-коде! Его там нет!

@extend или @include

На первый взгляд может показаться, что “тихие” placeholder - это почти тоже самое, что и миксины (mixin). С функциональной точки зрения такое утверждение абсолютно верно - результат в браузере получается идентичным. А вот с точки зрения CSS разница очень существенная!

Давайте снова изменим наш первоначальный пример и теперь воспользуемся миксином @mixin icon:

@mixin icon {
  transition: background-color ease .2s;
  margin: 0 .5em;
}

.error-icon {
  @include icon;
  /* здесь - специфичные стили класса .error-icon */
}

.info-icon {
  @include icon;
  /* здесь - специфичные стили класса .info-icon */
}

Посмотрим на сгенерированный CSS-код:

.error-icon {
  transition: background-color ease .2s;
  margin: 0 .5em;
  /* здесь - специфичные стили класса .error-icon */
}

.info-icon {
  transition: background-color ease .2s;
  margin: 0 .5em;
  /* здесь - специфичные стили класса .info-icon */
}

С точки зрения разработки данный пример ничем не хуже примера с использованием “тихого” placeholder‘а.

Но обратите внимание на тот факт, что CSS-правила transition: background-color ease .2s; и margin: 0 .5em; дублируются между селекторами .error-icon и .info-icon, что приводит к неоправданному раздутию кода. В случае использования “тихого” placeholder этого не происходит.

Ограничения

Использование директивы @extend имеет одно ограничение, связанное с тем, что применение “тихих” placeholder‘ов никак не оправдывает себя в медиа-запросах @media.

Рассмотрим такой пример:

%icon {
  transition: background-color ease .2s;
  margin: 0 .5em;
}

@media screen {

  .error-icon {
    @extend %icon;
  }

  .info-icon {
    @extend %icon;
  }

}

Видим, что в данном случае “тихий” placeholder добавлен для селекторов, находящихся внутри медиа-запроса @media.

Однако, при попытке компиляции этого SCSS-кода в CSS-код получиться ошибка:

You may not @extend an outer selector from within @media. You may only @extend selectors within the same directive. From “@extend %icon” on line 8 of icons.scss

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

Механизм работы директивы @extend основан на добавлении одного селектора к другому селектору без необходимости дублировать CSS-свойства этих селекторов. Однако невозможно объединять селекторы, находящиеся в разных медиа-запросах @media.

Но можно поступить по другому, чтобы выйти из данной затруднительной ситуации. Любой медиа-запрос, который служит оберткой для “тихого” placeholder, распространяют свои свойства на селекторы, не размещенные внутри этого запроса.

Выражение достаточно запутанное, поэтому лучше приведу пример:

@media screen {
  %icon {
    transition: background-color ease .2s;
    margin: 0 .5em;
  }
}

.error-icon {
  @extend %icon;
}

.info-icon {
  @extend %icon;
}

Компиляция пройдет без ошибок и ее результатом будет CSS-код:

@media screen {
  .error-icon, .info-icon {
    transition: background-color ease .2s;
    margin: 0 .5em;
  }
}

Заключение

Обе директивы @extend и @include являются очень полезными инструментами, между которыми существует тонкое различие. Если вопрос производительности генерируемого CSS-кода имеет для вас важное значение или же перед вами стоит проблема повторяемости кода, то решением будет являться директива @extend. В некоторых случая @extend значительно упрощает получаемый CSS-код и улучшает его производительность.

Конечно же, ничто не мешает вам смешивать между собой директиву @extend и миксин mixin (если этого требуют обстоятельства):

@media screen {
  %icon {
    transition: background-color ease .2s;
    margin: 0 .5em;
  }
}

@mixin icon($color, $url) {
  @extend %icon;
  background-color: $color;
  background-url: url($url);
}

.error-icon {
  @include icon(red, /images/error.png);
}

.info-icon {
  @include icon(blue, /images/info.png);
}

Однако, в разработке я придерживаюсь такого подхода, когда исходный код легко читается и поддерживается.


Различие между exports и module.exports

Попытка разобраться, в чем различие между exports и module.exports, основанная на статье Understanding module.exports ...Continue reading

Комментарии

Защита от автоматических сообщений
CAPTCHA
Введите слово на картинке