8 lines
16 KiB
JSON
8 lines
16 KiB
JSON
{
|
||
"index": 210,
|
||
"slug": "editorial-2022-03-practice-browser-rendering",
|
||
"title": "Медленный рендер: как связать DOM-изменение с причиной в кадре",
|
||
"excerpt": "Если интерфейс дёргается после одного действия, не называйте виновником весь браузер. Зафиксируйте DOM-изменение, отделите JavaScript от работы rendering pipeline и проверьте гипотезу одной записью Performance panel.",
|
||
"contentHtml": "<p>После нажатия «Сохранить» карточка должна сменить состояние, но экран на мгновение замирает. Иногда задержка заметна только на слабом ноутбуке. Иногда она появляется после добавления списка, тени или анимации. Команда видит слово «медленный рендер» и начинает менять всё сразу: переносит обработчик, добавляет <code>debounce</code>, убирает CSS и обвиняет фреймворк. Цена такой ошибки — потраченные часы и изменённый интерфейс без доказательства, что причина исчезла.</p>\n<p>Рабочий тезис проще: исследуйте одно пользовательское действие и один наблюдаемый результат DOM. Сначала зафиксируйте, что сделал JavaScript. Затем проверьте, какая работа последовала за этим изменением в конкретной записи. Слова <code>style</code>, <code>layout</code>, <code>paint</code> и <code>composite</code> удобны как вопросы к записи. Они не гарантируют одинаковый внутренний порядок во всех браузерах и не заменяют trace.</p>\n<h2>Симптом и граница задачи</h2>\n<p>Возьмём карточку заказа с кнопкой <code>save-card</code>. До клика у неё класс <code>card is-pending</code>. После успешного ответа интерфейс должен получить <code>card is-ready</code>. Сетевой запрос в этой статье не является предметом измерения. Нас интересует момент, когда обработчик применяет новый класс, и работа, которую браузер выполняет, чтобы показать результат.</p>\n<p>У такой задачи есть проверяемая граница: один клик, один target, одно свойство и два состояния. Не «страница тормозит», а «после клика на <code>save-card</code> меняется <code>className</code> с X на Y». Граница не ускоряет страницу сама. Она убирает лишние события из расследования и позволяет повторить тот же сценарий после правки.</p>\n<figure><img src=\"/assets/editorial/2022/browser-rendering-timeline-2022.svg\" alt=\"Учебная схема: после DOM-мутации показаны стадии style, layout, paint и composite, а synthetic units отделены от реального trace\" loading=\"lazy\" /><figcaption>Учебная timeline помогает задать порядок вопросов. Она не измеряет миллисекунды и не является записью Performance panel.</figcaption></figure>\n<h2>Механизм: от записи JavaScript к кадру</h2>\n<p>JavaScript меняет состояние документа. Например, обработчик записывает новое значение <code>className</code>. После этого user agent может пересчитать применимые стили, геометрию и визуальное представление. Часть работы может быть объединена, отложена или выполнена иначе. Приложение не получает универсальный контракт «после каждой записи всегда будут пять событий».</p>\n<p>Для диагностики полезна учебная цепочка. <code>js-mutation</code> означает изменение DOM. <code>style</code> задаёт вопрос о применимых стилях. <code>layout</code> — вопрос о геометрии. <code>paint</code> — вопрос об обновлении визуального представления. <code>composite</code> — вопрос о сборке результата. Это карта рассуждения, а не названия, которые нужно буквально искать в каждом trace.</p>\n<p>Модель помогает увидеть и отрицательный путь. Если обработчик записал тот же класс, новый visual result мог не появиться. Если выбранный элемент не меняется, длинный участок записи может относиться к таймеру, соседней анимации или расширению браузера. Если после правки исчезла задержка, но исчез и обязательный класс <code>is-ready</code>, это не исправление. Функциональный результат и производительность нужно проверять вместе.</p>\n<h2>Минимальный пример</h2>\n<p>Ниже — учебный код. Он показывает границу действия и ставит метки, чтобы связать запись с обработчиком. Он не обещает конкретное время, FPS, INP или порядок внутренних событий браузера.</p>\n<pre><code>const card = document.querySelector('#save-card');\n\nfunction setCardState(from, to) {\n if (!card || card.className !== from) {\n return false;\n }\n\n performance.mark('card-state-before');\n card.className = to;\n performance.mark('card-state-after');\n performance.measure(\n 'card-state-change',\n 'card-state-before',\n 'card-state-after'\n );\n return true;\n}\n\nbutton.addEventListener('click', () => {\n setCardState('card is-pending', 'card is-ready');\n});</code></pre>\n<p>Метки ограничивают время JavaScript между двумя точками. Они не доказывают длительность layout или paint. Для этого нужна запись исполнения в выбранной версии браузера. В реальном проекте сохраните также состояние до и после. Иначе вы можете сравнить две записи, в которых обработчик сделал разные вещи.</p>\n<h2>Симптом → причина → проверка → действие</h2>\n<div class=\"table-scroll\"><table><caption>Рабочая таблица для одного DOM-изменения</caption><thead><tr><th scope=\"col\">Симптом</th><th scope=\"col\">Причина</th><th scope=\"col\">Проверка</th><th scope=\"col\">Действие</th></tr></thead><tbody><tr><td>После клика интерфейс отвечает рывком</td><td>В одну проблему смешаны handler, layout и соседние события</td><td>Записать один клик и выделить его диапазон</td><td>Сузить запись до interaction и одного target</td></tr><tr><td>Длинный участок есть, но владелец неясен</td><td>Не зафиксированы before и after</td><td>Проверить класс в Elements и место записи в коде</td><td>Обновить контракт действия или остановить исследование</td></tr><tr><td>После изменения CSS стало «быстрее»</td><td>Исчез визуальный результат, а не причина</td><td>Сверить <code>className</code>, геометрию и screenshot</td><td>Откатить правку и проверить другой участок</td></tr><tr><td>В модели нет новой работы</td><td>from и to совпали либо выбран не тот элемент</td><td>Проверить no-op и повторить действие с чистым состоянием</td><td>Не искать layout в записи без новой мутации</td></tr><tr><td>Стадии trace отличаются от схемы</td><td>Учебные labels приняли за API браузера</td><td>Сверить документацию версии и фактические события</td><td>Описать наблюдаемую работу, не переименовывать её насильно</td></tr></tbody></table></div>\n<h2>Порядок проверки</h2>\n<ol><li>Опишите симптом как действие и наблюдаемый эффект: «после клика на <code>save-card</code> экран отвечает неровно, класс должен стать <code>card is-ready</code>».</li><li>Зафиксируйте target, свойство, значение до и значение после. Проверьте исходный DOM в Elements, а не только исходный код.</li><li>Откройте страницу в согласованном состоянии. Остановите лишние действия, очистите состояние сценария и запишите только один клик в Performance panel.</li><li>Найдите interaction и узкий диапазон вокруг неё. Сначала проверьте handler и DOM-result, затем рассматривайте связанные участки rendering.</li><li>Выберите одну гипотезу: лишний geometry read, широкий selector, дорогая визуальная область или лишняя работа JavaScript. Не меняйте несколько причин за один эксперимент.</li><li>Внесите обратимую правку. Сохраните ожидаемый DOM-result и опишите, какое наблюдение должно измениться, если гипотеза верна.</li><li>Повторите тот же сценарий в той же среде. Сравните запись, функциональный результат и отрицательный путь: no-op, ошибка или неверный target.</li><li>Сохраните trace или краткую ссылку на результат вместе с версией браузера и условиями запуска. Без этих условий цифры нельзя честно сравнивать.</li></ol>\n<h2>Что делать, если гипотеза не подтверждается</h2>\n<p>Если DOM после клика не соответствует контракту, остановитесь. Вы исследуете другой путь. Если метка JavaScript есть, а ожидаемой работы после неё нет, не делайте вывод «рендер бесплатный»: работу могли объединить или перенести. Если запись показывает длинный участок вне выбранной interaction, не приписывайте его кнопке. Сначала повторите сценарий с чистым состоянием.</p>\n<p>Не отключайте проверку стилей, не удаляйте обязательный визуальный результат и не добавляйте таймер как доказательство. <code>requestAnimationFrame</code> может помочь синхронизировать учебный эксперимент с обновлением кадра, но он не превращает callback в измерение причины. <code>PerformanceObserver</code> сообщает о поддерживаемых performance entries; он также не раскрывает весь внутренний pipeline и не заменяет запись DevTools.</p>\n<h2>Ограничения</h2>\n<p>Стоимость работы зависит от DOM, CSS, размера обновляемой области, устройства, браузера и состояния страницы. Один trace не описывает все устройства. Chrome DevTools показывает собственные представления и меняет интерфейс между версиями. Firefox и Safari могут группировать или называть события иначе. Даже в одном браузере кеш, шрифты, расширения, throttling и фоновые задачи меняют результат.</p>\n<p>Учебный пример с пятью labels и synthetic units нужен только для проверки порядка рассуждения. Synthetic units не являются миллисекундами, CPU time, FPS, LCP, INP или production-данными. В статье нет утверждения, что конкретная правка ускорила реальный продукт. Реальный вывод появляется только после повторяемой записи на нужном сценарии и подтверждения, что функциональный результат сохранился.</p>\n<h2>Критерий готовности</h2>\n<p>Диагностика готова, если выполнены четыре условия: записано одно действие; DOM before и after совпадают с контрактом; выбранный участок trace связан с этим действием и описан фактическими, а не учебными названиями; после обратимой правки повторная запись сравнима с исходной, а карточка сохраняет состояние <code>is-ready</code>. Если хотя бы одно условие не выполнено, результатом является новая гипотеза, а не заявление «рендер исправлен».</p>\n<h2>Проверяемые источники</h2>\n<ul><li><a href=\"https://html.spec.whatwg.org/multipage/webappapis.html#update-the-rendering\" target=\"_blank\" rel=\"noopener noreferrer\">WHATWG HTML Standard: update the rendering</a> — описывает алгоритм обновления rendering и не задаёт приложению фиксированный список внутренних событий.</li><li><a href=\"https://developer.chrome.com/docs/devtools/performance/reference\" target=\"_blank\" rel=\"noopener noreferrer\">Chrome DevTools: Performance features reference</a> — объясняет запись, навигацию по trace и связь выбранных участков с анализом.</li><li><a href=\"https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver\" target=\"_blank\" rel=\"noopener noreferrer\">MDN Web APIs: PerformanceObserver</a> — фиксирует назначение API для наблюдения за доступными performance entries.</li></ul>"
|
||
}
|