Вверх по водопаду: задачка для CSS-супергероя

Настоящего героя, владеющего суперсилой CSS-спецификаций, трудно сбить с толку. Но в жизни, как и в супергеройском кино, попадаются задачи, на первый взгляд неразрешимые. Например: есть страничка, которую кто-то нехороший собирается через 5 секунд погрузить во тьму, установив ей style="background:#000 !important" на body. Нужно «спасти» страничку, сохранив ее в читаемом виде, с белым фоном… ну хотя бы минут десять. Хотя бы в новейших браузерах.

Ну и поскольку это задачка для CSS-супергероя, то единственное, чего можно касаться — это CSS странички. Ни разметку, ни «злодейский» скрипт трогать нельзя. И чтобы не было слишком легко, псевдоэлементы использовать тоже нельзя.

See the Pen MaxvxJ by Ilya Streltsyn (@SelenIT) on CodePen.

Инлайновый стиль, да еще и с !important. Абсолютное оружие. В известной визуализации «SpeciFISHity» авторства Эстель Вайл (Стандартисты) — атомная бомба. Казалось бы, что можно ему противопоставить?

И всё же эта задача решаема. И даже более чем одним способом. Хотя, как обычно, один из способов — неправильный:

See the Pen dYrVxP by Ilya Streltsyn (@SelenIT) on CodePen.

Как? Как обычный стиль, для самого слабого селектора (тега), смог «побить» инлайновый !important? И почему этот способ работает во всех браузерах, кроме Firefox, неужели в нем баг?

Постоянные читатели нашего сайта уже догадываются, что на этом месте надо открыть спецификацию:). Осталось выяснить, какую именно.

С новым 2015-м CSS!

Вдруг кто пропустил: с октября 2015 года мы живем по новому официальному определению CSS! Оно же «снимок CSS на 2015 год», в постоянном адресе документа красуется гордое сочетание «CSS-2015». Приживется ли оно в качестве официального названия для современного CSS, как «ES2015» для новой версии JavaScript? Трудно сказать, пока что и «ES2015» с трудом отвоевывает позиции у привычного «ES6», а уж многострадальный CSS и вовсе не может избавиться от бессмысленных маркетинговых штампов вроде «CSS4». Лично мне «CSS-2015» нравится гораздо больше — солидно, современно и неизбито.

Так вот: по этому новому официальному определению CSS остается каскадными таблицами стилей. Да, никакие БЭМ и Реакт не отменили того факта, что стили к DOM-элементам приходят из разных источников (встроенных стилей браузера, внешнего CSS страницы, атрибутов style…) и из них побеждает один сильнейший. Именно это и называется «каскадом» (а не то, что кто-то, возможно, подумал). И отвечает за него теперь не старый пыльный (но еще способный удивлять) CSS2.1, а довольно свежий (апрельский уже майский-2016) кандидат в рекомендации (т.е. готовый для любых практических целей!) «CSS-модуль каскада и наследования 3 уровня»..

В котором вообще немало интересного.

Во-первых, к значению inherit, позволявшему явно воспользоваться свойством родителя, добавилась еще пара подобных глобальных значений — initial (сбросить значение свойства к начальному, например, для display это будет inline, а для widthauto) и unset (отменить установку свойства для конкретного элемента, как если бы оно не задавалось вообще: для наследуемых свойств эффект будет тот же, что от inherit, а для ненаследуемых — как от initial). Управлять влиянием окружения на стили элемента стало гораздо проще. А благодаря особому свойству all (all:initial) можно отменить наследование всех свойств сразу, сделав элемент (например, виджет) практически полностью независимым!

Во-вторых, «жизнь» каждого отдельного CSS-значения на пути от строчки в файле до экрана стала гораздо «насыщеннее». Если раньше она формально состояла из 4 стадий — указанное, вычисленное, использованное и фактическое значения — то теперь перед ними добавились еще две: объявленное и каскадное.

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

Новый порядок важности

Вот как распределяются источники значений CSS-свойств по приоритетности на сегодня (чем выше — тем приоритетнее):

  1. Стили, установленные переходами (CSS Transitions)
  2. Браузерные стили с !important
  3. Пользовательские стили с !important
  4. Стили с !important из атрибута style
  5. Обычные стили с !important
  6. Стили, установленные анимациями (CSS Keyframe Animations)
  7. Стили без !important из атрибута style
  8. Обычные стили без !important
  9. Пользовательские стили без !important
  10. Браузерные стили без !important

Оказывается, !important-стили из атрибута style — вовсе не последняя инстанция. У них целых три «более крутых» соперника. А вот анимаций-то среди них и нет. Так что не надо жаловаться в багзиллу на «баг Firefox, из-за которого не работает предыдущий пример»: там как раз Firefox единственный подчинился спецификации, а баг в остальных браузерах! Бывает и такое.

Но два из трех «претендентов» мало чем могут нам помочь. К браузерным стилям у нас доступа нет. А «пользовательские стили» — вообще такой загадочный зверь, которого мало кто видел (старожилы расскажут, конечно, что в былой Опере, мол, попадались во-от такие… но на то и легенды:). Не зря многие авторитетные разработчики разочаровались в их идее.

И лишь переходы на что-то годятся. Действительно, пока переход не закончен, новое значение не применится, откуда бы оно ни взялось. Если сделать переход очень долгим, то и новое значение не появится очень долго. А если у перехода еще и задержка… стоп, это же оно — решение нашей невозможной задачи стандартными средствами!

See the Pen meoBwb by Ilya Streltsyn (@SelenIT) on CodePen.

Мораль (вместо заключения)

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

Мой пример — не руководство к действию, а иллюстрация работы действующего стандарта. Вроде бы простого и всем известного… но, видите, даже браузеры пока трактуют его по-разному. И это простейший случай. А если бы это был чужой сверхдинамичный интерфейс, где были бы задействованы и переходы, и CSS-анимация, и перспективный Web Animations API?

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

Кстати, по-моему, в недавней статье, что в CSS-каскаде всё было бы просто и очевидно, если бы не специфичность, Филип Уолтон слегка противоречит собственным отличным советам по предыдущей ссылке. Мы только что видели, что и помимо специфичности там хватает сюрпризов. Но у всего есть своя логика и причина. И, на мой взгляд, понимать эти причины и эту логику полезнее, чем прятать от них глаза в песок (чего даже страусы, как выяснилось, не делают:).

Так что читайте спецификации! Хотя бы актуальные, что перечислены в официальном определении. Не слушайте тех, кто называет их скучными — мы-то знаем, что в них скрывается то детектив, то мистический триллер… а то и динамичный боевик (как вот с этой «битвой при водопаде»:). И да пребудет с вами сила знания CSS!

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

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

  1. Мимо проходил

    А «пользовательские стили» — вообще такой загадочный зверь, которого мало кто видел

    Stylish в FireFox разве не оно? Ничего загадочного там нет, обыденность.

    1. SelenIT (Автор записи)

      Это была шутка:). Но с долей шутки: всё-таки обыденностью Stylish и все его аналоги (для всех браузеров) стали лишь для гиков. А много ли обычных пользователей хотя бы слыхали о нем? По ссылке в твиттере занятная местами дискуссия, почему эта штука «не взлетела» массово.

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

    2. Сергей

      А разве стили Stylish будут _пользовательскими_? Я не изучал вопрос, но когда встретил это расширение, подумал, что оно просто js-ом подпихивает свои стили.

      1. SelenIT (Автор записи)

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

  2. Юлия

    Как вариант:
    background-image: linear-gradient(to right, rgb(255,255,255), rgb(255,255,255));

    1. SelenIT (Автор записи)

      Скрипт перезаписывает сокращенное свойство background, т.е. обнуляет background-image. Так что идея неплохая, но не поможет.

      Формально через box-shadow inset еще можно выкрутиться. Но вообще главная задача была показать, что каскад по-прежнему важен и даже !important не всесилен. И что это нужно учитывать, особенно в случае анимаций и т.п.

      1. Юлия

        ок =) Я поняла основной смысл, как перебить инлайн стиль с !important. Без скрипта. А так интересная задачка.

        1. SelenIT (Автор записи)

          Основной смысл — даже не в том, «как» (надеюсь, в реальности такая задача действительно не встретится), а что такое в принципе бывает. И отчего. Думаю, это знание может когда-нибудь помочь в отладке «почему что-то не работает» и т.п.

  3. Дима

    Всем привет! Безусловно, статья была про иллюстрацию стандартов (и за нее огромное спасибо!). Старожилам и так известно, что и ФФ и Опера в свое время приходилось имплементировать поведение в разрез стандартам по причине несовместимости с доминирующим Webkit.
    Вот еще один вариант решения (но без игры на поле специфичности):

    html {
    height: 100%;
    }

    body {
    height: 100%;
    margin: 0;
    box-shadow: inset 1000px 1000px #fff;
    }

  4. Виктор

    html {
    background: white;
    }
    body {
    width: 0px;
    white-space: nowrap;
    }
    как вариант :))

    1. Виктор

      или еще лучше :)
      html {
      background: white;
      }
      body {
      height: 0;
      }

      1. SelenIT (Автор записи)

        Красиво! Прямо, что называется, «out of the box thinking»:). Но что, если бы у этих body и html были какие-нибудь хитрые рамки, например?

        1. Алексей

          Это например?

          1. SelenIT (Автор записи)

            Я имел в виду, что не всегда можно визуально «скукожить» body до нуля и положиться на overflow: visible. Возможно, пример и впрямь неудачный, лучше было бы сослаться на какие-нибудь JS-плагины, отсчитывающие положение от краев body… Хотя, повторюсь, решение и впрямь остроумное!

            1. Алексей

              Я имел в виду, что не всегда можно визуально «скукожить» body до нуля и положиться на overflow: visible.

              Догадываюсь что не всегда, но примеры навскидку в голову не приходят. Не могли бы подкинуть?

              Хотя, повторюсь, решение и впрямь остроумное!

              Согласен, простое и элегантное!

              1. SelenIT (Автор записи)

                Бывают дизайны, в которых border/padding от body играет существенную роль. А добавлять новую обертку уже нельзя по условию:)

                1. Алексей

                  Интересно…

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

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

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

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