Reflow і repaint
Reflow, це перерахунок геометрії сторінки (layout), а repaint, це перемальовування пікселів уже порахованих елементів (paint). Коли ви змінюєте DOM або CSS, рушій рендерингу (у Chrome це Blink) мусить перерахувати, як сторінка виглядає, і ця робота ділиться саме на ці етапи.
Теорія
TL;DR
- Reflow (layout): перерахунок розмірів, позицій і відступів; repaint (paint): перемальовування видимих пікселів без зміни геометрії.
- Майже кожен reflow спричиняє repaint, але не кожен repaint потребує reflow.
- Reflow спричиняють
width,height,margin,padding,font-size, додавання і видалення вузлів, зміна тексту, resize, scroll. - Repaint спричиняють
color,background-color,box-shadow,outline,visibility. - Читання
offsetWidth,clientHeightчиgetComputedStyle()одразу після запису стилю змушує браузер зробити синхронний layout (forced reflow). - Найдешевший шлях, composite: анімуйте лише
transformтаopacity, вони йдуть повз layout і paint.
Швидкий приклад
const el = document.querySelector('.box');
el.style.width = '400px'; // reflow + repaint: змінилася геометрія
el.style.backgroundColor = 'red'; // лише repaint: змінився вигляд
el.style.transform = 'scale(1.1)'; // лише composite, робота для GPU
// Найгірший варіант: запис і одразу читання layout-властивості
el.style.width = '500px';
console.log(el.offsetHeight); // forced synchronous layout просто тутЩо робить браузер на кожному етапі
| Термін | Що робить | Коли відбувається |
|---|---|---|
| Reflow (layout) | Перераховує геометрію елементів: розміри, позиції, відступи, висоту, ширину | Коли змінюється структура DOM або CSS, що впливає на розкладку |
| Repaint (paint) | Перемальовує видимі пікселі (кольори, фон, тіні, рамки) без перерахунку геометрії | Коли змінюється лише зовнішній вигляд, а не розмір чи позиція |
Під капотом послідовність така.
Reflow змушує браузер:
- Перерахувати розміри й позиції елементів (layout tree).
- Визначити, які ділянки екрана треба перемалювати.
- Запустити repaint.
Repaint зафарбовує змінені пікселі, далі йде composite, склейка шарів.
Майже кожен
reflowспричиняєrepaint, але не коженrepaintпотребуєreflow.
Що спричиняє reflow, а що repaint
Reflow спричиняють:
- зміна розмірів:
width,height,padding,margin,border,font-size; - додавання або видалення DOM-елементів;
- зміна тексту;
- обчислення властивостей, що залежать від layout:
offsetWidth,clientHeight,getComputedStyle(); - зміна класу, який впливає на позиціювання чи розміри;
- зміна розміру вікна браузера (resize);
- прокручування (scroll).
element.style.width = '400px'; // reflow + repaintRepaint спричиняють:
- зміна кольору:
color,background-color; - зміна прозорості
opacity; - тіні
box-shadow; outline;visibility, якщо елемент лишається на місці.
element.style.backgroundColor = 'red'; // лише repaintЧому reflow небезпечний для продуктивності
Reflow, це одна з найдорожчих операцій у браузері, особливо коли вона зачіпає багато елементів.
- Layout-дерево пов'язане: зміна одного елемента може змусити перерахувати весь документ.
- Один reflow на складній розкладці може займати десятки мілісекунд.
Якщо робити його в циклі або дуже часто, інтерфейс починає гальмувати.
Reflow і repaint відбуваються у фазі рендерингу кадру, між макро- і мікрозадачами event loop. Щоб анімація була плавною, треба вкладатися в 16.6 мс на кадр (60 FPS). Якщо layout займає більше, кадри пропускаються і користувач бачить фризи.
Як зменшити кількість reflow і repaint
| Підхід | Що дає |
|---|---|
| Batch updates | Змінюйте кілька властивостей одразу, через classList або cssText, замість окремих присвоєнь |
| DocumentFragment | Збирайте DOM-елементи поза документом і вставляйте все одним рухом |
| Кешуйте обчислення | const height = el.offsetHeight; один раз перед циклом, а не всередині |
| Не анімуйте layout-властивості | Уникайте анімацій width, height, top, left |
| CSS transform і opacity | GPU-анімації не спричиняють reflow |
| requestAnimationFrame() | Для анімацій замість setInterval |
| will-change | Заздалегідь повідомляє браузеру, які властивості зміняться, щоб він виніс елемент у окремий шар |
Приклад поганого коду, де reflow відбувається сотні разів:
for (let i = 0; i < 100; i++) {
const el = document.createElement('div');
document.body.appendChild(el); // reflow щоразу
el.style.width = i + 'px'; // reflow
console.log(el.offsetHeight); // forced reflow
}Оптимізована версія з одним reflow:
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
const el = document.createElement('div');
el.style.width = i + 'px';
fragment.appendChild(el);
}
document.body.appendChild(fragment); // один reflowПідсумкова таблиця
| Термін | Що робить | Приклад | Вплив |
|---|---|---|---|
| Reflow (layout) | Перерахунок геометрії і позицій | зміна width, margin, додавання DOM | дорога операція |
| Repaint (paint) | Перемальовування зовнішнього вигляду | зміна color, background, opacity | помірно витратна |
| Composite | Склейка шарів після paint | transform, opacity | дешева, виконується на GPU |
| Мета | Мінімізувати reflow, анімувати лише transform і opacity | максимум FPS |
Типові помилки
| Помилка | Що відбувається |
|---|---|
| Багаторазова зміна стилю окремими присвоєннями | Щоразу тригериться новий reflow |
Звернення до offsetHeight одразу після зміни стилю | Браузер змушений синхронно перерахувати layout |
| Зміна класів у циклі | Reflow на кожній ітерації |
Анімація властивостей, що впливають на layout (width, top, margin) | Layout перераховується на кожному кадрі |
| Часте додавання DOM-елементів по одному | Кожна вставка спричиняє перерахунок дерева |
Крім того, часто плутають дві речі: думають, що opacity завжди безкоштовна. Вона дешева лише тоді, коли елемент уже винесений у власний шар композиції; інакше все одно буде repaint. І ще одна класика, накинути will-change на все підряд: кожен шар коштує пам'яті, тож надлишкові шари псують продуктивність замість того, щоб її покращити.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.