Files
progcode/editorial/agent-rewrites/209.json
T
huncode 2d914b543f
Build and deploy / deploy (push) Failing after 15s
Publish rewritten technical article archive
2026-08-02 22:19:34 +03:00

8 lines
16 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"index": 209,
"slug": "editorial-2022-03-mechanism-browser-rendering",
"title": "Почему браузер не успевает отрисовать изменение интерфейса",
"excerpt": "Разбор рывка после изменения DOM: как отделить работу JavaScript от style, layout, paint и composite, проверить одну запись и не исправить производительность ценой сломанного состояния интерфейса.",
"contentHtml": "<p>Кнопка меняет класс карточки, но экран отвечает с рывком. В Performance panel виден длинный участок работы. Команда называет виновником то JavaScript, то CSS и сразу меняет код. Это опасный диагноз. Если убрать обработчик, интерфейс может стать плавнее только потому, что карточка перестанет переходить в состояние «готово». Цена ошибки — потерянный пользовательский результат, лишний релиз и новая задержка, которую теперь труднее связать с причиной.</p>\n<p>Тезис статьи простой: изменение DOM — это вход, а видимое состояние — выход. Между ними браузер выполняет несколько видов работы. Их удобно разделить на вопросы: кто сделал mutation, какие стили стали применимы, нужна ли новая геометрия, какую область надо закрасить и как собрать результат на экране. Этот порядок помогает сузить гипотезу. Он не является обещанием одинакового внутреннего конвейера во всех браузерах.</p>\n<h2>Что происходит после изменения DOM</h2>\n<p>Рассмотрим учебный, но реалистичный сценарий. После отправки формы обработчик меняет класс карточки с <code>card is-pending</code> на <code>card is-ready</code>. Новый класс меняет цвет, высоту и подпись. Сначала нужно доказать сам факт изменения. Если <code>className</code> не изменился или выбран не тот элемент, искать дорогой paint бессмысленно.</p>\n<pre><code>const card = document.querySelector('.card');\n\nfunction markReady() {\n card.className = 'card is-ready';\n}\n\nbutton.addEventListener('click', markReady);</code></pre>\n<p>После mutation браузер пересчитывает применимые стили. Если свойства влияют на геометрию, он может пересчитать положение и размеры элементов. Затем он подготавливает пиксели для изменившейся области. В некоторых случаях отдельные слои можно собрать без полной перекраски. В DevTools эти этапы могут отображаться разными событиями, объединяться или отсутствовать в выбранном диапазоне. Поэтому названия <code>style</code>, <code>layout</code>, <code>paint</code> и <code>composite</code> ниже — рабочие labels для расследования, а не API-контракт.</p>\n<p>JavaScript часто запускает цепочку, но не равен всей цепочке. Обратная ошибка тоже встречается: строку CSS считают причиной только потому, что после неё виден layout. Нужна связь с действием пользователя, конкретным DOM-result и выбранным диапазоном записи.</p>\n<h2>Один пример: как чтение геометрии усиливает работу</h2>\n<p>Проблема становится заметнее, когда код сначала меняет стиль, а затем немедленно читает геометрию. Браузер ещё может откладывать расчёт. Чтение <code>offsetHeight</code> требует актуального значения, поэтому движок вынужден завершить нужную часть расчёта прямо внутри обработчика.</p>\n<pre><code>function resizeCard() {\n card.style.width = '320px';\n const height = card.offsetHeight;\n card.style.height = `${height}px`;\n}</code></pre>\n<p>Этот пример учебный. Он не доказывает, что каждый вызов приведёт к forced layout в каждом движке. Он показывает условие для проверки: запись стиля и чтение геометрии идут рядом, а между ними нет границы, на которой можно увидеть фактическую работу. В реальном расследовании надо открыть запись выбранного браузера и проверить, какой обработчик вызвал layout и какие узлы попали в расчёт.</p>\n<p>Без чтения записи нельзя объявлять свойство «дорогим» навсегда. Иногда браузер обновит только часть дерева. Иногда layout уже был нужен по другой причине. Иногда изменение попадёт в отдельный слой и не потребует полной перекраски. Поэтому заменять всё на <code>transform</code> или удалять visual state без проверки результата — такой же плохой путь, как игнорировать layout.</p>\n<h2>Как читать причинную цепочку</h2>\n<table><caption>Симптом → причина → проверка → действие</caption><thead><tr><th>Симптом</th><th>Возможная причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td>После клика меняется не тот элемент</td><td>Неверный target или selector</td><td>Сравнить DOM before/after в Elements и код обработчика</td><td>Исправить контракт изменения; не оптимизировать trace</td></tr><tr><td>Долгий участок появляется после записи класса</td><td>Новый стиль требует расчёта геометрии или paint</td><td>Выделить одну interaction и посмотреть связанные Layout/Paint events</td><td>Проверить одно свойство или правило и повторить запись</td></tr><tr><td>Layout находится внутри обработчика</td><td>Чтение геометрии после записи стиля</td><td>Перейти из события к строке кода, которая читает размер или позицию</td><td>Разнести запись и чтение, либо проверить другой способ обновления</td></tr><tr><td>График стал короче, но UI не меняется</td><td>Оптимизация убрала функциональный result</td><td>Сверить ожидаемый className, текст и размеры after-state</td><td>Откатить изменение и выбрать более узкую гипотезу</td></tr><tr><td>В разных браузерах видны разные события</td><td>Разные движки и версии по-разному показывают внутреннюю работу</td><td>Зафиксировать браузер, версию, сценарий и диапазон записи</td><td>Сравнивать результат и пользовательский симптом, а не одинаковые labels</td></tr></tbody></table>\n<h2>Иллюстрация одной проверки</h2>\n<figure><img src=\"/assets/editorial/2022/browser-rendering-pipeline-2022.svg\" alt=\"Схема пути от изменения className через style, layout, paint и composite к видимому кадру\"><figcaption>Схема разделяет вопросы расследования. Она не утверждает, что выбранный браузер всегда создаёт пять отдельных событий с такими именами.</figcaption></figure>\n<p>Начните слева с DOM-контракта: какой элемент меняется, какое свойство записывается, что было до изменения и какой результат ожидается. Затем связывайте каждый следующий вопрос с одной и той же записью. Если after-state не совпал с ожиданием, проблема ещё функциональная. Если результат совпал, ищите работу, которая возникла после конкретной mutation. Только после этого выбирайте правку.</p>\n<h2>Порядок расследования</h2>\n<ol><li>Опишите симптом одним предложением: действие, видимый сбой и цену для пользователя.</li><li>Зафиксируйте DOM before и after. Укажите target, свойство и обработчик.</li><li>Откройте Performance panel в согласованном браузере и запишите только один сценарий, без серии лишних кликов.</li><li>Найдите interaction и сузьте диапазон до действия. Не делайте вывод по всей временной шкале.</li><li>Сопоставьте событие с кодом. Проверьте, есть ли после mutation чтение геометрии, массовое изменение DOM или правило, меняющее размеры.</li><li>Сформулируйте одну гипотезу и измените только один owner: обработчик, правило CSS или способ обновления свойства.</li><li>Повторите тот же сценарий. Сравните не только длительность работы, но и DOM after, визуальный результат и отсутствие нового симптома.</li><li>Сохраните способ отката. Если гипотеза не подтверждается, верните прежний код и начните с другого участка цепочки.</li></ol>\n<h2>Отрицательный путь: когда оптимизировать нечего</h2>\n<p>Есть два честных случая остановки. Первый — no-op: код записывает то же значение, которое уже установлено. Нового DOM-result нет, поэтому нельзя строить историю «дорогого кадра» только по факту клика. Проверьте, не повторяется ли действие из-за двойного обработчика или лишнего рендера.</p>\n<p>Второй — invalid update. Обработчик выбирает отсутствующий элемент, передаёт не то свойство или рассчитывает значение из устаревшего состояния. Такой путь нельзя считать быстрым кадром с нулевой стоимостью. Сначала исправьте контракт и повторите запись. Иначе оптимизация скроет функциональную ошибку.</p>\n<h2>Ограничения модели</h2>\n<p>Учебная цепочка не отвечает на вопрос, какое CSS-свойство всегда дёшево. Стоимость зависит от дерева, размера изменённой области, движка, версии браузера, устройства, шрифтов, изображений, анимации и соседней работы. <code>composite</code> также не означает автоматически «бесплатно»: сборка слоёв использует ресурсы устройства и может стать узким местом.</p>\n<p>Нельзя переносить примерные числа из диаграмм или одного trace в production-бюджет. Нельзя объявлять проблему доказанной по цвету события в панели. Нельзя сравнивать две записи, если изменились браузер, throttling, состояние данных или сам DOM-result. Источники объясняют терминологию и инструмент. Они не заменяют запись именно вашего сценария.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Расследование готово, когда есть один воспроизводимый сценарий, ссылка на обработчик, DOM before/after, выделенный диапазон записи и одна подтверждённая или опровергнутая гипотеза. После правки тот же сценарий даёт ожидаемый after-state, а выбранная работа уменьшилась или стала понятнее без переноса симптома в другое место. Если это учебный пример, так и пометьте его. Не называйте условные значения миллисекундами и не выдавайте их за результат production-измерения.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://developer.chrome.com/docs/devtools/performance\" target=\"_blank\" rel=\"noopener\">Chrome DevTools: Analyze runtime performance</a> — официальный порядок записи, чтения flame chart и поиска forced layout.</li><li><a href=\"https://html.spec.whatwg.org/multipage/rendering.html\" target=\"_blank\" rel=\"noopener\">WHATWG HTML Standard: Rendering</a> — официальное описание rendering как поведения user agent, а не фиксированного набора trace-событий.</li><li><a href=\"https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/Critical_rendering_path\" target=\"_blank\" rel=\"noopener\">MDN: Critical rendering path</a> — справочная схема перехода от HTML/CSS/JavaScript к layout и pixels с оговорками о измерении.</li></ul>"
}