Директива @extend языка SASS
Процесс разработки сайта включает в себя многие моменты. Одним из этих моментов является цветовая тема сайта. Вполне логично, что сайт компании которая занимается доставкой воды будет выдержан в синих цветах. Для сайта компании занимающейся защитой окружающей среды, будет преволировать зеленый цвет. Тут нет ничего удивительного. С большой долей вероятности можно сказать, что при верстке данных сайтов у нас будут классы с одинаковыми стилями. У нас будут блоки с одинаковыми background-ми, цветами, шрифтами. Есть ряд методологий которые советуют выносить в отдельный файл цветовые темы сайта, к примеру SMACSS. Это тоже логично, у нас все background-ы, цвета будут находиться в одном месте. Их будет удобно найти и заменить если потребуется. Поскольку мы верстаем на SASS-е и уже освоили директиву @import, то нам не составит труда вынести цветовую тему в отдельный SCSS-файл, и подключить его к нашему проекту.
К примеру есть у нас какой-то блок с синим background-ом. В левом sidebar-е есть ссылки меню точно с таким же background-ом (ссылки сделаны кнопкой). Понятно, что если background в обоих случаях одинаковый, то данные стили стоит объединить.
.blueBlock,
aside nav a {
background: #006f9f;
}
Если у нас только 2 блока с одинаковым bаckground-ом, то все хорошо, а вот если таких блоков будет больше, и если эти блоки будут лежать в отдельных файлах, то переключаясь между файлами, мы каждый раз будем терять время. Для таких случаев в языке SASS есть директива @extend. Давайте посмотрим как она работает на конкретном примере.
%blueBackground {
background: #006f9f;
}
// Выше находятся к примеру стили header
aside nav a {
@extend %blueBackground;
display: block;
text-decoration: none;
}
// Тут классы между aside nav a и .blueBlock
.blueBlock {
@extend %blueBackground;
}
aside nav a,
.blueBlock {
background: #006f9f;
}
// Здесь находятся стили header
aside nav a {
display: block;
text-decoration: none;
}
// Тут классы между aside nav a и .blueBlock
В файле themes.scss мы можем увидеть строчку %blueBackground. В языке SASS такая конструкция называется "селектором заполнителем". Селекторы заполнители служат для группирования общих свойств разных классов и применяются вместе с директивой @extend. Селектор заполнитель состоит из знака "%" и следующего за ним названия селектора.
Директива @extend позволяет расширять стили других селекторов, внося тем самым объектно-ориентированный подхов в CSS. Если посмотреть на пример, то можно увидеть, что команда @extend %blueBackground для селекторов aside nav a и .blueBlock позволила взять имена данных селекторов и подставить их на место заполнителя %blueBackground. Давайте рассмотрим следующий пример:
#main %blueBackground {
background: #006f9f;
}
// Выше находятся к примеру стили header
aside nav a {
@extend %blueBackground;
display: block;
text-decoration: none;
}
// Тут классы между aside nav a и .blueBlock
.blueBlock {
@extend %blueBackground;
}
#main aside nav a,
#main .blueBlock {
background: #006f9f;
}
// Здесь находятся стили header
aside nav a {
display: block;
text-decoration: none;
}
// Тут классы между aside nav a и .blueBlock
Как можно заметить, имена селекторов aside nav a и .blueBlock подставились на место заполнителя %blueBackground, он у нас из примера является вложенным в блок #main, как следствие после подстановки и наши селекторы оказались вложены в блок #main. Селекторов заполнителей может быть несколько на странице. Обычно для сайта выделяют похожие элементы (правильнее сказать похожие стили) и для каждого такого элемента заводят отдельный селектор заполнитель. Так понятнее.
Если мы где-то в SCSS-файле ввели селектор заполнитель, но не сослались на него через директиву @extend, то данный кусок кода не будет выведен в итоговом CSS. Так же компилятор SASS не позволит вывести ничего не значащие селекторы, например #main#wrapper, ведь один элемент может иметь только один идентификатор.
Директива @extend может работать не только с селекторами заполнителями, но и с классами.
.news {
color: #666;
font-size: 17px;
line-height: 120%;
.row {
margin: 10px 0;
}
}
.article {
@extend .news;
}
.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 может работать с любыми селекторами, а не только с классами и селекторами заполнителями. Посмотрите следующий пример, я думаю объяснять его не нужно:
a:hover {
color: #006f9f;
text-decoration: none;
}
.news {
a {
@extend a:hover;
}
}
.linkHover {
@extend a:hover;
}
a:hover,
.news a,
.linkHover {
color: #006f9f;
text-decoration: none;
}
В директиве @extend расширяемые селекторы можно перечислять через запятую. Это называется множественным расширением.
$blueColor: #006f9f;
%themeMainColor {
color: $blueColor;
}
.logo {
display: block;
text-decoration: none;
}
.article-date a {
@extend %themeMainColor, .logo;
}
.article-date a {
color: #006f9f;
}
.logo,
.article-date a {
display: block;
text-decoration: none;
}
Мне удобнее для каждого расширяемого класса заводить отдельную директиву @extend, так на мой взгляд код проще обслуживать. Есть люди которые перечисляют расширяемые классы через запятую. Впринципе не важно как вы будете использовать директиву @extend, единственное что хочу сказать, выбрав определенный стиль оформления кода старайтесь его придерживаться.
Как я выше писал, директива @extend позволяет группировать похожие свойства, что делает обслуживание итоговой CSS очень удобным. Другим ее предназначением является уменьшение размера CSS-ки. Как только вы освоите данную директиву у вас довольно часто будет возникать такая ситуация:
.secondary-font {
font-family: 'Open Sans', arial;
}
.question {
@extend .secondary-font;
color: #006f9f;
margin: 10px;
}
.answer {
@extend .question;
font-style: italic;
margin-left: 75px;
}
.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)), я бы очень не советовал использовать большее цепное расширение.
Сайт можно сверстать тысячами способами, кому как удобно, но правильно сверстать его так, чтобы наиболее важные элементы находились в одном месте, иными словами сгруппировать их. Сейчас поясню:
$blueColor: #006f9f;
%bbStyle {
color: $blueColor
border-bottom: 1px solid $blueColor;
}
.news-date {
@extend %bbStyle;
border-bottom-width: 2px;
}
.bbStyle {
@extend %bbStyle;
}
.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 в медиа-запросах. Давайте я сейчас покажу не работающий пример:
%mainColor {
color: #006f9f;
}
@media screen and (max-width: 650px) {
.blueBack {
@extend %mainColor;
}
}
Данный пример работать не будет. На данный момент времени ни один компилятор SASS не может расширить селекторы лежащие за пределами медиа-запросов. Компилятор SASS выдаст нам ошибку, в которой будет сказано, что расширяемый селектор (в нашем случае это %mainColor) лежит за пределами директивы @media. Компилятор выдаст ошибку если будет попытка расширить не существующий селектор.
Для того, чтобы наш пример заработал, необходимо вложить расширяемый селектор в медиа-запрос.
@media screen and (max-width: 650px) {
%mainColor {
color: #006f9f;
}
.blueBack {
@extend %mainColor;
}
}
@media screen and (max-width: 650px) {
.blueBack {
color: #006f9f;
}
}
Если компилятор SASS не может расширить селекторы лежащие за пределами медиа-запросов, то он с легкостью расширяет селекторы лежащие в медиа-запросах, сейчас я это объясню на конкретном примере:
.fon-1 {
@extend %backReset;
background: #006f9f url(../images/pic-1.png) no-repeat right bottom;
}
.fon-2 {
@extend %backReset;
background: #ff6f9f url(../images/pic-2.png) no-repeat right bottom;
}
.fon-3 {
@extend %backReset;
background: #00ff9f url(../images/pic-3.png) no-repeat right bottom;
}
@media screen and (max-width: 650px) {
%backReset {
background-image: none;
}
}
.fon-1 {
background: #006f9f url(../images/pic-1.png) no-repeat right bottom;
}
.fon-2 {
background: #ff6f9f url(../images/pic-2.png) no-repeat right bottom;
}
.fon-3 {
background: #00ff9f url(../images/pic-3.png) no-repeat right bottom;
}
@media screen and (max-width: 650px) {
.fon-1,
.fon-2,
.fon-3 {
background-image: none;
}
}
У нас есть 3 блока с какими-то фонами, фоны у нас большие, в ширину они больше 600px. Нет никакого смысла отображать их на разрешении меньшем чем 650px, поэтому мы вводим селектор заполнитель для сброса фоновых картинок (%backReset). Компилятор SASS увидит данный селектор заполнитель даже в медиа-запросе, и соответсвенно расширит его.
Пример который я выше описал не очень применим. За все время, что я занимаюсь front-end-ом, у меня только пару раз возникала такая необходимость. Давайте я покажу более распространенный пример:
.news-col {
@extend %medium_col_6_12;
@extend %small_col_12_12;
float: left;
width: 25%;
margin: 15px 0;
box-sizing: border-box;
}
@media screen and (max-width: 750px) {
%medium_col_6_12 {
width: 50%;
}
}
@media screen and (max-width: 400px) {
%small_col_12_12 {
width: 100%;
}
}
.news-col {
floay: left;
width: 25%;
margin: 15px 0;
box-sizing: border-box;
}
@media screen and (max-width: 750px) {
.news-col {
width: 50%;
}
}
@media screen and (max-width: 400px) {
.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 и заменив пару переменных. Время разработки существенно ускоряется.