Архитектура CSS

Филип Уолтон 20 февраля 2014

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

Что интересно, обычно мы не упускаем из вида эту сторону, когда дело касается других языков. Разработчик на Rails не считается хорошим только потому, что его код работает по спецификации. Это считается базовым уровнем. Конечно, он должен работать по спецификации, но его качество измеряется другим: насколько код читаемый, легко ли его изменить или расширить, достаточно ли он отделён от других частей приложения, будет ли он масштабироваться?

Эти вопросы вполне естественны при оценке кода, и CSS здесь не должен быть исключением. Сегодняшние веб-приложения становятся всё более крупными, поэтому слабая и прямолинейная архитектура CSS может сильно усложнить разработку. Пришло время отнестись к CSS так же, как мы относимся ко всем другим частям наших приложений. Это не та вещь, которая может быть оставлена на потом или сброшена со счетов как «дизайнерская проблема».

Принципы хорошей архитектуры CSS

CSS-сообществу редко удаётся прийти к договорённости, что и как нужно делать. Даже если взглянуть только на комментарии с Hacker News или на реакцию разработчиков на выход CSS Lint, станет ясно, что многие не согласны друг с другом даже по поводу самых базовых вещей, которые стоит или не стоит делать при разработке.

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

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

Предсказуемость

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

Это вряд ли необходимо маленьким сайтам, которые редко изменяются, но для больших сайтов с десятками или сотнями страниц предсказуемый CSS необходим.

Повторное использование

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

Поддержка

Когда нужно добавить или изменить новые компоненты или возможности, это не должно приводить к рефакторингу существующего CSS. Добавление компонента А на страницу не должно своим появлением ломать компонент Б.

Масштабируемость

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

Распространённые ошибки

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

Следующие примеры обобщают код, который я писал сам. Технически они верны, но каждый из них привёл к проблемам и головной боли. Несмотря на мои самые лучшие намерения и обещания себе, что на этот раз всё будет по-другому, описанные подходы снова приводили к неприятностям.

Изменение компонентов в зависимости от родителя

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

.widget {
    background:yellow;
    border:1px solid black;
    color:black;
    width:50%;
    }
#sidebar .widget {
    width:200px;
    }
body.homepage .widget {
    background:white;
    }

На первый взгляд этот код выглядит вполне безобидно, но давайте проверим его на соответствие принципам, которые мы определили выше.

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

Также этот блок плохо масштабируется и не слишком подходит для повторного использования. Что случится, когда его вид на главной понадобится на какой-нибудь другой странице? Придётся добавить новый селектор.

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

Представьте, если бы такой код был написан на любом другом языке. Вы создаёте обычное определение класса и потом, в другой части кода, берёте это определение класса и меняете его для определённого случая. Это прямо противоречит принципу открытости и закрытости для разработки ПО:

Части ПО (классы, модули, функции и т.п.) должны быть открыты для расширения, но закрыты для изменения.

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

Слишком сложные селекторы

Удивительно, но эта статья обойдёт стороной демонстрацию всей мощи CSS-селекторов и не расскажет о том, как оформить целый сайт, не используя ни единого класса или ID.

Технически это возможно, но чем больше я работаю с CSS, тем больше стараюсь держаться подальше от сложных селекторов. Чем сложнее селектор, тем больше он привязан к HTML. Опираясь на HTML-теги и комбинаторы в селекторе, вы, может быть, и получите чистый до блеска HTML, но CSS от этого станет грязным и раздутым.

#main-nav ul li ul li div { }
#content article h1:first-child { }
#sidebar > div > h3 + p { }

Все эти примеры логически верны. Первый, по-видимому, оформляет выпадающее меню, второй говорит, что заголовок в <article> должен выглядеть иначе, чем остальные элементы <h1>, а последний пример, скорее всего, добавляет отступ к первому абзацу в блоках боковой колонки.

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

Упомянутые примеры совсем не готовы для повторного использования. Поскольку селектор указывает на очень конкретную часть разметки, то как может другой компонент с иной структурой HTML использовать эти стили? Возьмём для примера первый селектор (выпадающее меню) — что, если похожее меню понадобится на другой странице, на которой не будет элемента #main-nav? Тогда вам придётся повторить все стили.

Эти селекторы также очень непредсказуемы в случае, когда меняется оригинальный HTML. Представим, что разработчик решил поменять тег <div> в третьем примере на <section> — в результате весь селектор развалится.

И наконец: поскольку эти селекторы работают, только когда HTML остаётся неизменным, они по определению не поддерживаемы и не масштабируемы.

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

Слишком общие имена классов

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

<div class="widget">
    <h3 class="title">…</h3>
    <div class="contents">
        Лорем ипсум…
        <button class="action">Жми сюда!</button>
    </div>
</div>

.widget {}
.widget .title {}
.widget .contents {}
.widget .action {}

Идея состоит в том, что классы вложенных элементов .title, .contents и .action можно безопасно оформить, не боясь, что стили повлияют на элементы с такими же классами. Это, конечно, так, но совсем не значит, что эти стили не повлияют на элементы с таким же классом, которые могут оказаться внутри.

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

Слишком общие имена классов приводят к непредсказуемому CSS.

Когда правило делает слишком много

Однажды вы создаёте визуальный компонент, который должен отстоять на 20 пикселей от верхнего левого угла блока на вашем сайте:

.widget {
    position:absolute;
    top:20px;
    left:20px;
    background-color:red;
    font-size:1.5em;
    text-transform:uppercase;
    }

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

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

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

Причина

Упомянутые выше проблемные примеры объединяет одна особенность: все они слишком полагаются на оформление CSS.

Это заявление может прозвучать странно. В конце концов, это же стили. Разве не должны они нести большую (если не всю) нагрузку для оформления? Не этого ли мы хотим?

Простой ответ на этот вопрос «да», но, как обычно, всё не так просто. Разделять содержимое и представление хорошо, но содержимое не отделяется от представления только потому, что ваш CSS отделён от HTML. Скажем иначе: вы не достигнете цели, если просто уберёте всё представление из HTML, но при этом для работы вашего CSS потребуется подробнейшее знание структуры HTML.

Более того, HTML редко состоит только из содержимого, почти всегда в нём есть и структура. И часто эта структура состоит из контейнеров, единственной задачей которых является объединение некоторой группы элементов для работы CSS. Даже без презентационных классов такие структуры добавляют представление в HTML-код. Но действительно ли это смешивает содержимое с представлением?

Я уверен, что на текущем уровне развития HTML и CSS необходимо и зачастую разумно использовать HTML и CSS в качестве единого слоя представления. Слой содержимого в таком случае может быть отделён с помощью шаблонизаторов или подключаемых фрагментов (как partials в Ruby).

Решение

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

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

Например, в CSS с помощью класса .button описан компонент кнопки. Если в HTML понадобится конкретный элемент, который выглядит как кнопка, то нужно использовать этот класс. Если в другой ситуации этой кнопке понадобится выглядеть иначе (скажем, больше и в полную ширину), в CSS нужно определить этот новый вид, также с помощью класса, и тогда его можно будет добавить в HTML для достижения нужного вида.

CSS определяет, как выглядит ваш компонент, а HTML применяет этот вид к элементам на странице. Чем меньше CSS «знает» про структуру HTML, тем лучше.

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

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

Несмотря на то, что эти сомнения справедливы, они могут привести к ошибочным выводам. Вывод такой: либо вы используете родительский селектор в CSS, либо вам придётся написать этот HTML-класс тысячу раз руками. Но, очевидно, есть и другие варианты. Уровень абстракции компонентов в Rails и других фреймворках легко позволяет явно описывать внешний вид прямо в HTML без необходимости снова и снова писать один и тот же класс.

Правильный подход

Совершив описанные выше ошибки снова и снова, заплатив позднее за их последствия, я пришёл к некоторым принципам. Не претендуя не всеобъемлемость, мой опыт показывает, что эти принципы помогут достичь хорошей архитектуры CSS.

Будьте точнее

Лучший способ добиться того, чтобы ваши селекторы не влияли на ненужные элементы — это не дать им такой возможности. Со временем вы можете обнаружить, что селектор вроде #main-nav ul li ul li div применяется уже совсем не к тому элементу из-за изменившейся разметки. Класс .subnav, напротив, имеет очень мало шансов быть случайно применённым не к тому элементу. Назначать классы прямо элементам, которые вы хотите оформить — лучший способ сохранить ваш CSS предсказуемым.

/* Граната */
#main-nav ul li ul { }

/* Снайперская винтовка */
.subnav { }

Если взять два примера выше, то первый больше напоминает гранату, а второй снайперскую винтовку. Граната может прекрасно сработать сегодня, но кто может гарантировать, что завтра в радиус поражения не попадёт невинный гражданский?

Разделяйте ответственность

Я уже упоминал, что хорошо организованный компонентный слой может уменьшить зависимость CSS от структуры HTML. Помимо этого, сами компоненты должны быть модульными. Компоненты должны знать только, как оформить себя, и делать это хорошо. Но они совсем не должны отвечать за раскладку, позиционирование или делать слишком много предположений насчёт того, в каком окружении они будут располагаться.

В общем случае компоненты должны определять свой внешний вид, а не раскладку и позиционирование. Будьте осторожны, когда видите свойства вроде background, color и font в одном правиле с position, width, height и margin.

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

Задайте пространство имён

Мы уже выяснили, почему родительские селекторы не всегда на 100% эффективны для ограничения действия и пересечения стилей. Гораздо лучший подход — добавить пространство имён к самим классам. Если элемент является частью визуального компонента, то каждый из классов его вложенных элементов должен использовать имя класса базового компонента в качестве пространства имён.

/* Высокий риск пересечения имён классов */
.widget { }
.widget .title { }

/* Низкий риск пересечения имён классов */
.widget { }
.widget-title { }

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

Расширяйте компоненты модификаторами классов

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

/* Плохо */
.widget { }
#sidebar .widget { }

/* Хорошо */
.widget { }
.widget-sidebar { }

Мы уже рассмотрели все недостатки изменения компонентов на основе их родительских элементов, но для закрепления: модификатор класса может использоваться где угодно, переопределение на основе вложенности — только в конкретной ситуации. Модификатор класса также может использоваться сколько угодно раз. Наконец, модификаторы класса очень ясно выражают намерения разработчика прямо в HTML. Классы, основанные на вложенности, напротив, полностью скрыты для разработчика, который смотрит только на HTML, что сильно увеличивает вероятность, что они от него ускользнут.

Организуйте CSS в логическую структуру

Джонатан Снук в своей замечательной книге SMACSS убеждает разделять CSS-правила на четыре отдельные категории: базовые, раскладку, модули и состояния. Базовые состоят из сбросов и умолчаний для элементов. Раскладка — для расположения глобальных элементов сайта, а также общих вспомогательных вещей вроде модульных сеток. Модули — это визуальные элементы для повторного использования и стили состояний для оформления того, что можно включить или выключить с помощью JavaScript.

В системе SMACSS модули (эквивалентные тому, что я называю компонентами) составляют большинство от всех правил в CSS, поэтому я часто прихожу к необходимости разбить их ещё больше, отделив абстрактные шаблоны.

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

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

Обычно я не использую классы шаблонов прямо в HTML, если для этого нет веских причин. Вместо этого я использую препроцессоры для включения стилей шаблона в описание компонента. Далее мы обсудим подробнее этот подход и мои причины для его использования.

Используйте классы строго для оформления

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

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

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

Тем не менее, устоявшиеся договорённости помогут полностью избежать этой проблемы. Когда вы видите класс в HTML, вы должны быть способны чётко сказать, для чего он предназначен. Мой совет — добавлять префикс ко всем неоформительским классам. Я использую .js- для JavaScript и .supports- для классов Modernizr. Все классы без префикса — для оформления и только для оформления.

Это позволяет искать неиспользуемые классы и удалять их из HTML простым поиском по папке со стилями. Вы можете даже автоматизировать этот процесс с помощью JavaScript, сравнивая классы в HTML с классами в объекте document.styleSheets. Классы, которых нет в document.styleSheets, можно безопасно удалять.

Итак, полезно разделять содержимое и представление, и столь же важно отделять представление от поведения. Использование классов с оформлением в качестве основы для JavaScript так сильно связывает CSS с JavaScript, что становится сложно или даже невозможно обновить оформление некоторых элементов, не сломав их поведение.

Логическая структура в именах классов

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

Николас Галлахер недавно написал о своем решении этой проблемы, которое я тоже (с некоторыми изменениями) с большим успехом применил. Чтобы проиллюстрировать необходимость в договорённости об именовании, рассмотрим следующий пример:

/* Компонент */
.button-group { }

/* Модификатор компонента (изменяющий .button) */
.button-primary { }

/* Вложенный объект (находится внутри .button) */
.button-icon { }

/* Это класс компонента или раскладки? */
.header { }

Глядя на классы выше, нельзя сказать, к какому типу правил они применяются. Это не только добавляет путаницы при разработке, но и усложняет автоматическое тестирование вашего HTML и CSS. Структурированная договорённость об именовании позволяет с первого взгляда на имя класса точно понять, в каких отношениях он состоит с другими классами и в каком месте HTML должен располагаться — что, в отличие от обычной практики, упрощает именование и делает возможным тестирование.

/* Правила шаблонов (с использованием Sass) */
%template-name
%template-name--modifier-name
%template-name__sub-object
%template-name__sub-object--modifier-name

/* Правила компонентов */
.component-name
.component-name--modifier-name
.component-name__sub-object
.component-name__sub-object--modifier-name

/* Правила для раскладки */
.l-layout-method
.grid

/* Правила состояний */
.is-state-type

/* Классы для JavaScript без оформления */
.js-action-name

Переработанный первый пример:

/* Компонент */
.button-group { }

/* Модификатор компонента (изменяющий .button) */
.button--primary { }

/* Вложенный объект (находится внутри .button) */
.button__icon { }

/* Класс раскладки */
.l-header { }

Инструменты

Поддержка эффективной и хорошо организованной архитектуры CSS может быть очень сложной, особенно в больших командах. Несколько плохих правил здесь, несколько там — и снежный ком неразрешимых проблем уже покатился. Когда CSS вашего приложения уже дошел до войн за специфичность и сомнительных побед за счёт !important, может оказаться, что пути назад уже нет, разве что переписать всё с нуля.

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

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

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

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

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

  • Никогда не вкладывайте правила только для организации кода. Вкладывайте только тогда, когда это нужно в CSS на выходе.
  • Никогда не используйте примеси (mixin), если не передаёте аргумент. Примеси без аргументов гораздо лучше использовать в качестве шаблонов, которые можно расширить.
  • Никогда не используйте @extend для селектора, который не является одиночным классом. Это не имеет смысла с точки зрения дизайна и раздувает скомпилированный CSS.
  • Никогда не используйте @extend для компонентов интерфейса в модификаторе компонента, иначе вы нарушаете цепь наследования (подробнее об этом дальше).

Лучшая часть препроцессоров — это функции, вроде @extend и %placeholder. Обе они позволяют просто управлять абстракциями в CSS, при этом не раздувая код и обходясь без добавления в HTML огромного количества базовых классов, с которыми потом очень сложно управиться.

@extend нужно использовать с осторожностью, потому что иногда эти классы будут нужны в HTML. Например, когда вы впервые узнаёте про @extend, то его сразу хочется применить ко всем классам-модификаторам как-то так:

.button {
    /* Стили кнопки */
    }
/* Плохо */
.button--primary {
    @extend .button;
    /* Стили модификатора */
    }

Проблема с этим подходом в том, что вы теряете цепь наследования в HTML. Теперь довольно сложно выбрать все экземпляры кнопки с помощью JavaScript.

Как правило, я не расширяю компоненты интерфейса или что-то другое, тип чего мне может пригодиться знать в будущем. Для этого случая как раз и придуманы компоненты, и это ещё один способ, помогающий отличать шаблоны от компонентов. Шаблон — это нечто, к чему вы никогда не обратитесь в логике вашего приложения, и поэтому его можно безопасно расширить с помощью препроцессора.

Как это может выглядеть на примере модального окна, о котором шла речь выше:

.modal {
    @extend %dialog;
    @extend %drop-shadow;
    @extend %statically-centered;
    /* Другие стили окна */
    }
.modal__close {
    @extend %dialog__close;
    /* Другие стили кнопки закрытия */
    }
.modal__header {
    @extend %background-gradient;
    /* Другие стили заголовка окна */
    }

CSS Lint

Николь Салливан и Николас Закас создали CSS Lint, инструмент для контроля за качеством кода, который помогает разработчикам находить плохие подходы в их CSS. На сайте он описан так:

CSS Lint указывает на проблемы в вашем CSS-коде. Он делает базовую проверку синтаксиса, а также применяет к коду набор правил, которые позволяют выделить в нём проблемные подходы или признаки неэффективности. Все правила расширяемы, потому вы легко можете написать свои или пропустить ненужные.

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

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

Основываясь на договорённостях, которые я предложил выше, очень легко написать правила для определения неверных подходов. Вот несколько правил, которые я использую:

  • Не допускайте ID в своих селекторах.
  • Не используйте несемантические типы селекторов (div и span, напр.) для любого неодиночного правила.
  • Не используйте больше двух комбинаторов в селекторе.
  • Не допускайте использование классов, начинающихся с .js-.
  • Внимание, если раскладка и позиционирование применяются в правилах для элементов без префикса .l- в названии класса.
  • Внимание, если класс, определённый сам по себе, позднее переопределяется как дочерний или иначе.

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

HTML Inspector

Ранее я предположил, что будет довольно просто пройтись по вашим HTML-классам и всем подключенным стилям и выдать предупреждение, если класс был использован в HTML, но не определён ни в одном файле стилей. Сейчас я разрабатываю инструмент, который называется HTML Inspector, чтобы сделать этот процесс проще.

HTML Inspector проходит по вашему HTML и (почти как CSS Lint) позволяет вам написать собственные правила, которые вызывают ошибки или предупреждения, когда какие-то договорённости нарушаются. Сейчас я использую следующие правила:

  • Внимание, если один и тот же ID используется на странице два раза.
  • Не используйте классы, не упомянутые ни в одном из файлов стилей или передайте список разрешённых (с префиксом .js-, напр.)
  • Классы модификаторов не должны использоваться без их базовых классов.
  • Классы вложенных объектов не должны использоваться в отсутствие родительского базового класса.
  • Простые элементы <div> и <span> не должны использоваться в HTML без назначенных классов.

Заключение

CSS — это не просто средство визуального дизайна. Не стоит забывать о правильных подходах к программированию только потому, что вы пишете CSS. Подходы вроде ООП, DRY, принцип открытости и закрытости, разделение ответственности и т.п. также применимы к CSS.

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

Перевод оригинальной записи «CSS Architecture» Филипа Уолтона (Philip Walton). Переведено и опубликовано с разрешения автора.

Перевод Вадима Макеева, редактура Ольги Алексашенко.

Теги: , ,

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

  1. Сергей Васильев 20 февраля 2014 в 13:27

    В подписи ссылка на оригинальную статью неверная, должна быть эта — http://philipwalton.com/articles/css-architecture/.

  2. Вадим Макеев 20 февраля 2014 в 13:32

    Сергей, спасибо, поправили.

  3. Виктор Смирнов 20 февраля 2014 в 13:36

    Да уж... Сколько людей, столько и мнений

  4. Сергей Гурко 20 февраля 2014 в 14:45

    Действительно, модульный подход к написанию стилей и разметки намного облегчает жизнь при верстке крупных проектов, таких как интернет-магазины, и когда версткой занимается команда более двух человек.
    Только полностью использовать тот или иной подход (SMACSS, БЭМ) не получается. Выходит такой себе симбиоз, что в принципе тоже не плохо.

  5. Артем Lp 25 февраля 2014 в 15:09

    Думаю, что со временем в SMACSS и БЭМ отпадет все лишнее и мы получим отличный и удобный подход.

  6. Ян 25 февраля 2014 в 21:21

    Отличная статья. Прекрасная методология, вобравшая в себя всё лучшее, что есть на данное время.

    Перевод очень качественный, было приятно читать. Побольше бы таких статей!

  7. Дмитрий 11 апреля 2014 в 19:12

    Пример из статьи

    .widget {
        background:yellow;
        border:1px solid black;
        color:black;
        width:50%;
        }
    #sidebar .widget {
        width:200px;
        }
    body.homepage .widget {
        background:white;
        }
    

    Я так и не понял, какой выход из этой ситуации. Я например так часто делаю. Например, задается какой то общий вид для кнопки на всем сайте

    .button {
      color: #fff;
      backgorund: #ff0000;
      width: 100px;
    }
    

    Но если код в форме, то width: 200px и следовательно

    form .button {
      width: 200px;
    }
    

    Но вы говорите что это плохо ? А какова альтернатива ?

  8. Надя 24 апреля 2014 в 12:17

    Дмитрий, я в таких случаях пишу так:
    .button {
    color:#fff;
    background:#f00;
    }
    .w_100 {
    width:100px;
    }
    .w_200 {
    width:200px;
    }

  9. Вова 16 мая 2014 в 18:28

    Надя, и зачем это нужно? А если высота должны быть 200px, добавите h_200? А цвет розовый? Не лучше уже в style сразу закинуть, чем городить непонятные классы?

  10. Dmitry 26 мая 2014 в 2:07

    В дополнении к ".js-" и ".l-", хочу добавить, что мы в свое практике используем ".qa-" для обозначения классов используемых исключительно для автоматического тестирования (функционального в частности). В статье это упоминалось, но примера не было.

  11. Юлия 3 июля 2014 в 14:05

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

    /* Плохо */
    .widget { }
    #sidebar .widget { }
    
    /* Хорошо */
    .widget { }
    .widget-sidebar { }
  12. talgautb 6 октября 2014 в 22:00
    /* Высокий риск пересечения имён классов */
    .widget { }
    .widget .title { }
    
    

    Почему высокий риск пересечения имен классов ??

  13. Евгений 4 декабря 2014 в 1:02

    Опечаточка «Тогда вам придётся повторить всё стили.»

  14. Евгений 4 декабря 2014 в 1:53

    Почему высокий риск пересечения имен классов ??

    talgautb, высокий риск пересечения классов в состоит том, что класс .title может использоваться в качестве отдельного блока\виджета где-то еще помимо виджета widget и произойдет то самое пересечение, когда один селектор указывается несколько раз в CSS. В статье и в комментариях уже отвечено на этот вопрос и предложен вариант именования дочерних элементов виджета, путем расширения имени дочернего элемента именем самого виджета.

    Хотя статья — это очередная эволюция к БЭМу. Удивительно наблюдать, как многие авторы эволюционируют в своих рассуждениях к БЭМ-подходу :)

  15. V-Lad 16 января 2015 в 17:22

    Начал писать CSS с применением типовых классов, например:
    .footnote {}
    .title-4 {}
    .v-aln_tl {vertical-align:top; text-align:left;}
    .w33p {width:33%;}
    .head-pad {padding: 5px 10px;}
    Подобные классы я применял к разным элементам HTML на любой странице сайта. Однако вскоре контроль за использованием таких классов стал не возможен.
    Во-первых, при изменении свойств конкретного селектора приходилось просматривать соотнесенные с этим классом элементы HTML на многих страницах.
    Во-вторых, некоторые селекторы так широко применялись на страницах сайта, что их поиск стал затруднительным.
    Тогда я столкнулся с необходимостью создания карты используемых стилей. Эта карта должна была указывать, какие селекторы на каких страницах сайта применены.
    И уже в процессе работы над картой стилей я выработал для себя следующие принципы:
    1. Для каждого элемента HTML должен быть определен собственный набор стилей (через класс). Копии одного элемента в рамках шаблона веб-страницы являются одним элементом HTML. Похожие элементы в разных шаблонах являются разными элементами.
    2. Общая структура CSS должна отражать шаблонную организацию страниц сайта, а комментарии должны группировать классы в соответствии со структурой шаблонов. Таким образом CSS должна точно указывать место применения селектора в коде HTML.
    3. CSS должна гарантировать возможность автономного редактирования стилей для каждого отдельного элемента HTML.
    4. При редактировании свойств конкретного селектора CSS должна максимально точно указывать на элемент HTML, использующий данный селектор.
    5. Каждый самостоятельный (не входящий в группу) элемент HTML необходимо описывать полным набором стилевых правил для получения достаточного контроля за оформлением данного элемента.
    В результате, я отказался от создания карты стилей, так как структура самой CSS стала своеобразной картой, по которой легко найти в HTML-коде страниц сайта любое стилевое правило. Теперь, редактируюя CSS, я точно представляю, где и в каком элементе HTML произойдут изменения. И эти изменения не затронут ничего лишнего на странице или в шаблонах, кроме указанного в селекторе объекта.
    Для наглядности привожу фрагмент того, что у меня получилось:

    
    /* B1.Help */
    table.hlptable {border:0px; padding:0px; border-collapse:collapse; border-spacing:0px; width:100%; margin-top:25px;}
    /* td */
    .hlp_top {background-color:#FFE1C4; text-align:left; vertical-align:top;}
    .hlp_left {padding-top:10px; text-align:left; vertical-align:top; width:18%;}
    .hlpmain {padding:10px 10px; text-align:left; width:76%; background-color:#000000; opacity:.75; filter:alpha(opacity=75); -moz-opacity:0.75;}
    .hlpsides {width:3%;}
    /* span */
    .hlplogo {font-family:Arial,Helvetica; font-size:14pt; background-color:#800000; color:#FFE1C4; font-weight:bold; padding:0px 5px;}
    .hlptitle {font-family:Arial,Helvetica; font-size:14pt; color:#800000; font-weight:bold; padding-left:5px;}
    /* ul */
    .hlpconts {list-style-type:none; font-family:verdana,arial,helvetica; font-size:10pt; color:#FFCC99; font-weight:bold; padding-left:0px; margin-right:5px; margin-left:0px; line-height:200%;}
    .hlpconts a:link {text-decoration:none; color:#FF9900;}
    .hlpconts a:visited {text-decoration:underline; color:#FF9900;}
    .hlpconts a:active {text-decoration:underline; color:#FF9900;}
    .hlpconts a:hover {text-decoration:underline; color:#FFFF00;}
    /* div */
    .hlptxt {font-family:verdana,arial,helvetica; font-size:11pt; color:#FFCC99;}
    .hlptxt a:link {text-decoration:underline; color:#FF9966;}
    .hlptxt a:visited {text-decoration:underline; color:#FF9900;}
    .hlptxt a:active {text-decoration:underline; color:#FF9900;}
    .hlptxt a:hover {text-decoration:none; color:#FFFF99;}
    /* p */
    .hlpfootn {font-family:Arial,Helvetica; font-size:11pt; color:#FFCC99; font-style:italic;}
    /* END Help */
    
    
  16. fake 16 февраля 2015 в 16:43

    CSS создан для каскадов, почему нельзя использовать каскады для переопределения стилей? Это же киллер-фича всей технологии. Да, это усложняет код, но как же усложнится код, если отказаться от каскадов? Упрощать CSS путем усложнения HTML атрибутов – сомнительный путь.

  17. Дима 11 февраля 2016 в 11:43

    Всем Привет, очень хорошая статья. Архитектура(модель) в CSS очень важна. Я сам использую архитектуру Atomic CSS которая в принципе выдерживает все хорошие качества и тон при написании CSS кода.
    Хотел посоветовать инструменты которые сам использую.
    1. OrnaJS - js библиотека которая расширяет возможности inline style верстки, позволяет делать события и наследование, чего так не хватает в стандартном атрибуте style.
    Круто для landing page, там где вам вовсе не нужен отдельный CSS file.
    http://ornaorg.github.io

    2. Orna4Node - крутая программа которая на основе написанных вами имен атомарный классов сама создает CSS file. С ней у вас не будет не нужных классов она все обрабатывает и чистит за вас.
    http://ornaorg.github.io/Orna4Node.html

    Хорошие ресурсы по архитектуре Atomic CSS, Yahoo одобряет:
    http://ornaorg.github.io/AtomicCSS.html
    http://acss.io

Перейти к началу