Инкапсуляция CSS-стилей — Часть 1. Проблема

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

    В процессе этих трансформаций и миграций выяснилось, что рост трудоемкости создания и поддержания веб-интерфейса значительно опережает рост его сложности. Проблему пытались (и пытаются до сих пор) решить путем разбиения на модули, абстрагирования, инкапсуляции. С этой целю было создано большое количество JavaScript-фреймворков (Backbone, Ember, Angular), HTML-шаблонизаторов (Jade, Handlebars), систем управления зависимостями (RequireJS) и т.п.

    Наиболее сложным с этой точки зрения оказался CSS, где по дизайну языка любое свойство, объявленное в любом подключенном CSS-файле или тэге style, может повлиять на отображение любого элемента DOM-дерева.

    Формализация задачи.

    Предположим для простоты, что весь JavaScript-код заключен в модули, которые ничего не знают друг о друге и инкапсулируют в себе все необходимое им для своей работы. Модуль знает как сгенерить свое HTML-представление (назовем его блок) и куда его вставить в DOM-дереве. При этом блоки могут вкладываться друг в друга. На уровне разметки блок состоит из корневого элемента и дочерних элементов.
    Задача заключается в том, чтобы на отображение элементов любого блока можно было повлиять только намеренным изменением в HTML-представлении и соответствующих CSS-файлах и тэгах style.

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

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

    Иточники протечек стилей

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

    1. Наследование свойств

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

    К примеру,
    .outer-block{
        color: red;
    }
    

    <div class="outer-block">
        Я намеренно сделан красным
        <div class="current-block">
            Я красный из-за наследования стилей
        </div>
    </div>
    

    посмотреть на jsfiddle.net

    2. Конформизм свойств

    При отсутствии у CSS-свойства какого-либо элемента явно заданного значения используется значение по умолчанию. Если это свойство подстраивается под свойство родительского элемента (к примеру, как свойства width и height со значением по умолчанию auto) или мимикрирует (к примеру, как свойство background-color со значением по умолчанию transparent), то у конечного пользователя будет создаваться впечатление, что стили родительского элемента протекли на стили дочернего элемента.

    К примеру,
    .outer-block{
        background: red;
    }
    

    <div class="outer-block">
        Я намеренно сделан красным
        <div class="current-block">
            Я красный из-за конформизма стилей
        </div>
    </div>
    

    посмотреть на jsfiddle.net

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

    3. Каскадный беспредел

    Применение стилей к целевым элементам селекторов происходит в три этапа.
    На первом этапе из всего DOM-дерева выбираются все элементы, соответствующие селектору целевого элемента. К примеру, для селектора .current-block h3 на первом этапе будут выбраны все элементы с тэгом h3. Способа ограничить пространство выбора каким-либо участком DOM-дерева не существует.
    На втором этапе выбранные элементы фильтруются на предмет соответствия селектору путем обхода родительских элементов целевого элемента. При использовании комбинатора потомка пробел (descendant combinator) поиск соответствия может идти вплоть до корневого элемента DOM-дерева. При использовании сестринского комбинатора ~ (general sibling combinator) — до самого первого сестринского элемента.

    К примеру,
    .current-block h3 {
        background: blue;
    }
    .outer-block h3 {
        background: red;
    }
    

    <div class="outer-block">
        <h3>Я намеренно сделан красным</h3>
        <div class="current-block">
            <h3>Я красный из-за каскадного беспредела</h3>
        </div>
    </div>
    

    посмотреть на jsfiddle.net

    Единственным способом ограничить пространство поиска является использование дочернего комбинатора > (child combinator) и ближайшего сестринского комбинатора + (adjacent sibling combinator). Для этого необходимо задавать точный путь в DOM-дереве от целевого элемента к корневому элементу блока, что приводит к увеличению связанности CSS и HTML-кода.

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

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

    4. Позиционная обусловленность

    Элементы блоков могут менять свое отображение в зависимости от позиции блока в DOM-дереве при использовании в селекторах сестринских комбинаторов (+ и ~) или псевдоклассов (:first-child и т.п.).

    К примеру,

    .block {
        background: red;
    }
    .block + .block {
        background: blue;
    }
    

    <div class="block">
        <h3>Я красный, но при добавлении блока передо мной я стану синим</h3>
    </div>
    <div class="block">
        <h3>Я синий</h3>
    </div>
    

    посмотреть на jsfiddle.net

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

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

    Вторая часть статьи («Инкапсуляция CSS-стилей — Часть 2. Решения») будет посвящена анализу того, насколько текущие подходы (OOCSS, SMACSS, ACSS, BEM, CSS-препроцессоры) соответствуют идеалу, а также их классификации.

    Буду рад полезным советам и конструктивной критике.

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 14

      +3
      Помимо методологий разработки и препроцессоров было бы здорово во второй части рассмотреть Shadow DOM, в рамках которого можно реализовывать полную инкапсуляцию CSS-стилей.
        +2
        Я о нем обязательно упомяну. Но у него есть два недостатка в контексте статьи. Во-первых, он слабо распространен. Во-вторых, он полностью решает озвученную проблему :)
          0
          если что — учитывайте, что полифилл для shadow dom изолирует все только в js, не в css. я очень надеялся на это, когда увидел его, но увы, нет)
            0
            Да. Есть ShadowCSS, но он препятствует только протечкам наружу. Относительно протечек извне авторы отмечают, что для их устранения им надо было либо переопределять все свойства для всех селекторов, затрагивающих блок, либо реализовать функции CSS на JavaScript.
            Some possible ways to do this that
            were rejected due to complexity and/or performance concerns include: (1) reset
            every possible property for every possible selector for a given scope name;
            (2) re-implement css in javascript.

            Однако, вы меня убедили. Во второй части я рассмотрю и ShadowCSS тоже.
            0
            1.
            Во-первых, он слабо распространен.

            В контексте надвигающегося пришествия web-компонентов ситуация может радикально измениться. Там, как раз, shadow dom — одна из главных используемых концепций.
            2. Материал хороший, но читается с трудом. Очень сухой и формальный язык. :(
            0
            src-less iframe — тот же шедоу дом, только давно везде работает.
              0
              Поддерживаю. Пусть решение с iframe не всегда удобно использовать, т.к. оно наследует все сложности работы с ними (которые при желании решаемы), но оно позволяет инкапсулировать стили и DOM в рамках одного элемента уже сегодня.
            –2
            По поводу 3 пункта: http://jsfiddle.net/FKmef/4/
              +1
              Кто бы сомневался. О том и речь, что простое изменение в порядке объявления свойств и/или специфичности может вызывать протечку.
              0
              Может, просто надо грамотно писать стили?
                +10
                Нельзя просто взять — и грамотно написать стили.
                +5
                Скорее публикуйте вторую часть.
                  0
                  Ну есть же неймспейсы ещё. Странно, что их не упомянули. Да и поддержка вполне себе ничего: http://www.browsersupport.net/CSS/%40namespace
                    0
                    Эх, а продолжение статьи с решением так и не вышло :-(

                    Only users with full accounts can post comments. Log in, please.