← Назад к вопросам

В какой момент работы Event Loop происходит отрисовка

2.0 Middle🔥 271 комментариев
#JavaScript Core

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

В какой момент работы 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);

Когда отрисовка НЕ происходит

Браузер оптимизирует отрисовку и может пропустить её, если:

  1. Изменений нет — нет новых данных в DOM/CSS
  2. Вкладка невидима — браузер может отложить отрисовку
  3. Недостаточно времени — браузер может задержать отрисовку до следующего кадра
// Этот код НЕ вызовет отрисовку
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>;
}

Лучшие практики

  1. Используй requestAnimationFrame для анимаций, а не setTimeout
  2. Батчируй изменения DOM в одном макротасе
  3. Избегай читать offsetWidth/offsetHeight в цикле — вызывает layout thrashing
  4. useLayoutEffect для изменений перед отрисовкой, useEffect для после
  5. Профилируй производительность с помощью Performance API

Понимание этого механизма критично для создания гладких анимаций и высокопроизводительных приложений.