Перейти к содержанию

Псевдокласс Extend

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

Базовое применение и синтаксис

Extend это псевдокласс LESS, который позволяет объединить селектор в котором он был вызван, с тем, или теми селекторами на которые он ссылается при вызове. Грубо говоря, он позволяет создать групповой селектор, который объединит в себе стили необходимые для селектора в котором он был вызван, или для которого он был вызван с уже имеющимися селекторами. Перейдем к рассмотрению базового применения и синтаксиса псевдокласса extend. Допустим у нас объявлены следующие стили:

.myElement {
  color: red;
  border: 1px solid red;
}
.myAnotherElement {
  font-size: 2em;
}

Представим, что нам необходимо добавить стили элемента .myElement элементу .myAnotherElement, LESS в этом случае предлагает нам использовать такой инструмент как псевдокласс extend:

.myElement {
  color: red;
  border: 1px solid red;
}
.myAnotherElement {
  // используем оператор родительского элемента и псевдокласс extend
  &:extend(.myElement);
  font-size: 2em;
}

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

Результат компиляции:

.myElement,
.myAnotherElement {
  color: red;
  border: 1px solid red;
}
.myAnotherElement {
  font-size: 2em;
}

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

.myElement {
  color: red;
  border: 1px solid red;
}
.myAnotherElement {
  font-size: 2em;
}

// используем псевдокласс extend
.myAnotherElement:extend(.myElement) {
}

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

С первого взгляда преимущество использования псевдокласса extend может быть не очевидно, особенно на крупных проектах, но не торопитесь c выводами, возможно, он сможет решить конкретно вашу задачу.

Допускается вызывать псевдокласс extend не только по отношению к одному селектору, но и передавать несколько селекторов:

.myElement1 {
  color: red;
}
.myElement2 {
  border: 1px solid red;
}
.myAnotherElement {
  &:extend(.myElement1, .myElement2); // используем псевдокласс extend с несколькими селекторами
  font-size: 2em;
}

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

Результат компиляции:

.myElement1,
.myAnotherElement {
  color: red;
}
.myElement2,
.myAnotherElement {
  border: 1px solid red;
}
.myAnotherElement {
  font-size: 2em;
}

Классическое использование extend

Давайте рассмотрим с Вами классическое использование псевдокласса extend, допустим у нас объявлены классы fruit и apple, которые имеют следующие стили:

.fruit {
  background: red;
  transition: 0.5; // определяем сколько времени занимает эффект перехода
}
.apple {
  background: green;
}

Мы хотим разместить элемент, который бы получил цвет заднего фона объявленного в классе apple и получил оставшиеся стили класса fruit, для этого нам необходимо использовать оба класса и сделать следующую разметку документа:

<div class="fruit apple"></div>

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

.fruit {
  background: red;
  transition: 0.5;
}
.apple {
  &:extend(.fruit); // используем оператор родительского элемента и псевдокласс extend
  background: green;
}

В этом примере мы использовали оператор родительского элемента, чтобы сослаться на родительский элемент и использовали псевдокласс extend, которому в качестве параметра передали имя класса fruit, стили которого необходимо объединить с классом apple, в котором мы использовали этот псевдокласс.

Результат компиляции:

.fruit,
.apple {
  background: red;
  transition: 0.5;
}
.apple {
  background: green;
}

После этого нам достаточно разместить элемент только с классом apple для достижения поставленной перед нами задачи:

<div class="apple"></div>

Использование extend с селекторами

Псевдокласс extend по аналогии с CSS псевдоклассами может использоваться вместе с селекторами:

.myElement1 {
  color: red;
}
.myElement2 {
  border: 1px solid red;
}

// использование extend с селектором (синтаксис с использованием пробела)
.a,
.b :extend(.myElement1),
.c:extend(.myElement2) {
  // использование extend с селектором (синтаксис без использования пробела)
  background: yellow;
}

В этом примере мы использовали псевдокласс extend для двух селекторов, в первом случае в качестве параметра псевдокласса передали имя класса .myElement1, а во втором .myElement2.

Обратите внимание на разницу в синтаксисе использования псевдокласса extend совместно с селекторами, допускается между селектором и псевдоклассом указывать пробел.

Результат компиляции:

.myElement1,
.b {
  color: red;
}
.myElement2,
.c {
  border: 1px solid red;
}
.a,
.b,
.c {
  background: yellow;
}

Селектор может содержать несколько псевдоклассов extend, но все они в обязательном порядке должны располагаться в конце селектора:

// компиляция успешна
a:hover:extend(.myElement1):extend(.myElement2) {
}

// ошибка компиляции
a:extend(.myElement1):hover:extend(.myElement2) {
}

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

a:hover:extend(.myElement1, .myElement2) {
}

Extend внутри групповых селекторов

При размещении псевдокласса extend внутри группового селектора компилятор LESS создаст стили элементам таким образом, как будто мы разместили его отдельно в каждом селекторе этого набора правил, например:

.a {
  color: red;
}
.b,
.c,
.d {
  // размещение псевдокласса внутри группового селектора
  &:extend(.a);
}

// размещение псевдокласса отдельно к каждому селектору
.e:extend(.a),
.f:extend(.a),
.g:extend(.a) {
}

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

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

.a,
.b,
.c,
.d,
.e,
.f,
.g {
  color: red;
}

Extend для вложенных селекторов

Допускается использование псевдокласса extend не только к родительским селекторам, но и к вложенным:

.a {
  .b {
    // вложенный селектор
    color: green;
  }
}
.error:extend(.b) {
} // будет проигнорировано компилятором
.ok:extend(.a .b) {
} // будет скомпилировано

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

.a .b,
.ok {
  color: green;
}

Нюансы использования extend с селекторами

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

Давайте рассмотрим этот нюанс на следующем примере:

a:hover:active {
  background: green;
}

// будет проигнорировано компилятором
.error:extend(a:active:hover) {
}

.myClass > .someClass,
.myClass.someClass,
*.myClass {
  background: green;
}

// будет проигнорировано компилятором
.error:extend(.myClass) {
}

Как вы можете догадаться компилятор LESS проигнорирует все объявления псевдокласса extend по той причине, что не найдет точного вхождения указанного селектора и результат компиляции останется неизменным:

a:hover:active {
  background: green;
}
.myClass > .someClass,
.myClass.someClass,
*.myClass {
  background: green;
}

Следующий нюанс, который мы рассмотрим касается использования псевдокласса extend с CSS псевдоклассами, которые для поиска элементов в документе используют формулу. К таким CSS псевдоклассам относятся:

  • :nth-child()
  • :nth-last-child()
  • :nth-of-type()
  • :nth-last-of-type()

Мы уже говорили, LESS при использованиии псевдокласса extend ищет точное вхождение селектора, это утверждение верно и при использовании его с вышеуказанными CSS псевдоклассами. Например, формулы 1n+5 и n+5 эквивалентны и приводят к поиску одних и тех же элементов, но для LESS они отличаются, его компилятор не умеет производить подобные вычисления и сравнивать математические формулы.

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

a:nth-child(1n + 5) {
  color: green;
}

// будет проигнорировано компилятором
.error:extend(a:nth-child(n + 5)) {
}

// будет скомпилировано
.ok:extend(a:nth-child(1n + 5)) {
}

Результат компиляции:

a:nth-child(1n + 5),
.ok {
  color: green;
}

Следующей особенностью компилятора LESS является то, что он умеет сопоставлять селекторы как с одинарными, так и с двойными кавычками, так и без кавычек, для него они равнозначны, например:

[attr='title'] {
  background: green;
}
.ok:extend([attr='title']) {
}
.ok2:extend([attr='title']) {
}
.error:extend([attr='title']) {
} // ошибка компиляции

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

Результат компиляции первых двух примеров представлен ниже:

[attr='title'],
.ok,
.ok2 {
  background: green;
}

Ключевое слово all

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

При работе с ключевым словом all используется следующий синтаксис:

// поиск и копирование происходит по одному селектору
.replaceClass:extend(selector all) {
}

// поиск и копирование происходит по нескольким селекторам
.replaceClass:extend(selector, anotherSelector all) {
}

Давайте перейдем к рассмотрению примера:

.a {
  color: red;
}

.b {
  background: green;

  // используем оператор родительского элемента
  &:hover {
    color: green;
  }
}

// используем псевдокласс extend с ключевым словом all
.replaceClass:extend(.a, .b all) {
}

В этом примере мы использовали псевдокласс extend, которому в качестве параметра мы передали два класса и использовали при этом ключевое слово all, которое сообщает компилятору о необходимости копирования всех вхождений, переданных селекторов в класс replaceClass.

В результате компиляции мы получим следующие стили:

.a,
.replaceClass {
  color: red;
}
.b,
.replaceClass {
  background: green;
}
.b:hover,
.replaceClass:hover {
  color: green;
}

Интерполяция селекторов с extend

В этом разделе мы рассмотрим как происходит интерполяция селекторов совместно с использованием псевдокласса extend, если Вы не знакомы с понятием интерполяции, то вы можете изучить эту тему в статье этого учебника "Переменные в LESS".

Давайте рассмотрим следующий пример:

@myVar: .myClass;
@{myVar} {
  // интерполяция переменной внутри селектора
  display: flex;
}
.myClass2:extend(.myClass) {
}

// интерполяция переменной в качестве параметра псевдокласса extend
.myClass3:extend ( @{myVar} ) {
}

Важной особенностью при работе с переменными является то, что компилятор LESS не способен сравнивать селекторы, переданные в качестве параметра псевдокласса extend с переменными. В нашем случае мы производим интерполяцию переменной @myVar внутри селектора. Кроме того, если в качестве параметра псевдокласса extend передать переменную, то компилятор просто проигнорирует ее.

Результат компиляции будет следующий:

.myClass {
  display: flex;
}

Единственное место, где интерполяция переменных будет корректно работать с псевдоклассом extend это использование её вместо селектора, который мы хотим объединить с уже имеющимся селектором:

@variable: .myAnotherClass;

.myClass {
  color: khaki;
}
@{variable}:extend(.myClass) {
}

Результат компиляции:

.myClass,
.myAnotherClass {
  color: khaki;
}

Обнаружение дубликатов

Обратите внимание на еще одну особенность компилятора LESS при работе с псевдоклассами extend, в настоящее время, он не умеет находить дубликаты селекторов, что это значит, давайте рассмотрим на следующем примере:

.a,
.b {
  color: red;
}
.c:extend(.a, .b) {
}

В этом примере у нас имеется групповой селектор, элементы которого мы хотим объединить с классом c. Для этого мы используем псевдокласс extend и передаем ему в качестве параметров эти классы. Результат компиляции может вас удивить:

.a,
.b,
.c,
.c {
  // дубликат класса
  color: red;
}

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

Использование extend с правилом @media

В настоящее время псевдокласс extend, объявленный внутри правила @media будет выбирать только те селекторы, которые находятся непосредственно внутри этого правила:

.selector {
  // extend проигнорирует этот селектор
  color: green;
}

@media print {
  .selector {
    // extend проигнорирует этот селектор
    color: red;
  }
}

@media screen {
  .selector {
    // extend выберет этот селектор
    color: blue;
  }
  .screenClass:extend(.selector) {
  } // вызываем псевдокласс extend внутри правила @media
}

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

Результат компиляции:

.selector {
  color: green;
}
@media print {
  .selector {
    color: red;
  }
}
@media screen {
  .selector,
  .screenClass {
    color: blue;
  }
}

Обратите внимание на то, что если внутри правила @media вложено другое правило, то в этом случае псевдокласс extend при вызове из родительского правила не сможет выбрать селекторы во вложенном правиле, например:

@media screen {
  // вызываем псевдокласс extend внутри правила @media
  .screenClass:extend(.myClass) {
  }
  @media (max-width: 1200px) {
    // extend проигнорирует этот селектор
    .myClass {
      display: none;
    }
  }
}

В этом примере мы вызвали псевдокласс extend внутри правила @media и передали в качестве параметра имя класса, размещенное во вложенном правиле, что было проигнорировано компилятором:

@media screen and (max-width: 1200px) {
  .myClass {
    display: none;
  }
}

Мы с Вами рассмотрели использование псевдокласса extend внутри вложенных правил @media, давайте теперь рассмотрим его использование внутри глобальной области видимости на следующем примере:

@media screen {
  // extend выберет этот селектор
  .myClass {
    display: flex;
  }
  @media (max-width: 1200px) {
    // extend выберет этот селектор
    .myClass {
      display: none;
    }
  }
}

// вызываем псевдокласс extend внутри глобальной области видимости
.global:extend(.myClass) {
}

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

Результат компиляции:

@media screen {
  .myClass,
  .global {
    display: flex;
  }
}
@media screen and (max-width: 1200px) {
  .myClass,
  .global {
    display: none;
  }
}