Директива @extend языка SASS

Процесс разработки сайта включает в себя многие моменты. Одним из этих моментов является цветовая тема сайта. Вполне логично, что сайт компании которая занимается доставкой воды будет выдержан в синих цветах. Для сайта компании занимающейся защитой окружающей среды, будет преволировать зеленый цвет. Тут нет ничего удивительного. С большой долей вероятности можно сказать, что при верстке данных сайтов у нас будут классы с одинаковыми стилями. У нас будут блоки с одинаковыми background-ми, цветами, шрифтами. Есть ряд методологий которые советуют выносить в отдельный файл цветовые темы сайта, к примеру SMACSS. Это тоже логично, у нас все background-ы, цвета будут находиться в одном месте. Их будет удобно найти и заменить если потребуется. Поскольку мы верстаем на SASS-е и уже освоили директиву @import, то нам не составит труда вынести цветовую тему в отдельный SCSS-файл, и подключить его к нашему проекту.

К примеру есть у нас какой-то блок с синим background-ом. В левом sidebar-е есть ссылки меню точно с таким же background-ом (ссылки сделаны кнопкой). Понятно, что если background в обоих случаях одинаковый, то данные стили стоит объединить.

Файл example.scss
1 2 3 4

.blueBlock,

aside nav a {

background: #006f9f;

}

Если у нас только 2 блока с одинаковым bаckground-ом, то все хорошо, а вот если таких блоков будет больше, и если эти блоки будут лежать в отдельных файлах, то переключаясь между файлами, мы каждый раз будем терять время. Для таких случаев в языке SASS есть директива @extend. Давайте посмотрим как она работает на конкретном примере.

Файл themes.scss
1 2 3

%blueBackground {

background: #006f9f;

}

Файл style.scss
1

// Выше находятся к примеру стили header

2 3 4 5 6

aside nav a {

@extend %blueBackground;

display: block;

text-decoration: none;

}

7

// Тут классы между aside nav a и .blueBlock

8 9 10

.blueBlock {

@extend %blueBackground;

}

Скомпилированный CSS
1 2 3 4 5 6 7 8 9

aside nav a,

.blueBlock {

background: #006f9f;

}

// Здесь находятся стили header

aside nav a {

display: block;

text-decoration: none;

}

10

// Тут классы между aside nav a и .blueBlock

В файле themes.scss мы можем увидеть строчку %blueBackground. В языке SASS такая конструкция называется "селектором заполнителем". Селекторы заполнители служат для группирования общих свойств разных классов и применяются вместе с директивой @extend. Селектор заполнитель состоит из знака "%" и следующего за ним названия селектора.

Директива @extend позволяет расширять стили других селекторов, внося тем самым объектно-ориентированный подхов в CSS. Если посмотреть на пример, то можно увидеть, что команда @extend %blueBackground для селекторов aside nav a и .blueBlock позволила взять имена данных селекторов и подставить их на место заполнителя %blueBackground. Давайте рассмотрим следующий пример:

Файл themes.scss
1 2 3

#main %blueBackground {

background: #006f9f;

}

Файл style.scss
1

// Выше находятся к примеру стили header

2 3 4 5 6

aside nav a {

@extend %blueBackground;

display: block;

text-decoration: none;

}

7

// Тут классы между aside nav a и .blueBlock

8 9 10

.blueBlock {

@extend %blueBackground;

}

Скомпилированный CSS
1 2 3 4 5 6 7 8 9

#main aside nav a,

#main .blueBlock {

background: #006f9f;

}

// Здесь находятся стили header

aside nav a {

display: block;

text-decoration: none;

}

10

// Тут классы между aside nav a и .blueBlock

Как можно заметить, имена селекторов aside nav a и .blueBlock подставились на место заполнителя %blueBackground, он у нас из примера является вложенным в блок #main, как следствие после подстановки и наши селекторы оказались вложены в блок #main. Селекторов заполнителей может быть несколько на странице. Обычно для сайта выделяют похожие элементы (правильнее сказать похожие стили) и для каждого такого элемента заводят отдельный селектор заполнитель. Так понятнее.

Если мы где-то в SCSS-файле ввели селектор заполнитель, но не сослались на него через директиву @extend, то данный кусок кода не будет выведен в итоговом CSS. Так же компилятор SASS не позволит вывести ничего не значащие селекторы, например #main#wrapper, ведь один элемент может иметь только один идентификатор.

Директива @extend может работать не только с селекторами заполнителями, но и с классами.

Файл style.scss
1 2 3 4 5 6 7 8 9 10 11

.news {

color: #666;

font-size: 17px;

line-height: 120%;

.row {

margin: 10px 0;

}

}

.article {

@extend .news;

}

Скомпилированный CSS
1 2 3 4 5 6 7 8 9 10

.news,

.article {

color: #666;

font-size: 17px;

line-height: 120%;

}

.news .row,

.article .row {

margin: 10px 0;

}

Как видно из примера, директива @extend с классами работает точно так же как и с селектором заполнителем. Принцип работы не меняется. Компилятор берет название класса в котором была объявлена директива @extend, просматривает весь SCSS-документ, включая файлы подключаемые через директиву @import, и подставляет имя взятого класса на место класса определенного в директиве @extend. Класс определяемый в директиве @extend в таком случае называют расширяемым, а класс в котором определена директива @extend называется расширяющимся. В нашем случае расширяемым классом является класс news, а расширяющимся классом является класс article.

Как ни трудно догадаться директива @extend может работать с любыми селекторами, а не только с классами и селекторами заполнителями. Посмотрите следующий пример, я думаю объяснять его не нужно:

Файл style.scss
1 2 3 4 5 6 7 8 9 10 11 12

a:hover {

color: #006f9f;

text-decoration: none;

}

.news {

a {

@extend a:hover;

}

}

.linkHover {

@extend a:hover;

}

Скомпилированный CSS
1 2 3 4 5 6

a:hover,

.news a,

.linkHover {

color: #006f9f;

text-decoration: none;

}

В директиве @extend расширяемые селекторы можно перечислять через запятую. Это называется множественным расширением.

Файл style.scss
1 2 3 4 5 6 7 8 9

$blueColor: #006f9f;

%themeMainColor {

color: $blueColor;

}

.logo {

display: block;

text-decoration: none;

}

.article-date a {

10

@extend %themeMainColor, .logo;

11

}

Скомпилированный CSS
1 2 3 4 5 6 7 8

.article-date a {

color: #006f9f;

}

.logo,

.article-date a {

display: block;

text-decoration: none;

}

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

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

Файл style.scss
1 2 3 4 5 6 7 8 9 10 11 12 13

.secondary-font {

font-family: 'Open Sans', arial;

}

.question {

@extend .secondary-font;

color: #006f9f;

margin: 10px;

}

.answer {

@extend .question;

font-style: italic;

margin-left: 75px;

}

Скомпилированный CSS
1 2 3 4 5 6 7 8 9 10 11 12 13 14

.secondary-font,

.question,

.answer {

font-family: 'Open Sans', arial;

}

.question,

.answer {

color: #006f9f;

margin: 10px;

}

.answer {

font-style: italic;

margin-left: 75px;

}

Код выше обозначает стандартную форму "вопрос/ответ" (FAQ). По дизайнту данная форма отличается от всего остального текста только шрифтом и цветом. Смотрите, что здесь происходит. Класс answer через директиву @extend ссылается на класс question, а класс question в свою очередь через директиву @extend ссылается на класс secondary-font. Данная ситуация называется цепным расширением.

С одной стороны это удобно. У нас класс answer должен наследовать стили класса question, кратчайший способ это сделать через директиву @extend. С другой стороны код усложняется для понимания. В нашем примере понятно, что класс answer должен быть похожим на класс question, но ситуация может быть и более запутана. Старайтесь избегать цепного расширения, если классы лежат в разных модулях. При большом количестве кода через какое-то время вы можете запутаться в том кто, что у кого наследует. Не стоит лишний раз усложнять код, ведь понятный код обслуживать проще. Особенно не стоит его усложнять если вы не один работаете над проектом. Однажды я разбирался в коде одного начинающего верстальщика, он так переусердствовал с наследованием, что создал 4 класса которые наследовали друг-друга по цепочки. В нашем примере используется двойное цепное расширение (класс answer наследует класс question (1), класс question наследует класс secondary-font (2)), я бы очень не советовал использовать большее цепное расширение.

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

Файл style.scss
1 2 3

$blueColor: #006f9f;

%bbStyle {

color: $blueColor

4

border-bottom: 1px solid $blueColor;

4 5 6 7 8 9 10 11

}

.news-date {

@extend %bbStyle;

border-bottom-width: 2px;

}

.bbStyle {

@extend %bbStyle;

}

Скомпилированный CSS
1 2 3 4 5 6 7 8

.bbStyle,

.news-date {

color: #006f9f;

border-bottom: 1px solid #006f9f;

}

.news-date {

border-bottom-width: 2px;

}

Как я выше писал для каждого сайта есть своя цветовая тема, в нашем примере она синяя (#006f9f). Синий цвет к примеру применяется для цвета ссылок, нижнего подчеркивания, фона для разных блоков. Естественно данный цвет стоит вынести в переменную. Также у нас есть ряд блоков которые имеют нижнюю границу того же цвета. Для данных блоков стоит создать селектор заполнитель с данным стилем (в нашем примере это %bbStyle). Есть блок .news-date который отличается от других блоков, к примеру у него толщина границы больше. Правильнее будет сослаться на селектор заполнитель через директиву @extend, а толщину границы переопредеить (border-bottom-width). В этом случае, если только мы работаем с сайтом, мы можем заменив одну переменную, полностью изменить цветовую тему сайта. Если помимо нас кто-то еще работает с CSS-ой, то у нас все цвета будут находиться в одном месте (вверху или внизу CSS-файла, как захотим). Их будет удобно найти и заменить, не нужно будет искать каждое появление данного цвета в CSS, тем самым мы упростим обслуживания сайта.

Директива @extend в медиа-запросах

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

1 2 3 4 5 6 7 8

%mainColor {

color: #006f9f;

}

@media screen and (max-width: 650px) {

.blueBack {

@extend %mainColor;

}

}

Данный пример работать не будет. На данный момент времени ни один компилятор SASS не может расширить селекторы лежащие за пределами медиа-запросов. Компилятор SASS выдаст нам ошибку, в которой будет сказано, что расширяемый селектор (в нашем случае это %mainColor) лежит за пределами директивы @media. Вообще, это единственный случай когда компилятор SASS выдаст нам ошибку при использовании директивы @extend, во всех других случаях никакого предупреждения не будет.

Многие разработчики частенько допускают ошибки в расширяемых селекторах, как следствие компилятор SASS полностью просматривает SCSS-файл, не находит расширяемый селектор, естественно ничего не расширяет (можно сказать не группирует, кому как понятнее). Никакой ошибки не возникает, и разработчик долго не может понять, что не так. Если бы я разрабатывал компилятор SASS, я бы сделал ошибку при попытки расширить не существующй селектор, ну да ладно, просто запомните эту особенность.

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

Файл style.scss
1

@media screen and (max-width: 650px) {

2 3 4 5 6 7 8

%mainColor {

color: #006f9f;

}

.blueBack {

@extend %mainColor;

}

}

Скомпилированный CSS
1

@media screen and (max-width: 650px) {

2 3 4 5

.blueBack {

color: #006f9f;

}

}

Если компилятор SASS не может расширить селекторы лежащие за пределами медиа-запросов, то он с легкостью расширяет селекторы лежащие в медиа-запросах, сейчас я это объясню на конкретном примере:

Файл style.scss
1

.fon-1 {

2

@extend %backReset;

3

background: #006f9f url(../images/pic-1.png) no-repeat right bottom;

4

}

5

.fon-2 {

6

@extend %backReset;

7

background: #ff6f9f url(../images/pic-2.png) no-repeat right bottom;

8

}

9

.fon-3 {

10

@extend %backReset;

11

background: #00ff9f url(../images/pic-3.png) no-repeat right bottom;

12

}

13

@media screen and (max-width: 650px) {

14 15 16 17

%backReset {

background-image: none;

}

}

Скомпилированный CSS
1

.fon-1 {

2

background: #006f9f url(../images/pic-1.png) no-repeat right bottom;

3

}

4

.fon-2 {

5

background: #ff6f9f url(../images/pic-2.png) no-repeat right bottom;

6

}

7

.fon-3 {

8

background: #00ff9f url(../images/pic-3.png) no-repeat right bottom;

9

}

10

@media screen and (max-width: 650px) {

11 12 13 14 15 16

.fon-1,

.fon-2,

.fon-3 {

background-image: none;

}

}

У нас есть 3 блока с какими-то фонами, фоны у нас большие, в ширину они больше 600px. Нет никакого смысла отображать их на разрешении меньшем чем 650px, поэтому мы вводим селектор заполнитель для сброса фоновых картинок (%backReset). Компилятор SASS увидит данный селектор заполнитель даже в медиа-запросе, и соответсвенно расширит его.

Пример который я выше описал не очень применим. За все время, что я занимаюсь front-end-ом, у меня только пару раз возникала такая необходимость. Давайте я покажу более распространенный пример:

Файл style.scss
1 2 3 4 5 6 7 8

.news-col {

@extend %medium_col_6_12;

@extend %small_col_12_12;

float: left;

width: 25%;

margin: 15px 0;

box-sizing: border-box;

}

9

@media screen and (max-width: 750px) {

10 11 12 13

%medium_col_6_12 {

width: 50%;

}

}

14

@media screen and (max-width: 400px) {

15 16 17 18

%small_col_12_12 {

width: 100%;

}

}

Файл style.scss
1 2 3 4 5 6

.news-col {

floay: left;

width: 25%;

margin: 15px 0;

box-sizing: border-box;

}

7

@media screen and (max-width: 750px) {

8 9 10 11

.news-col {

width: 50%;

}

}

12

@media screen and (max-width: 400px) {

13 14 15 16

.news-col {

width: 100%;

}

}

В нашем примере у нас есть 4 колонки новостей (при "width: 25%" больше 4 колонок в ряд не войдет). В строчках с 1 по 7 мы описываем их состояние на обычном разрешении. На разрешении планшета (у нас это 750px) наши колонки начинают смотреться коряво, поэтому их стоит разместить не по 4 в ряд, а по 2, это мы и делаем. Для этого мы вводим в данном медиа-запросе селектор заполнитель %medium_col_6_12 (будем использовать сетку на 12 колонок). На разрешени мобильного телефона наши колонки стоит разместить по 1 в ряд, что мы и делаем вводя в данном медиа-запросе селектор заполниетль %small_col_12_12. Это очень удобно.

Если нам к примеру понадобится сделать адаптивными любые 4 колонки (товары в каталоге, блоки банеров, анонсы статей, рекламные объявления и тд, тут что угодно может быть), то мы можем просто сослаться на наши селекторы заполнители (%medium_col_6_12, %medium_col_12_12). Дописывать ничего не придется, директива @extend сделает все за нас.

Многие CSS-framework-и на этом построены. Если посмотреть тот же самый zurb foundation, то там вся сетка вынесена в отдельные классы, а точки перехода (кто не в теме, это точки по которым определяется разрешение для планшета и телефона) вынесены в переменные. Если подключать данный framework через SASS-файлы, то любой сайт можно сделать адаптивным в кратчайшие сроки. Я видел faramework-и в которых сетка вынесена в селекторы заполнители, в таких framework-ах любой блок сайта можно сделать адаптивным применив пару директив @extend и заменив пару переменных. Время разработки существенно ускоряется.