Вітаю вас, шановні читачі! Сьогодні я б хотів висвітлити питання рендеринга в веб-розробці. Звичайно, на цю тему вже написано багато статей, але, як мені здалося, вся інформація досить розрізнена і уривчаста. По крайней мере, щоб зібрати всю картину в своїй голові і осмислити її, мені довелося проаналізувати чимало інформації (в основному - англомовної). Саме тому я вирішив формалізувати свої знання до статті, і поділитися результатом з спільнотою Хабра. Думаю, інформація буде корисна як початківцям веб-розробникам, так і більш досвідченим, щоб освіжити і структурувати свої знання.
Даний напрямок можна і потрібно оптимізувати на етапі верстки / frontend-розробки, оскільки, очевидно, що розмітка, стилі і скрипти приймають в рендеринге безпосередню участь. Для цього відповідні фахівці повинні знати деякі тонкощі.
Процес обробки WEB-сторінки браузером
Для початку, розглянемо послідовність роботи браузера при відображенні документа:
- З отриманого від півночі HTML-документа формується DOM (Document Object Model).
- Завантажуються і розпізнаються стилі, формується CSSOM (CSS Object Model).
- На основі DOM і CSSOM формується дерево рендеринга, або render tree - набір об'єктів рендеринга (Webkit використовує термін «renderer», або «render object», а Gecko - «frame»). Render tree дублює структуру DOM, але сюди не потрапляють невидимі елементи (наприклад - . або елементи зі стилем display: none; ). Також, кожен рядок тексту представлена в дереві рендеринга як окремий renderer. Кожен об'єкт рендеринга містить відповідний йому об'єкт DOM (або блок тексту), і розрахований для цього об'єкту стиль. Простіше кажучи, render tree описує візуальне уявлення DOM.
- Для кожного елемента render tree розраховується становище на сторінці - відбувається layout. Браузери використовують потоковий метод (flow), при якому в більшості випадків достатньо одного проходу для розміщення всіх елементів (для таблиць проходів потрібно більше).
- Нарешті, відбувається отрисовка всього цього добра в браузері - painting.
В процесі взаємодії користувача зі сторінкою, а також виконання скриптів, вона змінюється, що вимагає повторного виконання деяких з перерахованих вище операцій.
У разі зміни стилів елемента, які не впливають на його розміри і положення на сторінці (наприклад, background-color. Border-color. Visibility), браузер просто отрісовиваєт його заново, з урахуванням нового стилю - відбувається repaint (або restyle).
Якщо ж зміни зачіпають вміст, структуру документа, положення елементів - відбувається reflow (або relayout). Причинами таких змін зазвичай є:
- Маніпуляції з DOM (додавання, видалення, зміна, перестановка елементів);
- Зміна вмісту, в т.ч. тексту в полях форм;
- Розрахунок або зміна CSS-властивостей;
- Додавання, видалення таблиць стилів;
- Маніпуляції з атрибутом «class»;
- Маніпуляції з вікном браузера - зміни розмірів, прокрутка;
- Активація псевдо-класів (наприклад. Hover).
Оптимізація з боку браузера
Браузери по можливості локалізують repaint і reflow в межах елементів, які були піддані зміни. Наприклад, зміна розмірів абсолютно або фіксоване позиційований елемента торкнеться тільки сам елемент і його нащадків, в той час як зміна статично позиційований - спричинить reflow всіх елементів, наступних за ним.
Однак, як описано вище, звернення до властивостей елементів викличе примусовий reflow. Тобто, якщо ми в наведений блок коду додамо звернення до властивості елемента, це викличе зайвий reflow:
У підсумку ми отримаємо 2 reflow замість одного. Тому, звернення до властивостей елементів по можливості потрібно групувати в одному місці, щоб оптимізувати продуктивність (див. Детальніший приклад на JSBin).
Але, на практиці зустрічаються ситуації, коли без примусового reflow не обійтися. Припустимо, у нас є завдання: до елементу потрібно застосувати один і той же властивість (візьмемо «margin-left») спочатку без анімації (встановити в 100px), а потім - анімувати за допомогою transition в значення 50px. Можете відразу подивитися цей приклад на JSBin. але я розпишу його і тут.
Для початку заведемо клас з transition:
Потім, спробуємо реалізувати задумане наступним чином:
Дане рішення не буде працювати, як очікується, тому що зміни кешуються і застосовуються тільки в кінці блоку коду. Нас врятує примусовий reflow, в результаті код буде мати наступний вигляд, і буде в точності виконувати поставлене завдання:
Практичні поради щодо оптимізації
На основі даної статті, а також інших статей на окупантів, де висвітлюється питання оптимізації JS, можна вивести наступні поради, які стануть в нагоді при створенні ефективного фронтенда:
- Пишіть валідний HTML і CSS, з зазначенням кодування. Стилі краще включати в . а скрипти - в кінці .
- Варто уникати складних вкладень селекторів в CSS (цим часто грішать розробники, що використовують препроцесори). Також замість тегів краще використовувати класи, або ідентифікатори. А також, слід уникати універсального селектора (*).
Сподіваюся, кожен читач витягнув зі статті що-небудь корисне. У будь-якому випадку - спасибі за увагу!