В записи Performance panel легко найти широкий участок работы и ещё легче назвать его «проблемой рендера». Но из такой фразы нельзя получить безопасную правку. Неизвестно, какой пользовательский шаг начался раньше, какой DOM-result был нужен и что сломается, если просто убрать часть кода. Цена — оптимизация, которая делает график спокойнее, но оставляет кнопку в старом состоянии, убирает сообщение об ошибке или переносит работу в другой момент без объяснения.
Полевой разбор начинается с малого: один input, один DOM-result и одна запись. Для примера пользователь нажимает «Сохранить», а карточка меняет className с card is-pending на card is-ready. Мы не утверждаем, что такой сценарий произошёл в реальном продукте. Это учебный case. Его задача — показать маршрут, где каждое утверждение можно заменить артефактом своего проекта: DOM snapshot, ссылка на handler, выбранный range trace и обратимая правка.
Сначала зафиксировать то, что нельзя потерять
До записи сформулируйте result в одном предложении: «после submit карточка остаётся той же, но className становится is-ready». Сохраните before/after в Elements или в UI-test expectation. Это удерживает границу функциональности. Если следующая правка убирает visual state, она не проходит, даже если trace выглядит лучше. Производительность не является отдельным экраном без результата; пользователь нажал кнопку именно ради изменения интерфейса.
| Артефакт | Что записать | Критерий готовности | Типичная ловушка |
|---|---|---|---|
| Сценарий | одно действие, исходный экран | другой человек повторяет его | записать длинную серию кликов |
| DOM contract | target/property/before/after | after виден после действия | считать код handler доказательством результата |
| Recording range | одна interaction и узкий диапазон | range соотносится со сценарием | читать всю timeline сразу |
| Гипотеза | один owner и один вопрос | можно опровергнуть следующей записью | объявить виновным весь CSS или framework |
| Rollback | как вернуть old result | после отмены DOM равен before | путать отмену с no-op |
Fixture вводит ровно те же поля, но оставляет их учебными. Changed route получает frame id, пять labels и total 11 synthetic work units. Boundary 10 даёт jank-boundary. Никаких duration, CPU, frame rate или реального UI в ней нет. Поэтому нельзя скопировать число 11 в performance ticket. Можно скопировать структуру вопроса: почему после этой mutation в выбранной записи находится работа, какой owner способен её поменять и как проверить, что DOM result не потерян.
Читать запись от действия к коду
Открыв Performance panel, сначала сузьте время: начните recording на уже подготовленном экране, выполните одно действие и остановите его сразу после visual result. Выберите interaction и найдите связанный участок на main thread. Затем вернитесь в код и спросите, что именно меняет DOM. Не делайте обратное — не берите самый широкий блок и не ищите ему удобное объяснение. У широкого блока могут быть unrelated timers, обработка сети, расширение браузера или соседняя animation. Без связи со сценарием у него нет владельца для правки.
После связи переходите по одному вопросу. Если handler читает геометрию после записи style, проверяем этот read. Если DOM result появляется раньше, а видимая работа следует позже, проверяем CSS rule и область update. Если mutation вообще не соответствует before/after, не продолжаем investigation: это другой сценарий. Такой порядок не обещает быстрый результат, но экономит время на спорах. Каждый шаг либо сужает причину, либо честно возвращает к отсутствующему артефакту.
// Сначала читается изменение, затем его одна frame-запись.
const question = {
mutation: "className: card is-pending → card is-ready",
frameId: "teaching-frame-07",
inspectOrder: ["js-mutation", "style", "layout", "paint", "composite"],
};
// В реальной записи ищут соответствующий interaction и события этой записи.
// Эти labels не являются именами событий Performance panel.
Три остановки, которые защищают от ложного вывода
Первая остановка — error route. В fixture неизвестное property, target или from возвращают invalid-update-contract, stages пусты и DOM не меняется. Это означает: модель не знает, что исследует. В проекте аналогом будет handler, который не является владельцем нужного result, или тест, который не воспроизводит действие. Нельзя назвать такой маршрут «быстрым»: он просто не прошёл входную проверку.
Вторая остановка — no-op. Если old value равен new value, model возвращает same-className-no-frame-planned. Реальный браузер может иметь другую сопутствующую работу, но конкретный contract не создал нового visual result. Поэтому recording надо пересобрать: возможно, действие уже было выполнено, state пришёл из cache или выбран неверный элемент. Продолжать искать layout в таком case — значит исследовать не то, что пользователь сделал.
Третья остановка — teaching boundary. Она не говорит «нашли jank», а запрещает довольствоваться суммой. Когда 11 больше 10, требуется одно ограниченное действие и повтор. Сумма не выбирает действие сама. Владелец может быть handler, selector, geometry read или код, который ставит className. Без связи с реальной записью она остаётся условной веткой fixture. Это ограничение полезно тем, что не позволяет превратить диаграмму в результат расследования.
Обратимое действие: одна гипотеза, один owner
Хорошая первая правка маленькая и отменяемая. Например, временно вынести один geometry read из handler, изменить локальное правило или отключить один visual effect за feature toggle. Нельзя заранее обещать, что это уменьшит layout или paint: сначала надо посмотреть новую запись. У правки есть owner, expected DOM after и путь rollback. Если change не сохраняет result, она не проходит функциональный gate. Если после change связь с записью неясна, evidence недостаточен для сильного вывода.
Fixture проверяет обратимость явно. Forward переводит карточку в is-ready; rollback — обратно в is-pending. У rollback отдельный frame id и тот же учебный порядок стадий. Итоговый DOM должен совпасть с base DOM. Этот test не говорит, что браузер отменяет frame или что в реальном интерфейсе нет побочных эффектов. Он удерживает более скромный контракт: автор умеет вернуть наблюдаемое состояние и не выдаёт «удалить изменение» за оптимизацию.
// В модели обратимое действие — отдельный контракт, не Ctrl+Z браузера.
const apply = { property: "className", from: "card is-pending", to: "card is-ready" };
const rollback = { property: "className", from: "card is-ready", to: "card is-pending" };
// После rollback result.dom.className снова "card is-pending".
// Отмена проходит тот же порядок стадий и получает свой frameId.
// Никаких DOM API и actual Performance recording этот пример не создаёт.
Маршрут: симптом → причина → проверка → действие
- Симптом. В конкретном сценарии интерфейс отвечает неровно, но trace содержит слишком много событий.
- Причина. Нет связи между user action, DOM result и выбранным range; слова style/layout/paint стали общими ярлыками.
- Проверка result. Зафиксируйте target, property, before и after. Если after не тот, расследование заканчивается до Performance panel.
- Проверка range. Повторите один action, выделите одну interaction и сопоставьте её с handler. Отделите этот range от соседних callbacks.
- Проверка гипотезы. Назовите один owner и один вопрос, который можно опровергнуть новой записью. Не переносите synthetic labels на реальные event names.
- Действие и rollback. Внесите маленькую обратимую правку, повторите сценарий и проверьте и DOM after, и новый range.
Что положить в результат исследования
Хороший результат помещается в несколько строк: версия браузера и условия записи; шаг действия; DOM before/after; ссылка на handler; выбранный range; один наблюдаемый факт; одна гипотеза; commit или toggle для rollback. Здесь нет места фразе «ускорили рендер», пока нет измерения, метода и сравниваемого варианта. Даже если реальная запись стала лучше, сначала нужно назвать, что именно изменилось и при каких условиях. Это язык системного практика 2022 года: достаточно строгий, чтобы другой инженер продолжил проверку, и достаточно скромный, чтобы не придумать эффект.
Исторические источники помогают обозначить границы. WHATWG snapshot от 27 февраля 2022 года показывает, что rendering обновляет user agent, а не приложение по фиксированному авторскому списку. W3C Working Group Note объясняет назначение animation frame callback, но fixture не вызывает requestAnimationFrame. Performance Timeline draft описывает работу с performance entries, но учебный result не является entry или trace. На этом материале нельзя обосновать актуальный интерфейс DevTools или поздние performance guidance; для настоящей записи всегда проверяйте версию конкретного браузера.
Следующий проверяемый шаг
Выберите один button на своей странице и проведите маршрут один раз без оптимизации. Итогом будут не числа, а четыре артефакта: DOM before/after, ссылка на mutation, один range и одна отменяемая гипотеза. Запустите fixture пакета, чтобы увидеть, что no-op, error и rollback не маскируются под успешный кадр. Затем проведите реальную запись по тому же контракту. Если она не подтверждает связь, это полезный результат: нужно менять сценарий или вопрос, а не публиковать удобную причину.
Проверяемые источники
- 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.