В какой момент работы Event Loop происходит отрисовка
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
В какой момент работы Event Loop происходит отрисовка
Отрисовка (rendering) в браузере — это процесс преобразования DOM и CSS в видимые пиксели на экране. Это критический момент в жизненном цикле браузера, который тесно связан с Event Loop.
Фазы Event Loop с учетом отрисовки
Полный цикл Event Loop выглядит следующим образом:
1. Выполнить одну задачу из Macrotask Queue
2. Выполнить все задачи из Microtask Queue
3. ОТРИСОВКА (Paint/Render) - ТОЛЬКО ЕСЛИ НУЖНА
4. Запросить AnimationFrame callbacks
5. Вернуться к шагу 1
Точный момент отрисовки
Отрисовка происходит между Macrotask и следующим циклом, ПОСЛЕ полной очистки Microtask Queue:
// Симуляция порядка выполнения
console.log("1. Синхронный код");
setTimeout(() => {
console.log("5. Macrotask - setTimeout");
}, 0);
Promise.resolve()
.then(() => console.log("3. Microtask - Promise"));
requestAnimationFrame(() => {
console.log("4. requestAnimationFrame перед отрисовкой");
});
console.log("2. Конец синхронного кода");
// Браузер в это время выполняет отрисовку
// ПОРЯДОК ВЫПОЛНЕНИЯ:
// 1. Синхронный код
// 2. Конец синхронного кода
// 3. Microtask - Promise
// (ОТРИСОВКА - браузер рисует изменения на экран)
// 4. requestAnimationFrame перед отрисовкой
// 5. Macrotask - setTimeout
Процесс отрисовки подробно
Шаг 1: Выполнение Macrotask
setTimeout(() => {
const box = document.getElementById("box");
box.style.width = "200px"; // Изменение в DOM
console.log("Ширина в setTimeout:", box.offsetWidth); // 200px
}, 0);
Шаг 2: Очистка Microtask Queue
Promise.resolve()
.then(() => {
const box = document.getElementById("box");
box.style.height = "100px";
console.log("Высота в Promise:", box.offsetHeight); // 100px
});
Шаг 3: Отрисовка (Paint/Render)
Браузер берет все накопленные изменения DOM и CSS, пересчитывает макет (Layout), применяет стили и рисует пиксели на экран.
Шаг 4: requestAnimationFrame callbacks
requestAnimationFrame(() => {
// Выполняется ПЕРЕД следующей отрисовкой
const box = document.getElementById("box");
console.log("После отрисовки, box виден", box.offsetWidth);
});
Практический пример: batch updates
function updateMultipleElements() {
const box = document.getElementById("box");
// ВСЕ эти изменения накапливаются
box.style.width = "200px";
box.style.height = "100px";
box.style.backgroundColor = "red";
// Браузер выполнит ОДНУ отрисовку со всеми изменениями
// Это эффективнее, чем отрисовка для каждого изменения
}
setTimeout(updateMultipleElements, 0);
Когда отрисовка НЕ происходит
Браузер оптимизирует отрисовку и может пропустить её, если:
- Изменений нет — нет новых данных в DOM/CSS
- Вкладка невидима — браузер может отложить отрисовку
- Недостаточно времени — браузер может задержать отрисовку до следующего кадра
// Этот код НЕ вызовет отрисовку
const elem = document.createElement("div");
elem.style.display = "none";
// Элемент в DOM, но невидим
requestAnimationFrame vs setTimeout
requestAnimationFrame синхронизируется с частотой обновления экрана (обычно 60 FPS = 16ms на кадр):
// Идеально для анимаций
function animate() {
element.style.transform = `translateX(${x}px)`;
x += 5;
if (x < 500) {
requestAnimationFrame(animate);
}
}
requestAnimationFrame(animate);
// setTimeout может вызвать jank (пропуск кадров)
function animateBad() {
element.style.transform = `translateX(${x}px)`;
x += 5;
if (x < 500) {
setTimeout(animateBad, 16); // Может быть неточен
}
}
Влияние на React компоненты
function ReactComponent() {
const [count, setCount] = useState(0);
const handleClick = () => {
// Это вызовет перерендер React компонента
setCount(count + 1);
// Отрисовка браузером произойдет ПОСЛЕ всех микротасков
// (React использует микротаски для батчинга обновлений)
};
useLayoutEffect(() => {
// Этот хук вызывается ПОСЛЕ расчета макета, но ДО отрисовки
console.log("После layout, перед paint");
});
useEffect(() => {
// Этот хук вызывается ПОСЛЕ отрисовки
console.log("После paint");
});
return <button onClick={handleClick}>Count: {count}</button>;
}
Лучшие практики
- Используй requestAnimationFrame для анимаций, а не setTimeout
- Батчируй изменения DOM в одном макротасе
- Избегай читать offsetWidth/offsetHeight в цикле — вызывает layout thrashing
- useLayoutEffect для изменений перед отрисовкой, useEffect для после
- Профилируй производительность с помощью Performance API
Понимание этого механизма критично для создания гладких анимаций и высокопроизводительных приложений.