От автора: в сегодняшнем типичном сценарии, где средний веб-сайт отправляет 500 КБ сжатого JavaScript и 1,5 МБ изображений, работающих на среднечастотном устройстве Android через 3G с 400-секундным временем в оба конца, производительность селектора CSS является наименьшей из наших проблем.
Тем не менее, есть что сказать об этой теме, особенно для отсечения некоторых мифов и легенд, окружающих их. Так что давайте разберемся, как делается оптимизация CSS.
Основы CSS-анализа
Во-первых, чтобы перейти на ту же страницу — эта статья не касается производительности свойств и значений CSS. Здесь мы говорим о стоимости исполнения самих селекторов. Я сосредоточусь на движке рендеринга Blink, в частности на Chrome 62.
Селекторы можно разделить на несколько групп и (грубо) отсортировать от наименее дорогостоящего:
Означает ли это, что вы должны использовать только идентификаторы и классы? Ну не совсем. Это зависит. Во-первых, давайте рассмотрим, как браузеры интерпретируют селектор CSS.
Браузеры читают CSS справа налево. Самый правый селектор в составном селекторе известен как селектор key. Так, например, в #id .class > ul a, селектор key является a. Браузер сначала соответствует всем клавишам выбора. В этом случае он находит все элементы на странице, которые соответствуют селектору. Затем он находит все элементы ul на странице и фильтрует a до тех же элементов, которые являются потомками ul, и так далее, пока не достигнет крайнего левого селектора.
Поэтому чем короче селектор, тем лучше. Если возможно, убедитесь, что селектор key является классом или идентификатором, чтобы сохранить его быстрым и конкретным.
Измерение производительности
Бен Фрейн создал серию тестов для измерения производительности селектора еще в 2014 году. Тест состоял из огромного DOM, состоящего из 1000 идентичных элементов, и измерения скорости, необходимой для анализа различных селекторов, от идентификаторов до некоторых серьезно сложных и длинных селекторов соединений. Он обнаружил, что дельта между самым медленным и быстрым селектором составляла ~ 15 мс.
Однако это было еще в 2014 году. С тех пор многое изменилось, и правила запоминания почти бесполезны в постоянно меняющемся ландшафте браузера. Всегда помните, чтобы делать свои собственные тесты, особенно когда речь идет о производительности.
Я пошел делать свои собственные тесты, и для этого я использовал тест Пола Льюиса, упомянутый в комментарии Пола Ирсиля, выражая озабоченность полезными, но запутанными «селекторами количества»:
Эти селекторы являются одними из самых медленных. ~ 500 медленнее, чем что-то дикое, как «div.box:not(:empty):last-of-type .title». Страница тестирования http://jsbin.com/gozula/1/quiet
Тест немного подскочил, до 50000 элементов, и вы можете проверить его сами. Я сделал в среднем 10 прогонов на моем MacBook Pro 2014, и я получил следующее:
Разумеется, результаты будут зависеть от того, используете ли вы querySelector или querySelectorAll и количество соответствующих узлов на странице, но querySelectorAll приближается к реальному варианту использования CSS, который нацелен на все соответствующие элементы.
Даже в таком крайнем случае, когда 50000 элементов соответствуют друг другу, и используя некоторые действительно безумные селекторы, такие как последний, мы обнаруживаем, что самый медленный — ~ 20 мс, а самый быстрый — простой класс на ~ 3,5 мс. Не совсем такая разница. В реалистичном, более «ручном» DOM, насчитывающем около 1000-5000 узлов, вы можете ожидать, что эти результаты упадут в 10 раз, доведя их до нескольких миллисекундных парсинга.
Что мы видим из этого теста, так это то, что не стоит беспокоиться о производительности селектора CSS. Просто не переусердствуйте с псевдоселекторами и действительно длинными селекторами. Мы также видим, как Blink улучшился за последние два года. Вместо указанного ~ 500x замедления для «селектора количества» ( .box:nth-last-child(n+6) ~ div ) по сравнению с «селектором безумия» ( .box:not(:empty):last-of-type .title ), мы видим только замедление ~ 1.5x. Это потрясающее улучшение, и мы можем только ожидать, что браузеры улучшатся, делая работу селектора CSS еще менее эффективной.
Тем не менее, вы должны придерживаться классов, когда это возможно, и принять какое-то соглашение об именах, такое как BEM, SMACSS или OOCSS, поскольку оно не только поможет производительности вашего веб-сайта, но и значительно поможет в ремонте кода. Переопределенные составные селекторы, особенно при использовании с тегами и универсальными селекторами — например, .header nav ul > li a > .inner — чрезвычайно хрупкие и являются источником многих непредвиденных ошибок. Они также являются кошмаром для поддержания, особенно если вы наследуете код от кого-то другого.
Качество важнее количества
Большая проблема просто наличия дорогих селекторов — их много. Это известно как «раздувание стиля», и вы, вероятно, часто видели эту проблему. Типичными примерами являются сайты, которые импортируют целые фреймворки CSS, такие как Bootstrap или Foundation, при использовании менее 10% переданного CSS. Другой пример можно увидеть в старых, никогда не рефакторизованных проектах, чей CSS перешел, как мне нравится называть их, «Хронологические таблицы стилей» — CSS с тонной добавленными классами в конец файла по мере того, как проект изменился и вырос, теперь больше похож на заросший сад, полный сорняков.
Мало того, что большой файл CSS занимает больше времени для передачи (и сеть является самым узким местом в производительности веб-сайта), они также занимают больше времени для синтаксического анализа. Помимо построения DOM из вашего HTML, браузеру необходимо построить CSSOM (объектную модель CSS), чтобы сравнить его с DOM и сопоставить селекторы.
Итак, держите свои стили стройными и сухими, не включайте все и кухонную раковину, загружайте то, что вам нужно, и когда вам это нужно, и используйте UNCSS, если вам нужно.
Слон в комнате: недействительность стиля
То, что мы рассмотрели, хорошо, но мы обсуждали только один проход передачи. Сегодняшние веб-сайты больше не являются статическими документами, но напоминают приложения с динамическим содержимым, с которыми пользователи могут взаимодействовать.
Это усложняет ситуацию, поскольку синтаксический анализ CSS — это всего лишь один шаг в конвейере рендеринга браузера. Вот рендеринг-ориентированный взгляд на то, как браузер отображает один кадр на экране (источник: Google):
Мы не будем входить в производительность JavaScript и компоновку, но будем фокусироваться вместо этого на фиолетовом разборе партитуры и выстраивании элементов.
После создания DOM и CSSOM браузеру необходимо объединить два в дерево рендеринга, прежде чем окончательно покрасить его на экране. На этом этапе браузеру необходимо вычислить вычисляемый CSS для каждого соответствующего элемента. Вы сами это видите в панели «Элементы> Стили» инструментов разработчика. Для построения конечного, вычисленного CSS для элемента требуется все подходящие стили стилей, каскада и специфические для браузера стили агента.
Затем он может перейти к этапу компоновки (также известному как reflow), где он вычисляет геометрию и создает полевую модель страницы, помещая каждый элемент в соответствующее положение в окне просмотра. Макет является наиболее вычислительно интенсивной частью этого процесса.
Наконец, браузер преобразует каждый узел в дереве рендеринга в фактические пиксели на экране на этапе рисования.
Теперь, что происходит, когда мы мутируем DOM, изменяя некоторые классы на странице, добавляя или удаляя некоторые узлы, изменяя некоторые атрибуты или каким-либо образом возиться с HTML (или самими стилями)?
Мы аннулируем вычисленные стили, и браузеру необходимо аннулировать все по дереву согласованных селекторов. В то время как современные браузеры намного умнее, обычно случалось, что если вы изменили класс на элемент body, все элементы-потомки должны были пересчитать рассчитанные стили.
Один из способов избежать этой проблемы — уменьшить сложность ваших селекторов. Вместо записи #nav > .list > li > a используйте один селектор, например .nav-link. Таким образом, вы уменьшите масштаб недействительности стиля, так как если вы измените что-либо внутри #nav, вы не будете инициировать пересчеты для всего узла.
Другой способ — уменьшить область действия — например, количество недействительных элементов. Будьте конкретны с вашим CSS. Имейте это в виду, особенно во время анимаций, где браузер имеет всего ~ 10 мс, чтобы выполнить всю необходимую работу.
Заключение
Подводя итог, вы не должны беспокоиться о производительности селектора, если только вы действительно не заходите за борт. Несмотря на то, что в 2012 году эта тема была полной, браузеры стали намного быстрее и умнее. Даже Google больше не беспокоится об этом. Если вы посмотрите страницу «Page Speed», вы не увидите правила «Использовать эффективные селектор CSS», который был удален в 2013 году.
Вместо этого сосредоточьтесь на том, чтобы сделать ваш CSS удобным и понятным. Ваши коллеги и ваше будущее будут благодарны вам за это. Попробуйте оптимизировать доставку CSS, включив только используемые стили. И после этого познакомьтесь с конвейером рендеринга. В отличие от селекторов, сами стили могут быть дорогими, а различие между janky-сайтом и гладким можно найти в том, как CSS реализован.
И как последнее замечание: всегда делайте свои собственные тесты.
Не верьте, что кто-то написал в Интернете несколько лет назад. Пейзаж меняется радикально и невероятно быстро. То, что актуально сегодня, может стать устаревшим раньше, чем вы знаете.
Автор: Ivan Čurić
Источник: https://www.sitepoint.com/
Редакция: Команда webformyself.