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

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

Рис.16 Псевдокласс Extend.

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

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(.myElement); // используем оператор родительского элемента и псевдокласс extend
	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;
}
.myAnotherElement:extend(.myElement){} // используем псевдокласс extend

В этом случае мы не стали использовать оператор родительского элемента, а явно указали селектор, который мы хотим объединить с имеющимся селектором. Кроме того, во избежании ошибки компиляции мы указали блок объявления стиля {}, это необходимо по той причине, что мы используем псевдокласс 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;
}
.a,
.b :extend(.myElement1), // использование extend с селектором (синтаксис с использованием пробела)
.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 псевдоклассам относятся:

Мы уже с Вами говорили, что 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;
	}
}

.replaceClass:extend(.a, .b all ) {}	// используем псевдокласс extend с ключевым словом 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 ) {} 
.myClass3:extend ( @{myVar} ) {}		// интерполяция переменной в качестве параметра псевдокласса extend

Важной особенностью при работе с переменными является то, что компилятор 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 с правилом @mediacss3

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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