Классный CSS: подход к таблицам стилей Sass как к программе

Перевод статьи Classy CSS: a Programmatic Approach to Sass Stylesheets с сайта una.im, опубликовано на css-live.ru с разрешения автора — Юны Кравец.

Я начала писать этот пост через несколько недель после выхода полусатирической статьи на Sitepoint с моим взглядом на смесь нынешних тенденций систем наименования и организации CSS, под названием «Атомный OOBEMITSCSS». Это было ещё в августе, но на меня навалилось много дел. Я назвала её «Атомный OOBEMITSCSS» в шутку, но люди начали применять это название и использовать его в реальных проектах (что, если честно, было довольно забавно, поскольку при встрече меня стали засыпать вопросами об этом). Споры за и против использования @extend на SassConf в этом году (и в Twitter в последнее время) напомнили мне об этой идее.

Классный CSS, пожалуйста

В вышеупомянутой статье («Атомный OOBEMITSCSS») я объясняла, как я размечаю компоненты (на примере Pinterest) и оформляю их соответственно. Я по-прежнему использую этот пост в качестве удобного руководства для всех, кто интересуется нашей фронтенд-архитектурой, которая с тех пор прошла полномасштабную проверку на большой библиотеке паттернов.

exОдин пример из статьи на Sitepoint

В результате я столкнулась с необходимостью объяснять эту систему (и сам Sass) людям с традиционным программистским образованием. Благодаря чему я осознала, насколько система CSS-архитектуры, которую я описала, похожа на программу и основана на классах. Поэтому я хочу воспользоваться моментом, чтобы дать ей заслуженное название, за которое мне не было бы стыдно, «Классный CSS», а также объяснить ее так, чтобы это оказалось чуть ближе традиционным программистам.

У этого поста три цели:

  1. Отстоять @extend
  2. Познакомить с Классным CSS (выговорить намного легче, чем «Атомный OOBEMITSCSS»)
  3. Рассказать о масштабируемом, модульном и основанном на классах подходе к CSS

Классная кнопка

Ну что, давайте разберём как это работает, на примере кнопки (ведь кнопка — это CSSный «hello world»):

buttons

Начнём с заготовки селектора с базовым кодом для каждой кнопки, используя чуть подправленный синтаксис, похожий на БЭМ. Первая часть его имени будет объектом, на который мы ссылаемся (button or btn), а после двойного дефиса будет модификатор (в данном случае base, т.е. базовый). Заметьте: переменные именуются таким же образом — тип переменной в роли основы (в примере ниже это color).

$color--primary: #b29;
$color--secondary: #19d;

%btn--base {
  border: 1px solid currentColor;
  border-radius: 1.5em;
  background: none;
  outline: none;
  transition-duration: .25s;
  cursor: pointer;
  margin: 30px;
  padding: .5em 1em;

  &:hover {
    color: white;
    background: black;
  }
}

Эта базовая кнопка — наш класс кнопок. К этому мы будем добавлять всё остальное для каждой кнопки, поскольку каждая кнопка — экземпляр этого класса. Каждый элемент-кнопка имеет тип «кнопка», поэтому у них всех есть общие черты (сплошная обводка, прозрачный фон, и т.д.). Каждая кнопка вбирает в себя одни и те же общие свойства и надстраивается над ними. Они не перезаписывают эти свойства, а добавляют к ним новые. Все они — кнопки++, наследующие свойства базовой кнопки. (Надо ли еще как-нибудь повторить эту же мысль?)

%btn--primary {
  @extend %btn--base;
  color: $color--primary;
  font-size: 1.5em;
}

%btn--secondary {
  @extend %btn--base;
  color: $color--secondary;
  font-size: 1.1em;

А вот их использование:

.hero__btn {
  @extend %btn--primary;
  margin: 2em;
}

.sidebar__btn {
  @extend %btn--secondary
}

.global-nav__btn--login {
  @extend %btn--secondary;
  margin-right: 1em;
}

ydkjs-figИллюстрация прототипного наследования из «Вы не знаете JS: Ключевое слово this и прототипы объектов»

Если вы пишете их «классным» способом, у каждого компонента (hero, sidebar, global-nav) будет его собственный частичный файл .scss, где создаются экземпляры класса для реального использования. Это может выглядеть так:

_hero.scss

.hero {

  ...

  &__btn {
    @extend %btn--primary;
  }

  ...

}

_sidebar.scss

.sidebar {

  ...

  &__btn {
    @extend %btn--secondary;
  }

  ...

}

_global-nav.scss

.global-nav {

    ...

    &__btn {
      @extend %btn--secondary;

      &--login {
        @extend .global-nav__btn;
        margin-right: 1em;
        // в этом месте вы 
        // оформляете .global-nav__btn--login
    }
  }
}

Так, а что насчёт миксинов?

Вам может быть интересно, почему бы вместо этого не использовать @mixin? Наш способ разбивки классов по файлам ближе к тому, для чего придуман и предназначен @extend. Можно создавать миксины в классах-заготовках, над которыми мы надстраиваем стили, а затем использовать @extend, чтобы создать экземпляры классов с этими стилями.

В защиту @extend

Применение @extend позволяет уменьшить итоговый файл CSS и сделать код чище, если понимать, что происходит (мы дополняем стили, а не дублируем их). Можно возразить, что это не проблема, есть же gzip — но всегда полагаться на gzip нельзя, ведь доступа к настройкам сервера может не оказаться.

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

@mixin

mixin

@extend

extend

@mixin — это как штамповка: он создает дубликат блока со свойствами, с необязательной добавкой в виде переданных аргументов. @extend добавляет тот элемент, который вы расширяете, к блоку свойств. Это всё равно что сказать «Да, и еще ___».

Это значит, что наш итоговый CSS-файл будет меньше, ведь мы не повторяем блок кода каждый раз, когда он используется. C @extend мы можем просто ссылаться на свойства. Директивы @extend идеально подходят для этого! Их логическое предназначение вполне разумно. #teamExtend

Недостаток здесь в том, что мы не знаем, где находится блок свойств, поскольку мы не создаем новую копию блока кода в директиве, а лишь ссылаемся на него как на источник стилей (вот почему нельзя использовать @extend в медиавыражениях). Но это неважно, если мы следуем принципам Классного CSS. Вы расширяете классы-заготовки, превращая их в настоящие, годные к использованию классы, и работаете с экземплярами этих классов, а директивы @mixin резервируются для другой цели. С их помощью мы выстраиваем в классах-заготовках тот код, который затем расширяем.

В случае Классного CSS, миксины напоминают конструкторы и используются только в заготовках селекторов.

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

// двоеточие после аргумента означает значение по умолчанию
// создает функцию-конструктор (миксин)
@mixin btn-me($color: hotpink, $size: normal) {
  border: 1px solid $color;
  border-radius: 1.5em;
  background: none;
  outline: none;
  transition-duration: .25s;
  cursor: pointer;
  margin: 30px;
  padding: .5em 1em;

  @if $size == 'small' {
    font-size: .8em;
  } @else {
    font-size: 1.2em;
  }

  &:hover {
    color: white;
    background: $color;
  }
}

// создает классы-заготовки как основу для будущего расширения, на которую мы будем ссылаться

%btn--primary {
  @include btn-me; // если аргументов нет, то подставляется значение по умолчанию
}

%btn--secondary {
  @include btn-me(blue, small)
}

// получаем из кода экземпляр с семантическим именем
// только здесь мы пишем компилируемый код
.hero__btn {
  @extend %btn--secondary;
}

И итоговый файл CSS выглядит так:

.hero__btn {
  border: 1px solid blue;
  border-radius: 1.5em;
  background: none;
  outline: none;
  transition-duration: .25s;
  cursor: pointer;
  margin: 30px;
  padding: .5em 1em;
  font-size: .8em;
}

.hero__btn:hover {
  color: white;
  background: blue;
}

Преимущества

Конечно, у этой системы свой характер, и она может показаться сложноватой для непосвящённых, но как только вы начнёте с ней работать, она здорово поможет придумывать имена и решать вопросы архитектуры

В числе преимуществ:

  • Гарантирует такую организацию кода, что в нем не случится конфликта специфичностей и ничего не будет переопределяться (см. ITCSS) — а значит, CSS становится проще, эффективнее, а размер итогового CSS-файла — меньше.
  • Последовательная система наименования (БЭМ) сохраняет единообразие в команде и уменьшает путаницу благодаря общим правилам.
  • Система организована, легко поддерживается и масштабируется

Пишите классный код, и пусть Sass поможет вам воплотить самые дерзкие идеи! 💁

Ну всё, на этом откланиваюсь

P.S. Это тоже может быть интересно:

2 Комментарии

  1. Joniks

    Я этим подходом уже год пользуясь. Однако надо добавить что екстенд и сасс плохое сочетание тк в сасс екстенд весьма урезан(не цепляет вложенные классы, не дружит с медиа) поэтому использовать стоит или лесс или стайлус.

  2. Sergey

    Можно обойтись без extend если уже используется БЭМ, как раз для этого и существуют модификаторы. Ссылка.

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

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