awesome-everything EN
↑ Обратно к восхождению

Браузер и фронтенд-рантайм

Observability в проде: LoAF, INP и полная поверхность атаки

Суть LoAF и INP в продовой телеметрии, скролл вне главного потока, display locking, reduced-motion, Web Workers, Service Workers, CI-тестирование и полная поверхность атаки пайплайна.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 18 min

Ваша страница набирает 95 в Lighthouse в офисе. На Pixel 4a пользователя в Джакарте с 3G-троттлингом INP — 340 мс. Это два разных измерения двух разных вещей. Только одно из них важно.

LoAF и INP: два взаимодополняющих сигнала

PerformanceLongAnimationFrameTiming (LoAF, поставлено в Chromium в 2023–2024) сообщает о каждом кадре длиннее 50 мс с разбивкой того, что доминировало: время рендеринга, блокирующий JS, форсированная компоновка. Это диагностический инструмент — говорит что работало долго, а не что почувствовал пользователь.

INP (Interaction to Next Paint) измеряет время от пользовательского input-события (клик, клавиша, тап) до следующего paint, видимо реагирующего. INP стал Core Web Vital в марте 2024, заменив FID. Плохой INP (>200 мс p75) почти всегда восходит к одной из двух проблем пайплайна:

  1. Длинная JS-задача в input-обработчике, откладывающая rAF
  2. Форсированная синхронная компоновка от чтения геометрии внутри обработчика

LoAF даёт данные для атрибуции в продакшене; INP даёт метрику, которую чувствует пользователь. Вместе они замыкают петлю от продовой телеметрии к конкретным диагностикам пайплайна.

Путь диагностики INP

INP > 200 мс — пользователь воспринимает взаимодействие вялым

↓ исследуйте с помощью

LoAF — какой кадр, что доминировало (JS / компоновка / рендеринг)

↓ отображается в
Длинная JS-задача в обработчике → уступить через scheduler.yield() или разбить с setTimeout
Форсированная синхронная компоновка в обработчике → двухпроходный батч чтений/записей

Скролл вне главного потока

Браузеры поставляют скролл на потоке compositor последнее десятилетие: когда пользователь скроллит обычную страницу, compositor транслирует viewport на GPU без касания главного потока. Страница может скроллить плавно даже пока идёт долгая JS-задача.

Подводный камень: любой элемент с JS scroll-обработчиком, подключённым не-passively (без {passive: true}), заставляет браузер откатиться на скролл главного потока, потому что обработчик может вызвать preventDefault(). Всегда передавайте passive: true слушателям scroll, wheel и touchmove, если только вам действительно не нужен preventDefault. Современные браузеры предупреждают в DevTools, когда non-passive слушатель задерживает скролл.

Display locking и content-visibility

Механизм за content-visibility: auto — «display locking»: браузер приостанавливает рендеринг заблокированного поддерева (без пересчёта стилей, без компоновки, без отрисовки) и заменяет его placeholder intrinsic-size. Когда поддерево пересекает viewport через внутренний IntersectionObserver браузера, оно разблокируется и рендерится.

Числа: таблица из 10 000 строк, ранее стоившая 1 200 мс style + layout, рендерится за ~10 мс с content-visibility: auto.

Компромисс: скролл в ранее заблокированную область кратко останавливается для рендеринга. Если строки дорогостоящие, виден однокадровый рывок на границе разблокировки. Пары с contain-intrinsic-size даёте браузеру реалистичный placeholder-размер, чтобы позиция скролла не прыгала.

Reduced-motion как клапан бюджета рендеринга

@media (prefers-reduced-motion: reduce) устанавливается пользователями, испытывающими укачивание или желающими снизить энергопотребление. Помимо доступности, это клапан бюджета рендеринга: любую compositor-управляемую анимацию можно заменить мгновенным изменением состояния при reduced-motion, освобождая compositor от покадровой работы. Пользователи с ограниченным зарядом на бюджетном Android-телефоне получают значимую экономию батареи.

Web Workers и разгрузка главного потока

Когда главный поток достигает потолка, единственный способ снизить его — перенести работу. Web Workers выполняют JS в отдельном потоке без доступа к DOM; сериализация через postMessage стоит ~1 мс/МБ, что оправдано для CPU-тяжёлых задач (парсинг большого JSON, сжатие, криптография, обработка разметки).

OffscreenCanvas даёт прямой доступ к canvas API из воркера, полностью обходя главный поток. SharedArrayBuffer + Atomics предоставляют примитивы синхронизации между потоками для высокочастотных данных (аудио, потоки сенсоров реального времени). Стоимость входа высока (structured cloning, архитектура передачи сообщений), но потолок производительности пропорционально выше: четырёхъядерный телефон с занятым главным потоком всё ещё имеет 3 незадействованных ядра, которые большинство приложений никогда не используют.

Service Worker и пайплайн первой отрисовки

Service Workers не часть пайплайна рендеринга, но критически влияют на его входные данные. Service Worker, отвечающий на запросы из кэша (cache-first для статических ресурсов, network-first для API), доставляет первую отрисовку за 50 мс на повторных посещениях вместо 500 мс — 4 кадра против 30 при 60 fps.

Ключевое ограничение: скрипт Service Worker работает в отдельном потоке, но его запуск (когда он перехватывает первый запрос) имеет небольшую латентность. Держите Service Worker тонким и быстрым; 200-мс запуск при перехвате fetch задерживает первый HTML-байт и весь пайплайн парсинга вместе с ним.

Performance CI: перехват регрессий до мерджа

Бюджеты удерживаются, только когда регрессии перехватываются до мерджа. Реалистичный CI-пайплайн имеет три уровня:

  1. Lighthouse CI в headless Chrome на каждом PR — ограничивает по абсолютным метрикам (LCP < 2.5 с, INP < 200 мс, CLS < 0.1), блокирует мердж при регрессии.
  2. Синтетические бенчмарки на критических сценариях (открыть страницу, проскроллить список 1000×, нажать 5 кнопок) — измеряет p95 длительности кадра и p95 LoAF, сравнивает с baseline-веткой.
  3. RUM (мониторинг реальных пользователей) в продакшене — отправляет перцентили INP, LCP, CLS в Datadog или подобное, алертит когда p75 INP пересекает 200 мс на любом сегменте пользователей (страна, устройство, версия приложения).

Без всех трёх уровней регрессия рендеринга попадает в продакшен, живёт там тихо месяцами и обнаруживается, когда жалоба пользователя заставляет кого-то смотреть.

Профилирование на реальном железе vs DevTools-троттлинг. «4-кратное замедление CPU» в DevTools Performance — приближение, не замена реального железа. M2 MacBook при 4-кратном замедлении всё равно имеет другую модель памяти, другой GPU и другой профиль теплового троттлинга, чем Pixel 6a. Профилируйте на физическом среднеценовом Android хотя бы раз в неделю с помощью Chrome для Android + удалённая отладка, и сравнивайте длительности кадров. Разница >30% означает мобильную регрессию, невидимую на десктопе.

Граничные случаи

Layer squashing — ответ compositor на взрыв слоёв из-за перекрытия. Когда много соседних неанимирующихся элементов продвигаются (из-за перекрытия с единственным анимированным слоем), compositor сжимает их в единый общий «squashed layer» bitmap. Это уменьшает GPU-память ценой большего одиночного bitmap — если один элемент в squash меняется, весь squashed layer должен перерисоваться. Эвристика squashing не управляется пользователем; единственное лечение — изолировать анимированные слои от неанимирующихся соседей, чтобы правило перекрытия не срабатывало.

Спроектируй

Спроектируйте поведение скролла для виртуализированного списка чата, содержащего 50 000 сообщений, с достижением 60 fps на среднеценовом Android-телефоне.

  • Бюджет кадра: 16.67 мс. Реалистичный бюджет главного потока после оверхеда браузера: ~10 мс.
  • Компоновка не должна зависеть от off-screen строк.
  • Composite-only путь во время скролла. Компоновка и отрисовка допустимы только когда новые строки входят в viewport.
  • GPU-память: предположим 200 МБ доступно. Количество слоёв должно оставаться ниже 30 в любой момент.
  • Resize-обработчик не должен зацикливать чтения и записи (без форсированного reflow).
Викторина

Click-обработчик выполняется 80 мс. Пользователь воспринимает задержку перед обновлением UI. Какой Core Web Vital это измеряет и что с этим связано в пайплайне?

Викторина

Touchmove-слушатель подключён без `{passive: true}`. Какая регрессия производительности это вызывает?

Вспомните перед уходом
  1. 01
    Что сообщает LoAF, и чем отличается от INP?
  2. 02
    Почему non-passive scroll/touchmove-слушатели вызывают jank?
  3. 03
    Назовите три уровня CI для перехвата регрессий рендеринга до продакшена.
Итог

LoAF атрибутирует долгие кадры; INP измеряет воспринятую латентность взаимодействия — плохой INP (>200 мс p75) восходит к длинной JS-задаче или форсированной синхронной компоновке в input-обработчике. Non-passive scroll-слушатели откатываются на скролл главного потока; passive: true восстанавливает compositor-скролл. content-visibility: auto display-блокирует off-screen поддеревья, снижая 1200-мс компоновку до 10 мс. @media (prefers-reduced-motion) — одновременно требование доступности и оптимизация бюджета рендеринга. Web Workers разгружают CPU-тяжёлый JS с главного потока; Service Workers обслуживают первую отрисовку из кэша. CI нужны три слоя: Lighthouse на PR, синтетический p95 LoAF на критических путях и RUM-алерты на продовые INP-перцентили. Полная поверхность атаки — блокирующие парсер скрипты, раздутый CSS, сложные селекторы, глубокие flex-компоновки, paint-тяжёлые фильтры, переполнение слоёв, layout thrash, non-passive слушатели, задачи >50 мс и долгие input-обработчики — отображается один к одному на конкретные стадии пайплайна.

Связанные уроки
встречается в162
Продолжить восхождение ↑Render pipeline: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources4
expand
  1. 01
  2. 02
  3. 03
  4. 04

Trademarks belong to their respective owners. Editorial reference only.