Проблема возникает, когда на карточке меняется класс, а команда спорит, «это JavaScript» или «это CSS». Оба ответа могут быть неполными. JavaScript может только записать className; дальше браузер решает, нужна ли работа со style, геометрией, пикселями и слоями. Цена неверной модели заметна сразу: удаляют часть handler, хотя DOM-result должен остаться, либо переписывают style rule, который не относится к выбранному действию. В итоге меняется код, но причина в Performance panel остаётся неподтверждённой.
Нужна простая причинная граница: изменение значения — это вход; visual result — это выход; между ними есть работа, которую нужно наблюдать на одной записи. Нельзя честно назвать одинаковый конвейер для каждого браузера и свойства. Но можно не смешивать вопросы. Что изменил handler? Какой result стал виден? Какая работа находится после этого действия в выбранном диапазоне? Какое минимальное обратимое действие проверит гипотезу? Такой порядок даёт техническую речь без лозунга «избегайте layout».
Пять labels модели и их ответственность
В модели js-mutation применяет объявленное изменение className. style означает, что нужно определить применимые стили после нового класса. layout представляет расчёт положения карточки после style. paint представляет подготовку её пикселей. composite представляет сборку подготовленного результата. Это не протокол браузера и не словарь Performance panel. Это пять вопросов, которые удобно держать рядом с DOM contract, чтобы не назвать одну широкую область причиной до исследования.
| Label модели | Вход | Результат модели | Вопрос к записи | Неправильный вывод |
|---|---|---|---|---|
js-mutation | handler и old/new className | обновлённый DOM object | какой код сделал mutation? | «всё после клика — JavaScript» |
style | новый selector state | resolved style в модели | есть ли связанная работа со style? | «class всегда означает одну цену» |
layout | style, влияющий на геометрию | placement карточки | какая геометрия стала нужна? | «layout всегда отдельная карточка» |
paint | визуальное представление | prepared pixels | какой визуальный участок обновился? | «paint равен CSS rule» |
composite | prepared result | собранный frame result | какая финальная работа видна? | «это автоматически дешёвый этап» |
Слово «модель» здесь делает важную работу. HTML Standard говорит о том, что user agent обновляет rendering, но не обещает авторскому коду доступ к одной неизменной внутренней очереди. Trace format Chromium описывает, как события следа представляются в записи, а не контракт «после className всегда идёт именно такой набор работ». Поэтому между документом, записью и проектным решением есть граница: стандарт и source помогают назвать предмет; запись показывает поведение выбранной версии; решение выбирает изменение и способ его отменить.
Почему порядок важнее суммы
Fixture ставит units 1, 2, 4, 3 и 1. Их сумма 11 пересекает teaching boundary 10. Числа специально не похожи на duration: это не миллисекунды, не CPU time и не FPS. Важен порядок. Если мы видим, что договорённая mutation не произошла, бессмысленно обсуждать paint. Если mutation есть, но DOM after не тот, сначала исправляется функциональный путь. Если result правильный, можно перейти к следующему вопросу и ограничить гипотезу. Сумма в такой модели нужна только для теста определённой ветки, а не для рейтинга производительности.
// Fixture хранит этот контракт внутри script; пример показывает его вход.
const update = {
target: "save-card",
property: "className",
from: "card is-pending",
to: "card is-ready",
};
// changed route: js-mutation → style → layout → paint → composite
// units: 1 + 2 + 4 + 3 + 1 = 11 synthetic units
// boundary 10 ⇒ teaching label jank-boundary, not milliseconds.
node web/scripts/upgrade-2022-03.mjs --verify-fixture
Валидный route проходит строго через пять labels. В fixture проверяется exact order и то, что у каждой стадии есть причина. Это полезно как защита от случайной правки самой модели: нельзя переставить paint перед layout и продолжать рассказывать тот же учебный маршрут. Но fixture не проверяет движок. Её нельзя запускать вместо Performance panel, сравнивать с отчётом Lighthouse или приклеивать к CI как замер страницы. Она проверяет только контракт статьи: что заявленные вход, стадийность, boundary и result не противоречат друг другу.
Jank boundary — условие остановки, а не диагноз
Название jank-boundary здесь намеренно ограничено. Оно срабатывает, когда 11 условных единиц больше 10. Оно не говорит, что пользователь увидел рывок, что кадр пропущен или что устройство не успело выполнить работу. В настоящем исследовании такие выводы требуют настоящей записи, условий запуска и наблюдения конкретного браузера. В учебной модели boundary отвечает на иной вопрос: есть ли заранее определённый случай, в котором автор обязан не продолжать оптимизацию на словах, а перейти к следующей проверке.
Это похоже на type check. Компилятор не измеряет удобство интерфейса, но останавливает конкретное несоответствие. Здесь ограничение останавливает расплывчатый вывод: «после клика что-то много происходило». Мы получаем ровно: «для declared change модель прошла определённую границу; теперь надо собрать реальный evidence на этой границе». Такая дисциплина не делает браузер предсказуемее, зато не позволяет из красивой схемы сделать выдуманный benchmark.
Две ветки, которые должны быть пустыми
No-op route важен не меньше changed route. Если to совпадает с текущим className, fixture возвращает no-op, не выдаёт frameId и оставляет stages пустыми. В модели это означает: новое действие не объявлено, поэтому конвейер не планируется. Это не утверждение, что реальный браузер никогда не делает никакой работы рядом с одинаковой записью свойства. Это защита контракта: статья не должна изображать кадр, если сама заявляет отсутствие DOM result.
Error route столь же полезен. Если передан textContent вместо разрешённого className, либо target/from не совпадают с контрактом, модель возвращает invalid-update-contract и также не меняет DOM object. Ошибка не превращается в «нулевой быстрый кадр». Она означает, что у автора нет основания строить причинную историю. В реальной задаче это момент вернуться к handler, selector или тесту UI, а не искать удачный цвет в диаграмме.
Маршрут: симптом → причина → проверка → действие
- Симптом. Один клик меняет интерфейс неровно, а команда называет причину то JS, то CSS.
- Причина. Не записаны mutation и result, поэтому разные стадии и соседняя работа попали в одну историю.
- Проверка контракта. Сверьте target, property, from и to. Если это не className
save-card, fixture честно идёт в error route. - Проверка порядка. Для changed route удерживайте цепочку mutation → style → layout → paint → composite как порядок вопросов, не как имена trace events.
- Проверка реальности. В Performance panel выделите только interaction и сопоставьте её с кодом. Если доменный result другой, начните новый сценарий.
- Действие. Поменяйте один owner: handler, правило style или geometry read. Сохраните способ rollback до повторной записи.
Обратимость не должна ломать визуальный result
Проверка улучшения без отмены легко обманывает. Можно убрать className change и увидеть меньше работы, но карточка больше не сообщает готовность. Поэтому fixture создаёт второй frame: rollback меняет card is-ready обратно в card is-pending, получает отдельный teaching-frame-08 и проходит тот же порядок labels. Assertion требует, чтобы final DOM равнялся base DOM. Это не browser undo; это минимальная проверка, что мы умеем вернуть исходный функциональный контракт.
// В модели обратимое действие — отдельный контракт, не 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 этот пример не создаёт.
В приложении rollback может быть feature flag, локальная правка selector или отдельный commit. Форма не важна, пока она возвращает ожидаемый DOM result и может быть повторена. Важно не смешивать rollback с отсутствием изменения: no-op ничего не проверяет, потому что не создаёт нового result. Обратимое изменение создаёт result, наблюдает его, а затем осознанно возвращает старый. Именно поэтому оно пригодно для исследования, а не только для безопасного релиза.
Ограничения и историческая рамка
Март 2022 — подходящая рамка для разговора о DevTools trace и frame update, но не повод использовать будущие интерфейсные метрики или поздние UI Performance panel. Здесь нет INP, interaction breakdown, современных DevTools labels и обещаний о RAIL. Ссылки зафиксированы до заданного месяца: WHATWG commit от 27 февраля 2022 года, W3C Working Group Note 2015 года и W3C Performance Timeline draft августа 2021-го. Они первичны или официальны, но не превращают эту схему в нормативную декомпозицию render pipeline.
Следующий проверяемый шаг: добавьте в задачу ровно одну строку «mutation, before, after, owner, rollback». После первой реальной записи оставьте ссылку на выделенный range и короткую гипотезу уровня «проверить geometry read в handler», а не «убрать layout». Если следующий trace не подтверждает связь, вернитесь к контракту. Это быстрее, чем защищать оптимизацию, которая была выбрана до того, как появилось доказательство.
Проверяемые источники
- 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.