Проблема «медленный рендер» обычно скрывает четыре разные работы: JavaScript меняет DOM, движок пересчитывает style, раскладывает boxes, готовит paint и собирает результат. Пока всё это называют одним словом, исправление выбирают наугад: переносят обработчик, меняют CSS или добавляют debounce. Цена — дорогая правка, после которой пользовательский сценарий не стал понятнее, а следующая запись Performance panel всё ещё не отвечает, какое действие создало тяжёлый кадр.
Начинать стоит не с цветов в панели, а с одного наблюдаемого изменения. Например: нажатие «Сохранить» меняет у карточки className с card is-pending на card is-ready. У этого изменения есть исходный DOM-result, место в обработчике и ожидаемый новый result. Если такой контракт записан, его можно связать с одной interaction в реальной записи и просмотреть события выбранного кадра по порядку. Если контракта нет, trace остаётся длинной лентой, в которой всё похоже на причину.
Сначала назвать границу, а не оптимизацию
Полезная единица работы — «одна DOM-мутация в одном пользовательском действии». Не «вся страница», не «React медленный» и не «CSS тяжёлый». Для выбранной мутации выписываем target, property, old value, new value и способ повторить действие. В статье target — save-card, property — className. Это не универсальная форма для любого приложения. Она нужна, чтобы reviewer мог отличить исходную работу от соседней анимации, сетевого ответа или рендера другого компонента.
Дальше важно не превратить имена стадий в обещание браузера. HTML Standard описывает update the rendering как алгоритм user agent; его подробности и планирование не дают автору права требовать одинаковый внутренний список событий у всех движков. Поэтому ниже есть учебная модель. Она говорит, какой вопрос задать о результате изменения: была ли JS-мутация, потребовалась ли работа style/layout/paint/composite, где образовалась граница диагностики. Она не говорит, как Chrome, Firefox или Safari обязаны назвать одно событие в trace.
| Поле | Пример | Зачем нужно | Чего не доказывает |
|---|---|---|---|
| Действие | нажатие «Сохранить» | повторить один сценарий | что все другие пути быстры |
| DOM before | card is-pending | зафиксировать исходное состояние | что style уже вычислен |
| DOM after | card is-ready | проверить результат обработчика | что был фактический paint |
| Граница записи | одна interaction и один frame range | сузить ленту событий | причину без чтения деталей |
| Следующий вопрос | какая работа следует за mutation | выбрать один объект проверки | готовое архитектурное решение |
Учебный контракт кадра
Fixture пакета не открывает браузер. Она работает с локальными object и array, не вызывает DOM API, requestAnimationFrame, сеть, таймер, profiler или Performance API. Для валидной смены класса она строит пять labels: js-mutation, style, layout, paint и composite. У каждого label есть synthetic work units. Это проектные значения, специально выбранные для теста порядка и границы. Они не являются milliseconds, CPU, FPS, LCP, INP, Web Vitals или данными настоящей записи.
# Проверяется локальная учебная модель, а не страница в Chrome.
node web/scripts/upgrade-2022-03.mjs --verify-fixture
# PASS fixture: 16/16 assertions
# В output: changed, jankBoundary, noOp, error и rollback.
В forward route сумма равна 11 synthetic units: 1 + 2 + 4 + 3 + 1. Учебная граница равна 10, поэтому fixture отмечает jank-boundary. Это слово здесь не означает dropped frame и не является измерением задержки. Оно означает ровно одно: заранее описанное правило модели пересечено. Такая фиксация полезна для примера, потому что тест может проверить условие, а текст не обещает скорость устройства, которой никто не измерял.
Как связать изменение с реальной записью
В настоящем проекте маршрут короче, чем кажется. Откройте страницу в согласованном состоянии, выполните только выбранное действие и остановите recording. Затем найдите interaction по времени и выделите узкий диапазон вокруг неё. Сначала вернитесь к коду: действительно ли обработчик меняет тот DOM-result, который записан в контракте? Затем читайте подробности выбранного диапазона, а не всю ленту. Если изменение оказалось другим, не переносите вывод с учебной схемы: обновите контракт или возьмите новый scenario.
- Симптом. После конкретного действия экран отвечает неровно, но «медленный рендер» не указывает место в коде.
- Причина. В одну причину смешаны DOM mutation, style, layout, paint и JavaScript до них.
- Проверка входа. Запишите target, property, before, after и один способ повторить действие. Убедитесь в DevTools Elements, что after действительно появился.
- Проверка записи. Соберите одну interaction, выделите её диапазон и найдите работу, связанную с этим действием. Не называйте учебные labels буквальными именами trace events.
- Действие. Выберите один следующий объект: handler, CSS rule, geometry read или paint boundary. Подготовьте одну обратимую правку.
- Повтор. Снова выполните тот же контракт. Если DOM result изменился, старую запись нельзя использовать как доказательство нового поведения.
Где часто теряется причина
Первый сбой — брать запись после серии кликов. В ней соседствуют несколько mutations, timers, network callbacks и события интерфейса. Даже если внутри есть длинный участок, привязать его к одной строке кода уже трудно. Второй сбой — смотреть только итоговую шкалу и не проверять DOM after. Тогда мы не знаем, рисовали ли именно ожидаемый элемент или обработчик вообще выбрал другую ветку. Третий — считать, что style, layout и paint всегда существуют как отдельные и одинаково подписанные карточки. Это удобная ментальная модель, но не API контракта между приложением и браузером.
Сильнее работает обратная формулировка: «после className X → Y я хочу понять, какая работа возникла в выбранной записи и можно ли убрать её без изменения result». Она запрещает лечить симптом удалением видимого эффекта. Если карточка должна стать is-ready, действие не считается успешным, пока after-state не сохранён. Производительность и функциональный result проверяются вместе, а не в разных спорящих задачах.
Ограничения модели
Учебный порядок из пяти labels не раскрывает все случаи. Например, изменение может не требовать layout, браузер может отложить часть работы, а compositing зависит от свойств и движка. Модель также не содержит изображений, шрифтов, iframe, анимации, cache, процессора или расширений. Поэтому она не отвечает на вопрос «какой CSS property всегда дешёвый» и не годится для сравнения устройств. Её единственная роль — удержать связь между declared DOM result, порядком проверки и обратимым действием.
Источники ниже существуют до марта 2022 года: immutable HTML snapshot от 27 февраля, датированная W3C Working Group Note для animation timing и W3C draft Performance Timeline 2021 года. Они помогают не выдать модель за стандартную шкалу или trace. Ни один из них не задаёт synthetic units и boundary fixture. Перед переносом на приложение зафиксируйте версию браузера, сценарий и способ записи отдельно: это уже свойства реального исследования, которых в пакете намеренно нет.
Следующий проверяемый шаг
Возьмите один реальный button или input, составьте таблицу before/after и сделайте единственную запись вокруг одного действия. Результатом следующего шага должен стать не «страница быстрее», а проверяемый артефакт: ссылка на handler, DOM-result и один выделенный диапазон trace. Затем внесите обратимое изменение и повторите тот же сценарий. Если after-state сохранён, а связанная работа стала понятнее, диагностика уже стала дешевле — без придуманной production-метрики.
Проверяемые источники
- WHATWG HTML: immutable snapshot 2022-03-01 — первичный снимок HTML Standard от 27 февраля 2022 года. Он задаёт алгоритм update the rendering и подчёркивает, что шаги зависят от user agent; он не устанавливает числовой бюджет и не обещает одинаковую последовательность внутренних событий во всех браузерах.
- W3C Timing control for script-based animations, Working Group Note от 22 сентября 2015 года — датированная W3C Working Group Note от 22 сентября 2015 года, доступная к марту 2022-го. Она описывает requestAnimationFrame как запрос к user agent запланировать animation frame update; это не Recommendation. Fixture не реализует этот API и не измеряет частоту кадров.
- W3C Performance Timeline Level 2, Working Draft от 19 августа 2021 года — датированный первичный W3C draft о performance entries и их получении. Он не описывает labels fixture, не задаёт teaching boundary и не превращает synthetic units в настоящий trace или данные Performance panel.