Статьи

Оптимизация CSS: селекторы идентификаторов и другие мифы

В сегодняшнем типичном сценарии, когда средний веб-сайт отправляет 500 КБ сжатого JavaScript и 1,5 МБ изображений, работающих на среднем устройстве Android через 3G с временем прохождения сигнала 400 мс, производительность CSS-селектора является наименьшей из наших проблем.

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

Основы CSS-парсинга

Во-первых, чтобы попасть на ту же страницу — эта статья не о производительности свойств и значений CSS. Здесь мы рассмотрим стоимость производительности самих селекторов. Я сосредоточусь на движке рендеринга Blink, в частности Chrome 62.

Селекторы можно разбить на несколько групп и (примерно) отсортировать от наименее до самых дорогих:

ранг тип пример
1. Я БЫ #classID
2. Учебный класс .class
3. Тег div
4. Генеральный и соседний брат div ~ a , div + a
5. Ребенок и потомок div > a , div a
6. универсальный *
7. атрибут [type="text"]
8. Псевдоклассы и элементы a:first-of-type , a:hover

Значит ли это, что вы должны использовать только идентификаторы и классы? Ну не совсем. По-разному. Сначала рассмотрим, как браузеры интерпретируют селекторы CSS.

Браузеры читают CSS справа налево. Самый правый селектор в составном селекторе известен как ключевой селектор. Так, например, в #id .class > ul a , селектор ключа — a . Браузер сначала сопоставляет все ключевые переключатели. В этом случае он находит все элементы на странице, которые соответствуют селектору. Затем он находит все элементы ul на странице и фильтрует элементы a только по тем элементам, которые являются потомками ul s, и так далее, пока не достигнет крайнего левого селектора.

Поэтому чем короче селектор, тем лучше. Если возможно, убедитесь, что ключевым селектором является класс или идентификатор, чтобы он был быстрым и конкретным.

Измерение производительности

Бен Фрейн создал серию тестов для измерения производительности селекторов еще в 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 года, и получил следующее:

селектор Время запроса (мс)
div 4,8740
.box 3,625
.box > .title 4,4587
.box .title 4,5161
.box ~ .box 4,7082
.box + .box 4,6611
.box:last-of-type 3,944
.box:nth-of-type(2n - 1) 16,8491
.box:not(:last-of-type) 5,8947
.box:not(:empty):last-of-type .title 8,0202
.box:nth-last-child(n+6) ~ div 20,8710

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

Даже в таком крайнем случае, когда нужно сопоставить 50000 элементов и использовать некоторые действительно безумные селекторы, такие как последний, мы обнаруживаем, что самый медленный — ~ 20 мс, а самый быстрый — простой класс с ~ 3,5 мс. Не очень большая разница. В реалистичном, более «прирученном» DOM с около 1000–5000 узлов вы можете ожидать, что эти результаты упадут в 10 раз, что приведет к скорости разбора менее миллисекунды.

Из этого теста видно, что на самом деле не стоит беспокоиться о производительности селектора CSS. Только не переусердствуйте с псевдо-селекторами и действительно длинными селекторами. Мы также можем видеть, как Blink улучшился за последние два года. Вместо заявленного ~ 500-кратного замедления для «селектора количества» ( .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 Object Model), чтобы сравнить его с DOM и сопоставить селекторы.

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

Если вы хотите больше узнать о том, как браузеры анализируют CSS, посмотрите статью Николь Салливан о Webkit , статью Ильи Григорика о том, как это делает Blink , или статью Лин Кларк о новом движке Mozilla Stylo CSS .

Слон в комнате: недопустимость стиля

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

Это усложняет ситуацию, поскольку синтаксический анализ CSS — это только один шаг в конвейере рендеринга браузера. Вот ориентированное на рендеринг представление о том, как браузер отображает один кадр на экране (источник: Google ):

Конвертер рендеринга браузера

Мы не будем вдаваться в производительность и компоновку JavaScript, а сосредоточимся вместо этого на фиолетовом разборе стиля элементов и разметке элементов.

После создания DOM и CSSOM браузер должен объединить их в дерево рендера, прежде чем, наконец, нарисовать его на экране. На этом этапе браузер должен выяснить вычисленный CSS для каждого соответствующего элемента. Вы можете сами убедиться в этом на панели « Элементы»> «Стили» инструментов разработчика. Для создания окончательного, вычисленного CSS для элемента требуются все соответствующие стили, каскад и стили пользовательского агента, специфичные для браузера.

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

Наконец, браузер преобразует каждый узел в дереве рендеринга в фактические пиксели на экране на этапе рисования.

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

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

Один из способов избежать этой проблемы — уменьшить сложность ваших селекторов. Вместо того, чтобы писать #nav > .list > li > a , используйте один селектор, например .nav-link . Таким образом, вы уменьшаете объем аннулирования стиля, поскольку, если вы измените что-либо внутри #nav , вы не будете запускать пересчеты для всего узла.

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

Если вы хотите перейти к мельчайшим деталям аннулирования стиля, я рекомендую прочитать Invalidation в Blink .

Вывод

Подводя итог, вы не должны беспокоиться о производительности селектора, если вы действительно не за борт. Хотя эта тема была в моде в 2012 году, с тех пор браузеры стали намного быстрее и умнее. Даже Google больше не беспокоится об этом. Если вы заглянете на страницу Google Page Speed ​​Insights , вы не увидите правило «Использовать эффективные селекторы CSS», которое было удалено в 2013 году.

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

И в заключение: всегда делайте свои собственные тесты.

Не просто верьте тому, что кто-то написал в Интернете несколько лет назад. Пейзаж резко меняется и невероятными темпами. То, что актуально сегодня, может устареть раньше, чем вы знаете.