awesome-everything EN
↑ Обратно к восхождению

Браузер и фронтенд-рантайм

Флейм-стрип DevTools и жизненный цикл кадра

Суть Как читать флейм-стрип Performance, что делают contain/content-visibility, точный порядок событий внутри одного кадра и чем микрозадачи отличаются от задач.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 16 min

Вы открываете DevTools Performance, записываете скролл и видите море цветных полос. Некоторые красные. Большинство жёлтые. Вы знаете, что jank где-то там — но не понимаете, какую полосу чинить. Флейм-стрип — это карта. Этот урок учит её читать.

Чтение флейм-стрипа Performance в DevTools

Откройте DevTools → Performance, нажмите запись, воспроизведите jank, остановите.

Цвета дорожек (главный поток):

  • Жёлтый — scripting (исполнение JavaScript)
  • Фиолетовый — rendering (пересчёт стилей + компоновка)
  • Зелёный — paint
  • Тёмно-синий — compositing

Цвета дорожек (кадры):

  • Зелёный — кадр вовремя
  • Жёлтый — опоздавший кадр
  • Красный — пропущенный кадр

Флейм-стрип укладывает вызовы сверху вниз: верхняя полоса — то, что вызвал JS; каждая ниже — то, что вызвала она. Найдите длинную красную или жёлтую полосу — это ваше бутылочное горлышко. Наведите для точного значения в мс.

Распространённые паттерны DevTools → диагноз

Жёлтая JS-полоса → фиолетовая Layout-полоса

JS вызвал форсированную синхронную компоновку. Самый частый баг производительности рендеринга.

Широкая фиолетовая Recalculate Style после смены класса высоко в дереве

Вы каскадировали инвалидацию стилей на слишком много потомков. Перенесите смену класса ниже или используйте contain: style.

Широкая зелёная Paint после изменения filter: blur(…)

Вы превысили сложность отрисовки. filter тяжёл — каждый пиксель требует мультипиксельной выборки.

Широкий тёмно-синий Composite Layers, когда всё остальное выглядит нормально

Слишком много слоёв — часто из-за злоупотребления will-change. Проверьте Layer Borders.

Жизненный цикл кадра: точный порядок

Внутри одного кадра браузер выполняет работу в строгом порядке, заданном HTML-спецификацией:

  1. Сбор input-событий (mousemove, keypress)
  2. Запуск setTimeout / setInterval-колбэков, если их время пришло
  3. Запуск микрозадач (promise-резолюции) до опустошения очереди
  4. Запуск requestAnimationFrame-колбэков
  5. Запуск ResizeObserver и IntersectionObserver-колбэков
  6. Пересчёт стилей и компоновка
  7. Отрисовка
  8. Композитинг

После шага 8 браузер отдаёт управление ОС и ждёт следующего vsync.

Ключевое свойство: rAF запускается до стилей/компоновки, поэтому запись внутри rAF приводит ровно к одному проходу компоновки на кадр — в этом весь смысл rAF. ResizeObserver срабатывает после компоновки, но до отрисовки: можно реагировать на изменения компоновки без форсированной повторной компоновки. IntersectionObserver срабатывает асинхронно между кадрами, не блокируя текущий.

Микрозадачи vs задачи: границы планирования

Очередь микрозадач (промисы, queueMicrotask) опустошается полностью между каждым шагом event loop, в том числе между rAF-колбэками.

Некорректная promise-цепочка, планирующая себя бесконечно, голодит рендеринг — браузер не может добраться до шага 6, пока очередь не опустеет. Именно поэтому длинные promise-цепочки и тесные await-циклы отображаются в DevTools как единая непрерывная жёлтая scripting-полоса, а не серия коротких.

Современный код, желающий уступить рендереру в середине задачи, использует:

  • scheduler.yield() (Scheduler API, Chrome 115+) — создан именно для этого
  • setTimeout(fn, 0) — помещает работу в очередь макрозадач и разблокирует рендеринг

Реактивные фреймворки и пайплайн

Когда React рендерит, DOM-обновления происходят на главном потоке, затем пайплайн запускает пересчёт стилей и компоновку на изменённых узлах. Сама реконсиляция — чистый JS, она отображается жёлтым в DevTools. Если ре-рендер затрагивает узел у вершины дерева, инвалидация стилей каскадирует вниз; затрагивает изолированный лист — инвалидация локальна.

Ключевая практика: держите состояние локальным, как можно ближе к листу. useState в листовом компоненте инвалидирует один узел; useContext в провайдере верхнего уровня может инвалидировать сотни. Тот же принцип работает для Svelte-stores, MobX-observables, Vue refs — каждая реактивная модель в итоге пишет в DOM, а DOM-запись запускает тот же пайплайн.

Почему это работает

Понимание порядка жизненного цикла кадра отделяет «трюки планирования, которые работают» от «трюков, которые незаметно трэшат». Частая ошибка: запись CSS-свойства внутри setTimeout(fn, 0) в расчёте, что она забатчится с rAF-записями. Не забатчится — setTimeout срабатывает на шаге 2, до rAF на шаге 4, поэтому запись происходит на шаг раньше rAF-записей и не может быть с ними забатчена. Всегда помещайте визуальные записи внутрь rAF, а не в макрозадачные колбэки.

Расставь шаги по порядку

Расставьте события в порядке их появления внутри одного кадра, согласно шагам рендеринга из HTML-спецификации.

  1. 1 Браузер обрабатывает input-события (mousemove, keypress)
  2. 2 setTimeout / setInterval-колбэки срабатывают, если пришло время
  3. 3 Микрозадачи опустошаются (обработчики promise.then)
  4. 4 requestAnimationFrame-колбэки запускаются
  5. 5 ResizeObserver / IntersectionObserver-колбэки срабатывают
  6. 6 Пересчитываются стили и компоновка
  7. 7 Отрисовка выполняется на главном потоке
  8. 8 Поток композитора собирает и отправляет кадр
Викторина

В панели Performance DevTools жёлтая JS-полоса сразу за ней фиолетовая Layout-полоса сопоставимой длины. Что произошло?

Викторина

Бесконечный `await`-цикл в promise-цепочке работает 200 мс. Что показывает панель Performance DevTools?

Викторина

Вы хотите прочитать новый размер изменённого элемента и обновить стиль сиблинга в ответ. Какой observer срабатывает в нужное время, чтобы не вызвать вторую компоновку?

Вспомните перед уходом
  1. 01
    В каком порядке срабатывают внутри одного кадра: rAF, setTimeout, микрозадачи, ResizeObserver, style/layout?
  2. 02
    Почему бесконечная promise-цепочка голодает рендеринг?
  3. 03
    Какого цвета полосы компоновки в панели Performance, и какого — scripting?
Итог

Панель Performance DevTools кодирует стадии пайплайна цветами: жёлтый для JS, фиолетовый для компоновки/стилей, зелёный для отрисовки, тёмно-синий для composite. Жизненный цикл кадра зафиксирован HTML-спецификацией — rAF срабатывает до style/layout, ResizeObserver после компоновки перед отрисовкой. Микрозадачи полностью опустошаются между каждым шагом, поэтому бесконечный promise-цикл голодает рендерер. Для уступки в середине задачи используйте scheduler.yield() (Chrome 115+) или setTimeout(fn, 0). Реактивные фреймворки запускают тот же пайплайн при DOM-записях; держите состояние локальным ближе к листовым компонентам — это минимизирует каскад инвалидации стилей.

Связанные уроки
встречается в143
Продолжить восхождение ↑Layout thrash: форсированная синхронная компоновка
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.