НовостиАдминистрированиеУправления хостингомАрхив
17 февраля 2023

Побочные эффекты в CSS

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

Так как BEM является одной из самых распространенных методологий написания CSS на сегодняшний день, многие практики основаны именно на ней. Большинство из разработчиков совершают одну и ту же ошибку: они берут части своих любимых подходов и смешивают их в надежде получить новый, более эффективный. Проблема объединения BEM с несвойственными ей методиками заключается в том, что вы можете разрушить защиту, которую создает эта методология.

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

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

Ситуация, в которой свойства, прописанные для одного элемента, непредсказуемо влияют на другой.

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

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

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

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

Побочные эффекты в CSS

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

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

В CSS побочные эффекты бывают трех основных форм:

  • Изменение базовых правил;
    • Конфликты имен;
    • Пересечение поддеревьев.

Изменение базовых правил

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

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

Проблема в том, что вам только кажется, что этот подход экономит ваше время. На практике, проекты очень часто меняются. Вам может понадобиться немного увеличить размер шрифта ваших заголовков или изменить некоторые отступы, установленные по умолчанию, а может быть, вы решите изменить оформление ссылок. Если ваши компоненты с классами .article-title, .alert-content и .footer-link завязаны на этих глобальных стилях, вы очень быстро поймете, что ошибались.

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

Конфликты имен

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

Пересечение поддеревьев

Многие разработчики знакомы с двумя видами побочных эффектов, описанными выше. Поэтому они используют наследственные комбинаторы, чтобы ограничить сферу применения классов (#homepage .header или .some-widget .title).

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

Рассмотрим следующий пример:


/*Файл article.css */
.article .title { border-bottom: 1px solid; font-size: 1.5em;}


/* Файл widget.css */.widget .title { color: gray; text-transform: uppercase;}

Хотя этот код определяет разные свойства для класса title, лежащего в объекте с классом article, и класса title, лежащего в объекте с классом widget, нет никакой гарантии, что объект с классом title окажется вложенным одновременно как в объект с классом article, так и в объект с классом widget:

<article class=\"article\">
 <h2 class=\"title\">Article Title</h2> <div class=\"content\">  <p>…</p>
  <form class=\"widget\">   <h2 class=\"title\">Widget Title</h2>   <fieldset>…</fieldset>  </form>
 </div></article> 

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

Как BEM устраняет побочные эффекты

Учитывая особенности языка и его способность к переопределению, можно утверждать, что побочные эффекты не могут быть предотвращены с помощью самого языка. Однако они могут быть предотвращены с помощью дисциплинированного и нормированного именования. И это именно то, что обеспечивает BEM.

Изменение базовых правил:

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

Конфликты имен:

В BEM каждый селектор класса это либо само название блока, либо название, которое использует имя блока в качестве префикса, а правила для каждого блока расположены в своем собственном специальном файле. Файловые системы не позволяют двум файлам иметь одинаковое имя, ОС фактически помогает предотвратить случайное дублирование. Если вы будете следовать всем правилам именования BEM, и убедитесь, что код каждого блока находится в отдельном файле, вы никогда не будете сталкиваться с конфликтом имен.

Пересечение поддеревьев:

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

Отслеживание выполнения

Строгое соблюдение правил BEM позволит предотвратить побочные эффекты в CSS, но как вы убедитесь, что эти соглашения всегда выполняются?

К счастью, в отличие от большинства методик CSS, выполнение правил BEM очень легко проверить, как в CSS, так и в HTML. Ниже приведены несколько правил, которые вы можете проверить с помощью СУБД Линтер.

В CSS:

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

В HTML:

  • Любой HTML тег с классом элемента должен быть потомком тега с классом из блока с тем же именем.
    • Любой HTML тег с классом модификаторов также должен иметь класс из блока с таким же именем.

Создание исключений

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

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

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

.Content h1:not([class]) { }.Content p:not([class]) { }.Content a:not([class]) { }

Хотя не существует достаточного объема статистических данных, которые подтверждали бы эффективность этой методики, такой подход имеет право на жизнь, так как в своей основе содержит все ту же BEM. Так как все блоки, согласно методологии, должны быть оформлены с помощью классов, использование общих стилей для элементов, которые не имеют классов, должно быть безопасным.

Другой пример это использование различных классов для поддержки устаревших браузеров. Modernizr является хорошим примером решения такой проблемы. Хотя этот метод приводит к увеличению специфичности селекторов, это должно отразиться только на правилах из того же файла, при условии, что вы выполнили все остальные требования BEM.

.GridRow { display: flex;}
/* Фолбэк для старых браузеров */.no-flexbox .GridRow { display: table;} 

Конечно, если вы в состоянии написать компоненты, которые могут управлять их состоянием с помощью модификаторов BEM, вам стоит отдать предпочтение именно этому варианту.

Опыт JavaScript

В старые недобрые времена JavaScript авторы библиотек использовали добавление методов к прототипам глобальных конструкторов, таких как объект, массив, строки и функции. На первый взгляд это казалось удобным, но разработчики быстро поняли, что это был кошмар. Если две различные библиотеки добавили метод с одним названием, но разным поведением для Array.prototype, это приведет к ошибкам, которые практически невозможно отследить.

Современные библиотеки практически никогда не меняют оригинальные прототипы. Если мы усвоили урок в JavaScript, почему мы не усвоили его в CSS?

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

Рассмотрим Bootstrap. Каждый из его плагинов JavaScript использует пространство имен и поставляется с методом .noConflict (), чтобы избежать конфликта имен. В тоже время, его CSS, не дает таких усилий, несмотря на многочисленные просьбы и простые решения, которые предлагались пользователями.

Мы не пытаемся дискредитировать Bootstrap, большинство современных CSS-фреймворков делает тоже самое. Надеемся, что CSS сообщество начнет требовать лучшего от разработчиков инструментов так же, как это делает JavaScript-сообщество.

Подводя итог

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

Хотя написание 100% предсказуемого кода практически невозможно, важно понимать, к чему могут привести нарушения тех или иных правил методологии. Если вы будете следовать строгим правилам BEM, вы сможете обновлять и добавлять код с полной уверенностью в том, что ваши изменения не будут иметь побочных эффектов. Если вы решите использовать более свободную версию BEM, вы будете подвергаться большему риску. Иногда эти риски являются управляемыми, иногда это не так. Уровень риска, который вы можете принять, обратно пропорционален размеру вашей команды.

По материалам philipwalton.com