Донецкий техникум промышленной автоматики

Швидкість CSS-селекторів - істотна різниця в їх ефективності?

  1. Основи парсинга CSS
  2. Вимірювання продуктивності
  3. Якість важливіша за кількість
  4. Очевидна проблема: інвалідація стилів
  5. висновок

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

І все ж ця тема заслуговує обговорення, хоча б для того, щоб розвіяти деякі міфи і легенди, що оточують її. Отже, давайте розглянемо проблему продуктивності CSS-селекторів докладніше.

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

По-перше, щоб уникнути нерозуміння в подальшому - ця стаття не стосується продуктивності CSS-властивостей і їх значень. Тут ми розглядаємо експлуатаційні витрати самих селекторів. Я зосереджуся на движку рендеринга Blink, а саме на браузері Chrome версії 62.

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

Тип селектора Приклад 1. Ідентифікатор (ID) #classID 2. Клас (Class) .class 3. Тег (Tag) div 4. Узагальнений і cоседній споріднений комбінатор (General and adjacent sibling) div ~ a, div + a 5. Спадковий і потомствений комбінатор (Child and descendant) div> a, div a 6. Універсальний (Universal) * 7. Атрибут (Attribute) [type = "text"] 8. Псевдокласи і елементи (Pseudo-classes and elements) a: first-of -type, a: hover

Чи означає це, що потрібно використовувати тільки ID і класи? Ну не зовсім. Залежить від ситуації. По-перше, давайте розглянемо, як браузери інтерпретують CSS-селектори.

Браузери читають CSS справа наліво. У складеному селекторі найправіший селектор називається ключовим. Так, наприклад, в #id .class> ul a, ключовим селектором є a. Спочатку браузер шукає саме ключові селектори. У нашому прикладі він знаходить все елементи на сторінці, які відповідають селектору a. Потім він знаходить все елементи ul на сторінці і фільтрує елементи a залишаючи тільки ті, які є нащадками елементів ul, і так далі, поки не досягне крайнього лівого селектора.

Тому чим коротше складовою селектор, тим краще. По можливості переконайтеся, що ключовий селектор є класом або ID, щоб він був швидким і специфічним.

Вимірювання продуктивності

Бен Фрейн створив серію тестів для вимірювання продуктивності різних селекторів ще в 2014 році. Тестування полягало в вимірі швидкості, необхідної для аналізу різних селекторів, починаючи з простих ідентифікаторів (ID) і закінчуючи деякими, дійсно складними і довгими, складеними селекторами. Вимірювання проводилося на величезному DOM, що складається з 1000 ідентичних елементів. У підсумку він виявив, що різниця між самим повільним і швидким селектором становила ~ 15мс.

Однак з тих пір багато чого змінилося, а заучування старих правил в постійно мінливому світі браузерів майже марно. Завжди пам'ятайте, що потрібно робити власні тести, особливо коли мова йде про продуктивність.

Я вирішив зробити свої власні тести і для цього я використовував тест Пола Льюїса, згаданий в одному коментарі, що виражає заклопотаність корисними, але надмірно мудрими «кількісними CSS-селекторами»:

Ці селектори є одними з найбільш повільних. Приблизно в 500 разів повільніше, ніж щось дике, типу "div.box:not(:empty):last-of-type .title". Сторінка з тестом http://jsbin.com/gozula/1/quiet

Тест був трохи змінений - кількість елементів збільшена до 50000, і ви можете перевірити його самі. Я зробив в середньому 10 прогонів на моєму MacBook Pro 2014 року, і я отримав наступні результати:

Селектор Час виконання (ms) 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, а також від кількості співпадаючих вузлів (nodes) на сторінці, але використання querySelectorAll ближче до реального варіанту використання CSS, який орієнтований на всі співпадаючі елементи.

Навіть при таких екстремальних умовах, коли на сторінці знаходиться 50000 співпадаючих елементів і використовуються дійсно "божевільні" селектори, типу останнього в таблиці вище, ми виявляємо, що найповільніший селектор обробляється приблизно 20мс, найшвидший - простий клас - приблизно 3,5мс. Не така вже й суттєва різниця. У реалістичному і менш екстремальному DOM, що нараховує від 1000 до 5000 вузлів, можна очікувати падіння часу обробки приблизно в 10 разів, довівши час парсинга до декількох мілісекунд.

З цього тесту можна зробити висновок про те, що турбуватися про продуктивність CSS селекторів не варто. Просто не варто перестаратися з псевдоселекторамі і дійсно довгими складовими селекторами. Ми також бачимо, як за останні два роки поліпшився движок Blink. Замість зазначеного в цитованому коментарі уповільнення в ~ 500 разів при обробці "кількісних CSS-селекторів" (.box: nth-last-child (n + 6) ~ div) в порівнянні з "божевільними селекторами" (.box: not (: empty ): last-of-type .title) ми бачимо падіння продуктивності всього в 1,5 рази. Це вражаюче поліпшення. При цьому слід очікувати, що браузери будуть і далі поліпшуватися в цьому напрямку, що ще більше знизить виробничі витрати при роботі з CSS-селекторами.

Проте по можливості ви повинні використовувати класи, а також прийняти будь-які правила іменування (методологію) типу BEM ( "Block, element, modifier" - "Блок, елемент, модифікатор"), SMACSS ( "Scalable and Modular Architecture for CSS "-" Швидка, і модульна архітектура для CSS ") або OOCSS (" Object-oriented CSS "-" Об'єктно-орієнтований CSS "), оскільки це не тільки поліпшить продуктивність вашого веб-сайту, але і значно полегшить підтримку коду. Занадто складні складові селектори, особливо використовують теги і універсальні селектори - наприклад, .header nav ul> li a> .inner - надзвичайно тендітні і є джерелом багатьох непередбачених помилок. Вони також є справжнім кошмаром за підтримки коду, особливо якщо він дістався вам від кого-то другого.

Якість важливіша за кількість

Гірше наявності "дорогих" селектор є тільки велика їх кількість. Це явище відоме як "роздуті стилі" і, ймовірно, ви зустрічали цю проблему в своїй практиці. Типовими прикладом є сайти, які цілком імпортують різні CSS-фреймворки (типу Bootstrap або Foundation), використовуючи при цьому менше 10% їх коду. Інший приклад - це старі, ніколи не переживали рефакторинг, проекти, чий CSS-код перейшов в стан "хронологічні таблиці стилів" - тобто CSS з купою класів, доданих в кінець файлу в міру зростання проекту, який з часом став схожий на занедбаний сад.

Крім того, що передача великого файлу стилів по мережі займає багато часу (а мережа є найвужчим місцем в продуктивності веб-сайту), великі CSS-файли також довше Парс. Але ж крім побудови DOM вашого HTML документа, браузеру необхідно побудувати CSSOM (об'єктну модель CSS), щоб порівняти його з DOM і зіставити селектори.

Отже, тримайте свої стилі "стрункими", не додавайте в них все підряд, завантажуйте тільки те, що вам потрібно, і тільки тоді, коли це вам потрібно. Крім того, в разі потреби користуйтеся різними інструментами для виявлення невикористаного CSS-коду (типу UNCSS).

Для отримання більш детальної інформації про те, як браузери Парс CSS, подивіться запис Ніколь Салліван про движку Webkit, статтю Іллі Григорик про движку Blink або статтю Лін Кларк про новий CSS-движку Mozilla (aka Stylo).

Очевидна проблема: інвалідація стилів

Ми розглянули хороші приклади залежності швидкості обробки CSS-селекторів від їх складності, проте це все стосується одноразової рендеринга. Але сьогоднішні веб-сайти вже не є статичними документами, а більше нагадують додатки з динамічним контентом, з яким користувачі можуть взаємодіяти. Це все трохи ускладнює, оскільки синтаксичний аналіз CSS-коду - це всього лише один крок в конвеєрі рендеринга браузера. Ось більш детальний (рендеринг-орієнтований) погляд на те, як браузер відображає кадр на екрані (зі статті "Продуктивність візуалізації" на developers.google.com):

Ми опустимо питання продуктивності JavaScript і композітінг ( "Composite" на малюнку вище), а зосередимося на фіолетовою частини - парсінгу стилів ( "Style") і компонуванні елементів ( "Layout").

Після побудови DOM і CSSOM, браузеру необхідно об'єднати їх в дерево рендеринга, перш ніж почати отрисовку на екрані. На цьому етапі браузеру необхідно обчислити остаточний набір CSS властивостей і їх значень для кожного знайденого елемента. Ви самі можете це спостерігати в панелі Elements> Styles інструментів розробника браузера. Браузер бере все, що відповідають певному елементу стилі, включаючи специфічні, властиві самому браузеру (так звані user agent stylesheet) для створення остаточного, обчисленого (computed) стилю елемента.

Потім браузер може перейти до етапу компонування (також відомому як reflow), де він обчислює геометрію і створює польову модель сторінки, розміщуючи кожен елемент на відповідній йому позиції у вікні перегляду. Компонування є найбільш обчислювально-інтенсивної частиною цього процесу.

Нарешті, на етапі відтворення, браузер перетворює кожен вузол дерева рендеринга в фактичні пікселі на екрані.

Тепер про те, що відбувається, коли ми видозмінюємо DOM, змінюючи деякі класи на сторінці, додаючи або видаляючи деякі вузли, змінюючи деякі атрибути або яким-небудь іншим чином "бавлячись" з HTML (або самими стилями)?

Ми скасовуємо обчислені раніше стилі, і браузеру необхідно анулювати все вниз по дереву співпадаючих селектор. І хоча сучасні браузери стали значно розумніші, раніше, в ситуації, коли ви змінювали клас елемента <body>, браузеру доводилося перераховувати стилі всім його елементів-нащадкам.

Один із способів уникнути цієї проблеми - зменшити складність ваших селектор. Замість використання таких варіантів як #nav> .list> li> a, використовуйте один селектор, наприклад .nav-link. Таким чином, ви зменшите обсяг відміняються стилів, так як тепер, якщо ви зміните що-небудь всередині #nav, це не призведе до перерахунку стилів для всього вузла.

Інший спосіб - зменшити область дії - наприклад, кількість які відміняються (що вимагають перерахунку стилів) елементів. Будьте конкретні у вашому CSS. Майте це на увазі, особливо при використанні анімації, де браузеру дається всього ~ 10мс * , Щоб виконати всю необхідну роботу.

Більш детально вивчити проблему інвалідаціі стилів можна прочитавши статтю Style Invalidation in Blink .

висновок

Підводячи підсумки, можна сказати, що турбуватися про продуктивність CSS-селекторів не варто, якщо тільки ви дійсно не переходьте межі. Незважаючи на те, що в 2012 році ця тема була актуальною, з тих пір браузери стали набагато швидше і розумніші. Навіть Google більше не турбується про це. Якщо ви подивіться сторінку PageSpeed ​​Insights Rules , Ви не побачите там правила «Use efficient CSS selectors» - «Використовуйте ефективні CSS-селектори», яке було видалено звідти в 2013 році.

Натомість зосередьтеся на тому, щоб зробити ваш CSS зручним і зрозумілим. Ваші колеги і ваше майбутнє будуть вдячні вам за це. Спробуйте оптимізувати завантаження CSS, включивши в них тільки використовувані стилі. А після цього познайомтеся з конвеєром візуалізації. На відміну від селектор, самі стилі можуть бути витратними в плані продуктивності, а відмінність між другосортним і якісним сайтом часто можна виявити в тому, як вони реалізують CSS.

І останнє зауваження: завжди робіть свої власні тести. Не вірте тому, що було написано кимось в Інтернеті кілька років тому. Ситуація змінюється кардинально і неймовірно швидко. Те, що актуально сьогодні, може стати застарілим раніше, ніж ви це вивчіть.

* Мається на увазі, що для гладкої анімації (з частотою 60 fps) на один кадр відводиться 1000мс / 60кадров ≈ 16,6мс. Відкинувши час на саму отрисовку кадру, у браузера залишається ~ 10мс для проведення всіх обчислень. - прим. перев. ^

переклав scorp13

Стаття є вільним перекладом матеріалів сайту https://www.sitepoint.com
Автор Ivan Čurić