Примечание: ниже находится перевод заметки от Steve Souders Simplifying CSS Selectors. Steve рассуждает и приводит примеры неэффективных селекторов и обозначает основные направления оптимизации в этой области. Мои комментарии далее курсивом.
Эта заметка является частью моей новой книги Even Faster Web Sites. Предыдущие заметки в этой серии включают: chapters and contributing authors, Splitting the Initial Payload, Загружаем скрипты без блокировки, Стыкуем асинхронные скрипты, Размещаем внутренние скрипты, Балансируем основной домен, Быстро сбрасываем документ и Скупимся на фреймы.
«Упрощаем CSS-селекторы» — это последняя часть моей готовящейся книги Even Faster Web Sites. Однако я начал исследования производительности CSS-селекторов не так давно. Несколько месяцев назад была заметка На что влияет производительность CSS-селекторов (заметка весьма поверхностная, и каких-то конкретных указаний к действия она не дает). В ней идет разговор о различных типах CSS-селекторов, некоторые из которых, по всей видимости, является довольно болезненными с точки зрения производительности. Также в заметке делается предварительный вывод о переоцененном влиянии CSS-селекторов на скорость загрузки:
Для большинства сайтов возможный выигрыш в производительности после оптимизации CSS-селекторов будет крайне незначительным и не будет стоить потраченного времени. Есть несколько типов CSS-правил (например, expression для IE) и взаимодействий с JavaScript, которые могут существенно замедлить страницу. Именно на них и нужно концентрировать усилия.
Я получил довольно много примеров, когда используемые CSS-селекторы действительно заметно замедляют загрузку страницы. Пытаясь выделить среди этих медленных CSS общее, я пришел в следующей цитате из статьи David Hyatt Пишем эффективный CSS для интерфейсов в Mozilla:
Система стилей находит элемент, соответствующий CSS-правилу, начиная с правого конца селектора и двигаясь налево. Пока мы еще находим элементы, соответствующие селектору, мы продолжаем двигаться. Остановка возможна только в случае нахождения элемента, соответствующего всему CSS-селектору, или невозможность такой элемент найти для какой-то части селектора.
Не очень понятно, как устроен движок CSS-селекторов в других браузерах, например, в IE. Может быть, он использует комбинированный подход: как справа налево, как и слева направо. Переключение может происходить по какому-то признаку (например, #id
).
Благодаря вышесказанному мы можем сфокусировать наши оптимизационные усилия на тех CSS-селекторах, правая часть которых будет соответствовать большому числе элементов на странице. Примеры из прошлой статьи содержали часть CSS-селекторов, которые выглядели «тяжелыми», но в новом свете таковыми уже не являлись. Например, DIV DIV DIV P A.class0007 {}
. У этого селектора присутствует 5 уровней вложенности потомков, для которых необходимо найти соответствие в DOM-дереве. Это выглядит очень ресурсоемко, однако при взгляде на самую правую часть этого селектора, A.class0007
, мы прекрасно понимаем, что ей соответствует только 1 элемент на странице, поэтому браузеру очень легко установить точное соответствие .
Ключевым моментом в оптимизации CSS-селекторов является самая правая часть, часто также называемая «ключевым селектором» (key selector, совпадение?). Вот пример гораздо более ресурсоемкого селектора: A.class0007 * {}
. Хотя он может и выглядеть просто, но для браузера очень тяжело его вычислить. Поскольку браузер будет двигаться справа налево, то он начнет со всех элементов, которые подходят под ключевой селектор, "*
". Это означает, что браузер попытается проверить все элементы на странице, нужно ли к ним применить этот стиль. На следующей диаграмме показана разница во времени между тестовой страницей для универсального селектора и прошлой тестовой страницей с потомственным селектором.
Разница во времени загрузки для универсального селектора
Очевидно, что CSS-селекторы с ключевым элементом, который соответствует большому числу узлов, будут вычисляться существенно дольше и заметно тормозить загрузку страницы. В качестве других примеров селекторов, которые могут привести к большим вычислениям со стороны браузера, можно отнести:
A.class0007 DIV {} #id0007 > A {} .class0007 [href] {} DIV:first-child {}
Не все CSS-селекторы существенно влияют на производительность, даже те, которые могут выглядеть сложно. При оптимизации стоит фокусироваться на устранении CSS-правил с ключевым селектором, соответствующим большому числу элементов. Это становится особенно важно для Веб2.0 приложений с большим числом узлов DOM-дерева, огромным количеством CSS-правил и отрисовок страниц.
Как было упомянуто в комментариях к этой статье, некоторой проблемой является то, что почти все известные JavaScript-библиотеки разбирают селекторы слева направо. Тем самым, мы должны писать 2 вида селекторов: оптимизированных под браузеры и оптимизированных под JavaScript-библиотеки. На данный момент только немногие поддерживают нотацию справа налево, например, CSS1-ветка YASS.