Начинаем писать CSS

Вам не кажется, что CSS уже не такой как раньше? Последние несколько лет это актуальная тема для множества умных людей. CSS — это не просто звено, которое фронтенд-разработчик должен добавить, чтобы страница выглядела красиво. Это намного больше. Производительность для нас играет большую роль и мы стремимся разрабатывать хорошие сайты. В этой статье я хочу поделиться опытом, приобретённым за последние несколько месяцев и моими взглядами на написание CSS-кода. Меня как программиста очень интересует архитектурная часть процесса. Мне кажется, что процесс написания CSS нуждается в изменениях и я усердно изучаю этот вопрос. Я веду поиск удачных подходов и методов, новых вариантов организации процесса написания кода. Эта статья является своеобразным итогом путешествия в мир CSS. Многие говорят, что написание CSS — это не совсем программирование. Я с этим не согласен и считаю, что оно не менее интересно и сложно.

Препроцессоры CSS

Препроцессоры

Взглянем правде в лицо. Написание чистого CSS — не самое увлекательное занятие в мире. Препроцессоры берут нечто напоминающее CSS и чудесным образом выдают валидный CSS-код. Это добавляет дополнительную прослойку между разработчиком и конечным стилем, который будет послан браузеру. Однако всё не так плохо, ведь препроцессоры обладают некоторыми очень полезными возможностями.

Конкатенация

Наиболее ценной возможностью препроцессоров я считаю конкатенацию файлов. Уверен, вы знаете что написав @import в файле .css вы собственно говорите браузеру: «используй и этот файл, пожалуйста». И он его использует. Добавляется еще один запрос, что не очень хорошо, ведь таких файлов у вас может быть много. Большое количество запросов ухудшает производительность приложения. Если использовать препроцессоры CSS, эта проблема устраняется. Они просто объединяют все стили в один css-файл.

Расширение

Есть два основных препроцессора CSS — LESS и Sass. Они оба поддерживают расширение CSS. Да, работают они немного по-разному, но идея та же. Вы создаете базовый класс (его обычно называют «миксин») с набором свойств и затем импортируете эти свойства в другой селектор. Например:

// less
.bordered(@color: #000) {
    border: dotted 2px @color;
}
.header { .bordered; }
.footer { .bordered(#BADA55); }

// компилируется в
.header {
    border: dotted 2px #000000;
}
.footer {
    border: dotted 2px #bada55;
}

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

.bordered {
    border: dotted 2px #000;
}

он целиком добавляется в скомпилированный файл CSS, вне зависимости от того используется он или нет. Это происходит, потому что он является валидным селектором. Sass даёт немного больше гибкости за счёт миксинов, расширений и плейсхолдеров (если вас интересует в чём между ними разница, очень советую прочитать эту статью). Давайте рассмотрим следующий Sass и его компиляцию:

// Sass
@mixin bordered($color: #000) {
    border: dotted 2px $color;
}
.header { @include bordered; }
.footer { @include bordered(#BADA55); }

// компилируется в
.header {
    border: dotted 2px black;
}
.footer {
    border: dotted 2px #bada55;
}

Выглядит почти так же. Но если перейти к второму варианту использования и определить плейсхолдер:

// Sass
%bordered {
    border: dotted 2px #000;
}
.header {
    @extend %bordered;
}
.footer {
    @extend %bordered;
}

// компилируется в
.header, .footer {
    border: dotted 2px #000;
}

Очевидны два позитивных момента. Во-первых, класс .bordered не компилируется. Во-вторых, Sass комбинирует селекторы, что делает CSS немного короче.

Компоновка

LESS и Sass поддерживают определение переменных. Можно в любой момент обратиться к этим переменным и использовать их в качестве значений для свойств.

// Sass
$brand-color: #009f0A;
…
h1 {
    color: $brand-color;
}

Это удобная функция, поскольку она позволяет сохранять важные данные вроде цветов компании или ширину сетки в одном месте. Если вам нужно что-то изменить, не придётся просматривать весь код.

Также удобно использовать переменные, когда нужно сделать вставку. Это продемонстрировано в следующем примере:

// Sass
@mixin border($side) {
    border-#{$side}: solid 1px #000;
}
.header {
    @include border("left");
}

// компилируется в
.header {
    border-left: solid 1px #000;
}

Аргументы против препроцессоров

БЭМ

БЭМ

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

<header class="site-header">
    <div class="logo"></div>
    <div class="navigation"></div>
</header>

Стили могут быть такими:

.site-header { … }
.logo { … }
.navigation { … }

Такой код, конечно же, будет работать, однако есть проблема: читая CSS вы не сможете понять что, например, логотип logo является частью шапки header. Еще один маленький логотип может быть использован в подвале. Следующий логичный шаг — прописать в селекторе элемент-родитель.

.site-header .logo { … }

Однако это не очень хорошая идея, ведь стили становятся зависимыми от иерархии конкретных тегов. Что если потребуется перенести логотип за пределы тега header? Стиль не будет применён. Можно добавить site-header в название класса логотипа:

.site-header-logo { … }

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

.site-header-logo-xmas { … }

потому что по логике название селектора должно отвечать расположению тегов в HTML.

БЭМ может стать решением в таком случае. Он расшифровывается как «Блок, Элемент, Модификатор» и диктует некоторые правила, которым нужно следовать. Используя БЭМ можно превратить наш маленький пример в:

.site-header { … } /* блок */
.site-header__logo { … } /* элемент */
.site-header__logo--xmas { … } /* модификатор */
.site-header__navigation { … } /* элемент */

Т.е. site-header — это у нас блок. Логотип logo и навигация navigation — элементы этого блока, а версия логотипа xmas — модификатор. Возможно всё и выглядит очень просто, но такой подход даёт широкие возможности. Начав его использовать, вы убедитесь что архитектура ваших работ улучшится. Слабой стороной БЭМ является разве что синтаксис. Да, он немного безобразен, но я готов пойти на жертвы ради исправности системы.

(материалы для чтения: здесь и здесь)

OOCSS

OOCSS

Открыв для себя БЭМ, я научился правильно называть классы и задумался над структурой. Наверное первой мне на глаза попалась статья об Объектно-ориентированном CSS. Суть объектно-ориентированного программирования частично заключается в использовании абстракций и язык CSS их поддерживает. Не важно используете вы препроцессоры или нет, вам нужно знать об OOCSS. Я программист, поэтому эта концепция показалась мне очень похожей на то, как я программирую каждый день, например, на JavaScript. У неё есть два главных принципа:

Разделение структуры и оформления

Взгляните на следующий пример:

.header {
    background: #BADA55;
    color: #000;
    width: 960px;
    margin: 0 auto;
}
.footer {
    background: #BADA55;
    text-align: center;
    color: #000;
    padding-top: 20px;
}

В нём несколько стилей продублированы. Их можно выделить в отдельный класс следующим образом:

.colors-skin {
    background: #BADA55;
    color: #000;
}
.header {
    width: 960px;
    margin: 0 auto;
}
.footer {
    text-align: center;
    padding-top: 20px;
}

Теперь у нас есть объект colors-skin, которому можно найти широкое применение. Разметка может выглядеть так:

<div class="header colors-skin"></div>
<div class="colors-skin"></div>
<div class="footer colors-skin"></div>

У такого изменения есть несколько плюсов:

Разделение контейнера и содержимого

Здесь суть в том, чтобы у каждого элемента был один и тот же стиль вне зависимости от его расположения. Это значит что следует избегать использования селекторов вроде следующего:

.header .social-widget {
    width: 250px;
}

Ведь если переместить .social-widget за пределы контейнера .header, его ширина изменится. В общем, так делать не рекомендуется. Особенно для компонентов, которые встречаются по всей странице. Это принцип блочного CSS и я настоятельно рекомендую выделить немного времени на то, чтобы попробовать такой подход. Лично мне следование этому принципу помогает писать лучший CSS.

Фреймворк

Если вы откроете репозиторий OOCSS на GitHub, то увидите фреймворк. Да, в этом фреймворке используется концепция объектно-ориентированного CSS и да, у него есть несколько крутых компонентов, готовых к использованию. С некоторого времени мне не нравятся фреймворки. Если вы на минуту задумаетесь, то увидите что слово фреймворк состоит из двух частей — «frame» и «work», что значит «каркас, рамка»1 и «изделие». И действительно, работая с фреймворком вы ограничены рамками. Вы связались с этим инструментом и вынуждены играть по его правилам. Я отдаю предпочтение микро-фреймворкам или подобным инструментам, которые дают мне лишь основу. Я ни в коем случае не пытаюсь изобрести колесо, только хочу найти баланс. Очень часто готовые к использованию решения ведут к неопрятной и слишком запутанной системе. Я бы советовал создавать такие инструменты с одной конкретной целью. Если пытаться предвидеть побольше способов применения, в результате получите… ну, вы поняли — фреймворк.

Тем не менее я настоятельно рекомендую взглянуть на фреймворк OOCSS. Возможно он подойдёт под ваши требования. Создатель репозитория — Николь Саливан (Nicole Sullivan). Она первооткрыватель OOCSS и если у вас появится немного свободного времени, советую послушать её презентации/лекции.

SMACSS

SMACSS

Представляю вам ещё одну популярную концепцию: SMACSS. SMACSS расшифровывается, как масштабируемая модульная архитектура CSS (Scalable and Modular Architecture for CSS). Джонатан Снук (Jonathan Snook) предложил нечто вроде гида по стилю для CSS-разработчиков. Суть в том, чтобы разделить ваше приложение на следующие категории:

Мне пока не приходилось использовать концепцию SMACSS, но она довольно популярна и действительно продвигает хорошие идеи. Больше всего радует, что она скорее является идеей реализации, чем фреймворком. Следовательно, вы не связаны чёткими правилами, классами или компонентами.

Атомарный дизайн

Атомный

Изучив OOCSS и SMACSS, я начал искать подходящее модельное представление и довольно быстро оказался на этой странице. Это презентация отличной концепции «Атомный дизайн». Её автор — Бред Фрост (Brad Frost), известный веб-разработчик, работающий преимущественно в мире отзывчивого дизайна и дизайна для мобильных устройств.

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

<label>Поиск по сайту</label>

или

<input type="text" placeholder="введите ключевое слово" />

Т.е. атомы содержат базовые стили элементов DOM. Например, цветовую палитру, размеры шрифтов или переходы. Затем эти частички можно объединить в молекулы. Например:

<form>
    <label>Поиск по сайту</label>
    <input type="text" placeholder="введите ключевое слово" />
    <input type="submit" value="Поиск" />
</form>

Элемент form состоит из нескольких атомов. Абстрагируясь таким образом, мы получаем большую гибкость, ведь можно использовать те же атомы чтобы построить другую молекулу. Кроме того, ту же форму можно использовать в разных контекстах.

На этом Бред не остановился. Молекулы объединяются в организмы. Следуя тому же подходу, можно прописать следующее и назвать его организмом:

<header>
    <div class="logo">
    <nav>
        <ul>
            <li><a href="#">Домашняя страница</a></li>
            <li><a href="#">Общие сведения</a></li>
            <li><a href="#">Контакты</a></li>
        </ul>
    </nav>
    <form>
        <label>Поиск по сайту</label>
        <input type="text" placeholder="введите ключевое слово" />
        <input type="submit" value="поиск" />
    </form>
</header>

Еще одна особенность концепции — заготовки. Они не имеют ничего общего с химией, однако вписываются в веб-контекст. Сочетая разные организмы, мы создаём заготовку. Затем из этих заготовок формируется конечная страница.

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

Органический CSS

ОCSS

Пару месяцев назад я написал статью об Organic. Это чудесный маленький фреймворк для приложений на JavaScript. Он даже больше смахивает на шаблон разработки и лично мне он очень понравился. Я даже использовал Organic в нескольких проектах и всё работает без каких-либо проблем. Если он вас заинтересовал, советую почитать этот пост.

Когда я натолкнулся на статью Бреда Фроста, мне уже была знакома похожая концепция, реализованная в Organic. Работа Бреда просто великолепна, однако я решил пойти дальше и написать собственный микро-фреймворк на основе концепции атомного дизайна. Я использовал Sass в качестве препроцессора и создал репозиторий на Github — https://github.com/krasimir/organic-css.

Атомы

Начнём с наименьшего компонента фреймворка — атома. Согласно определению из википедии, атом является наименьшей частицей вещества. В контексте CSS, думаю этому определению соответствует свойство и его значение. Например:

margin-top: 24px;

Добавление атомов посредством прописания стилей прямо в классах не соответствует моим намерениям. Потому что если я напишу что-то вроде этого:

body {
    margin-top: 24px;
}
header {
    margin-top: 24px;
}

препроцессор оставит эту запись как есть. А мне нужен вот такой результат:

body, header {
    margin-top: 24px;
}

В Sass такого эффекта можно добиться используя плейсхолдеры. Т.е.

%margin-top-24 {
    margin-top: 24px;
}
body {
    @extend %margin-top-24;
}
header {
    @extend %margin-top-24;
}

Итак, мне пришлось использовать плейсхолдеры. Это также значит что мне потребовалось создать большое количество плейсхолдеров, которые можно было бы использовать впоследствии. Я решил, что фреймворк будет состоять только из атомов. И может быть нескольких молекул с функциями широкого применения вроде всем известного reset.css, определения сетки и т.п. Я хотел написать нечто, что могло бы служить основой для разработки CSS. Возможно в дальнейшем процессе работы над проектами я найду и какие-то заготовки, которые также можно будет предложить в качестве основы, но для начала я хотел сохранить репозиторий чистым и простым.

Для большей последовательности работы я создал миксин для определения атома. Итак, вот пример:

@include define-atom("block") {
    display: block;
}
@include define-atom("font-family") {
    font-family: Georgia;
}

Используя этот подход я создал подборку атомов, которые можно с легкостью применить для любого проекта. Взглянуть на них можно здесь. Я использовал некоторые хорошие правила из других фреймворков, не всё является моей заслугой. Также доступен миксин для объединения атомов в молекулу:

@mixin header { // <- молекула с названием 'header'
    @include atoms((
        block,
        clearfix,
        font-family
    ));
}

Молекулы

Молекула — это элемент DOM, который нужно стилизировать и у которого нет дочерних элементов. Или же если у него есть дочерние элементы, они напрямую с ним не связаны. Например, <img src="logo.jpg" /> может быть молекулой. Если вам трудно определить молекулы у себя на странице, просто подумайте что построено из атомов. Если какой-то элемент построен из других молекул, это наверное органелла. Несколько строчек выше я показал как определить молекулу:

@mixin login-box {
    @include atoms((
        block,
        font-size-20,
        margin-top-23,
        bold
    ));
}

Ко мне пришла одна интересная мысль. Рассмотрим тег body. Что это? Молекула или что-то другое? Он, без сомнения, нуждается в стилизации посредством атомов, но в общем содержит другие молекулы. Значит он должно быть не молекула, а что-то другое. Я пришёл к заключению, что CSS должен быть на первом месте. Т.е. если для стилизации body нужны несколько атомов, значит он является молекулой, что значит что теоретически я не должен присоединять к нему никаких других молекул. Это может показаться непрактичным, но в большинстве случаев поможет вам воздержаться от использования дочерних селекторов, что хорошо.

Органеллы

Как только вы разберётесь с тем какие элементы DOM являются молекулами, вы поймёте что такое органеллы. Например, обычный элемент form служит прекрасным примером органеллы. Он содержит молекулы label, input и textarea.

.login-form {
    @include label;
    @include input;
    @include textarea;
}

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

Больше абстракций

Очень часто у вас может вознкать желание объединить органеллы ещё во что-то. В таком случае добавим ещё абстракций.

Атом → Молекула → Органелла → Клетка → Ткань → Орган → Система → Организм

Только от вас зависит, как вы построите свой CSS. Пока я использовал OrganicCSS только в одном проекте, но могу сказать что он помогает внести в проект ясность. Я рассортировал разные элементы по разделам и дал классам такие названия чтобы с лёгкостью ориентироваться с чем я имею дело. Например, если у меня есть органелла с названием header, я просто изменю её название на o-header. Просматривая HTML-разметку спустя некоторое время, я сразу вижу что CSS-стили для этого элемента находятся в разделе «органеллы».

Заключение

Это было интересное путешествие. Не знаю буду ли я использовать OrganicCSS в будущем, но это не самое главное. Главное — чему я научился. Я понимал, что мне нужно изменить свой подход к процессу разработки CSS и я это сделал. Мне кажется нужно больше говорить об архитектуре CSS. Как видите, для этого есть много хороших ресурсов. Их только нужно найти, понять как они работают и что с их помощью можно сделать. Только тогда можно выбирать что использовать, а что нет. Даже больше того, когда видите целостную картину, вы получаете возможность придумать что-то, что будет больше соответствовать вашим потребностям.


Примечания

1 В английском языке слово «frame» имеет большое количество значений. Одно из них — «рамка» (напр. для картины или зеркала), его обыгрывает автор статьи в своём рассуждении о работе с фреймворком.