CSS-live.ru

Вверх по водопаду: задачка для 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, неужели в нем баг? (Добавлено 2.02.2022: в Chrome уже тоже не работает, из актуальных браузеров остался только Safari)

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

С новым 2015-м CSS!

(Примечание от 2.02.2022: со времени написания статьи официальное определение CSS успело обновиться еще несколько раз, спецификация CSS-каскада тоже подросла на несколько уровней, критериев каскада стало больше, «перекрывающие стили» исчезли… но большая часть материала актуальна и сейчас)

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

Так вот: по этому новому официальному определению CSS остается каскадными таблицами стилей. Да, никакие БЭМ и Реакт не отменили того факта, что стили к DOM-элементам приходят из разных источников (встроенных стилей браузера, внешнего CSS страницы, атрибутов style…) и из них побеждает один сильнейший. Именно это и называется «каскадом» (а не то, что кто-то, возможно, подумал). И отвечает за него теперь не старый пыльный (но еще способный удивлять) CSS2.1, а свежий (на момент написания статьи) кандидат в рекомендации (т.е. готовый для любых практических целей!) «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 Перекрывающие стили (что бы это ни значило) с !important
  5. Обычные стили с !important (включая инлайновые, у которых наивысшая специфичность)
  6. Стили, установленные анимациями (CSS Keyframe Animations)
  7. Стили без !important из атрибута style Перекрывающие стили без !important
  8. Обычные стили без !important (включая инлайновые)
  9. Пользовательские стили без !important
  10. Браузерные стили без !important

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

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

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

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

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

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

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

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

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

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

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

31 комментарий

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

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

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

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

      1. Ну а зачем пользователям переделывать внешний вид сайта? Есть в этом что-то, не вполне нормальное, что ли — всё-таки любое изменение во внешнем виде сайта, должно быть предусмотрено разработчиками, иначе это как-то не надёжно что ли… Разработчики ХОТЯТ показать одно, а я ХОЧУ УВИДЕТЬ другое… По-моему, какое-то странное желание…

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

          1. Тут я согласен, но как раз понятно, почему таких пользователей не много, иначе бы сами пользователи стали превращаться в разработчиков. Впрочем, кто-то, может так и превращается :-)

            1. Я, к слову, на fontawesome.ru увеличил размеры иконок в несколько раз, ибо по дефолту они такие маленькие, что при подборе можно глаза сломать.
              Это к слову о том, что разработчики, возможно, чего-то не учли.

              К вашему тезису о том, что из тех, кто баловался со стилями сайтов, могут вырасти разработчики, соглашусь — я один из таких :D

        2. Что если сайт очень ползный, но разработчики в дизайнерсокм порыве наожили рукописный шрифт на пестрый фон, и прочитать «это» просто невозможно.
          Или использовали отвратительные кислотные цвета для фона.
          Есть у меня в загашнике пара сайтов где я перебил стили т.к. оригинальная картинка сильно снижала удовольствие использования, или вовсе препятствовала ему.
          Ну и кстати, такие расширения внедряют стили через js так что назвать из пользовательскими нельзя, по крайней мере с т.з. каскада

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

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

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

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

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

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

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

    html {
    height: 100%;
    }

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

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

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

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

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

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

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

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

  3. «4. Стили с !important из атрибута style» заблуждаетесь :)» 4. Important override declarations » это немного не то, и по действию перезаписывают инлайн с !important

    1. Огромное спасибо! Исправление заблуждения в таких основах — что может быть лучшим новогодним подарком:). Исправил статью.

      Кстати, я не смог найти никаких упоминаний о каких-либо реализациях этого механизма и тех самых «binding-specific casting methods», которыми можно достучаться до этого загадочного DocumentCSS. Буду благодарен за любые наводки. Давайте распутаем эту загадку до конца!

  4. новая идея:)), «псевдоклассы» не ограничены же?^_^

    html {
    background: white;
    }
    body:first-line {
    background: white;
    }
    body {
    display:inline-block;
    line-height: 1;
    }

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

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

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