Статья посвящена вопросу “тихих” 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 были введены в 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');
}Однако, в разработке я придерживаюсь такого подхода, когда исходный код легко читается и поддерживается.