API длинных кадров анимации

API длинных кадров анимации (LoAF — произносится как Lo-Af) — это обновление API длинных задач , позволяющее лучше понять медленные обновления пользовательского интерфейса (UI). Это может быть полезно для выявления кадров медленной анимации, которые могут повлиять на метрику Interaction to Next Paint (INP) Core Web Vital, которая измеряет скорость реагирования, или для выявления других ошибок пользовательского интерфейса, влияющих на плавность .

Статус API

Поддержка браузера

  • 123
  • 123
  • Икс
  • Икс

Источник

После первоначального пробного перехода от Chrome 116 к Chrome 122 API LoAF был переведен из Chrome 123 .

API длинных задач

Поддержка браузера

  • 58
  • 79
  • Икс
  • Икс

Источник

API Long Animation Frames — это альтернатива API Long Tasks, который уже некоторое время доступен в Chrome (начиная с Chrome 58). Как следует из названия, API длинных задач позволяет отслеживать длинные задачи, то есть задачи, которые занимают основной поток в течение 50 миллисекунд или дольше. Длинные задачи можно отслеживать с помощью интерфейса PerformanceLongTaskTiming с помощью PeformanceObserver :

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'longtask', buffered: true });

Длинные задачи могут вызвать проблемы с реагированием. Если пользователь пытается взаимодействовать со страницей (например, нажать кнопку или открыть меню), но основной поток уже выполняет длинную задачу, то взаимодействие пользователя задерживается в ожидании завершения этой задачи.

Чтобы улучшить скорость реагирования, часто советуют разбивать длинные задачи . Если вместо этого каждая длинная задача разбивается на серию нескольких более мелких задач, это может позволить выполнять более важные задачи между ними, чтобы избежать значительных задержек в реагировании на взаимодействия.

Поэтому при попытке улучшить скорость реагирования в первую очередь часто приходится запускать трассировку производительности и анализировать длинные задачи. Это можно сделать с помощью лабораторного инструмента аудита, такого как Lighthouse (который имеет функцию «Избегать длинных задач основного потока »), или путем просмотра длинных задач в Chrome DevTools .

Лабораторное тестирование часто является плохой отправной точкой для выявления проблем с реагированием , поскольку эти инструменты могут не учитывать взаимодействия, а если и включают, то представляют собой небольшую подгруппу вероятных взаимодействий. В идеале вы должны измерить причины медленного взаимодействия в полевых условиях.

Недостатки API длинных задач

Измерение длительных задач в полевых условиях с помощью Performance Observer приносит лишь некоторую пользу. На самом деле он не дает столько информации, кроме того факта, что была выполнена длинная задача и сколько времени она заняла.

Инструменты реального мониторинга пользователей (RUM) часто используют это для отслеживания количества или продолжительности длительных задач или определения страниц, на которых они выполняются, но без основных подробностей о том, что вызвало длительную задачу, это имеет лишь ограниченное применение. API длинных задач имеет только базовую модель атрибуции , которая в лучшем случае сообщает вам только о контейнере, в котором возникла длинная задача (документ верхнего уровня или <iframe> ), но не о скрипте или функции, вызвавшей ее, как показано на рисунке. типичная запись:

{
  "name": "unknown",
  "entryType": "longtask",
  "startTime": 31.799999997019768,
  "duration": 136,
  "attribution": [
    {
      "name": "unknown",
      "entryType": "taskattribution",
      "startTime": 0,
      "duration": 0,
      "containerType": "window",
      "containerSrc": "",
      "containerId": "",
      "containerName": ""
    }
  ]
}

API длинных задач также является неполным представлением, поскольку также может исключать некоторые важные задачи. Некоторые обновления, такие как рендеринг, происходят в отдельных задачах, которые в идеале должны быть включены вместе с предыдущим выполнением, чтобы это обновление точно измеряло «общую работу» для этого взаимодействия. Более подробную информацию об ограничениях использования задач см. в разделе пояснения «Когда не хватает длинных задач» .

Последняя проблема заключается в том, что измерение длительных задач сообщает только об отдельных задачах, длительность которых превышает лимит в 50 миллисекунд. Кадр анимации может состоять из нескольких задач, длительность которых меньше этого предела в 50 миллисекунд, но в совокупности все равно блокировать возможность рендеринга браузера.

API длинных кадров анимации

Поддержка браузера

  • 123
  • 123
  • Икс
  • Икс

Источник

API Long Animation Frames (LoAF) — это новый API, который призван устранить некоторые недостатки API Long Tasks, чтобы дать разработчикам возможность получить более полезную информацию, которая поможет решить проблемы с реагированием и улучшить INP.

Хорошая отзывчивость означает, что страница быстро реагирует на взаимодействия с ней. Это предполагает возможность своевременно создавать любые обновления, необходимые пользователю, и избегать блокировки этих обновлений. Для INP рекомендуется отвечать в течение 200 миллисекунд или меньше , но для других обновлений (например, анимации) даже это может быть слишком долго.

API длинных кадров анимации — это альтернативный подход к измерению работы блокировки. Вместо измерения отдельных задач API Long Animation Frames, как следует из названия, измеряет длинные кадры анимации . Длинный кадр анимации — это когда обновление рендеринга задерживается более чем на 50 миллисекунд (так же, как пороговое значение для API длинных задач).

Длинные кадры анимации можно наблюдать так же, как и длинные задачи, с помощью PerformanceObserver , но вместо этого обращая внимание на тип long-animation-frame :

const observer = new PerformanceObserver((list) => {
  console.log(list.getEntries());
});

observer.observe({ type: 'long-animation-frame', buffered: true });

Предыдущие длинные кадры анимации также можно запросить на временной шкале производительности следующим образом:

const loafs = performance.getEntriesByType('long-animation-frame');

Однако для записей производительности существует maxBufferSize , после которого новые записи удаляются, поэтому рекомендуемым подходом является подход PerformanceObserver. Размер буфера long-animation-frame установлен равным 200, как и для long-tasks .

Преимущества просмотра фреймов вместо задач

Ключевое преимущество рассмотрения этого с точки зрения кадра, а не с точки зрения задач, заключается в том, что длинная анимация может состоять из любого количества задач, которые в совокупности привели к созданию длинного кадра анимации. Это решает последний момент, упомянутый ранее, когда сумма многих более мелких задач, блокирующих рендеринг, перед кадром анимации может не отображаться API длинных задач.

Еще одним преимуществом этого альтернативного подхода к длительным задачам является возможность разбивки по времени всего кадра. Вместо того, чтобы просто включать startTime и duration , как API длинных задач, LoAF включает гораздо более подробную разбивку различных частей длительности кадра, включая:

  • startTime : время начала длинного кадра анимации относительно времени начала навигации.
  • duration : продолжительность длинного кадра анимации (не включая время презентации).
  • renderStart : время начала цикла рендеринга, который включает в себя обратные вызовы requestAnimationFrame , расчет стиля и макета, обратные вызовы наблюдателя изменения размера и наблюдателя пересечения.
  • styleAndLayoutStart : начало периода времени, затраченного на расчеты стиля и макета.
  • firstUIEventTimestamp : время первого события пользовательского интерфейса (мыши/клавиатуры и т. д.), которое будет обработано в течение этого кадра.
  • blockingDuration : продолжительность в миллисекундах, в течение которой кадр анимации блокировался.

Эти временные метки позволяют разделить длинный кадр анимации на тайминги:

Тайминг Расчет
Время начала startTime
Время окончания startTime + duration
Продолжительность работы renderStart ? renderStart - startTime : duration
Продолжительность рендеринга renderStart ? (startTime + duration) - renderStart: 0
Рендеринг: продолжительность предварительного макета styleAndLayoutStart ? styleAndLayoutStart - renderStart : 0
Рендеринг: продолжительность стиля и макета styleAndLayoutStart ? (startTime + duration) - styleAndLayoutStart : 0

Для получения более подробной информации об этих отдельных таймингах обратитесь к объяснителю , который предоставляет подробные сведения о том, какое действие способствует созданию длинного кадра анимации.

Улучшенная атрибуция

Тип записи long-animation-frame включает более точные данные об атрибуции каждого сценария, который способствовал созданию длинного кадра анимации.

Подобно API длинных задач, он будет предоставлен в виде массива записей атрибуции, каждая из которых содержит подробную информацию:

  • name и EntryType вернут script .
  • Осмысленный invoker , указывающий, как был вызван сценарий (например, 'IMG#id.onload' , 'Window.requestAnimationFrame' или 'Response.json.then' ).
  • invokerType точки входа скрипта:
    • user-callback : известный обратный вызов, зарегистрированный из API веб-платформы (например, setTimeout , requestAnimationFrame ).
    • event-listener : прослушиватель событий платформы (например, click , load , keyup ).
    • resolve-promise : Обработчик обещания платформы (например, fetch() . Обратите внимание, что в случае с обещаниями все обработчики одних и тех же обещаний смешиваются в один «скрипт») .
    • reject-promise : согласно resolve-promise , но для отклонения.
    • classic-script : оценка сценария (например, <script> или import() )
    • module-script : То же, что и classic-script , но для скриптов модуля.
  • Отдельные данные о времени для этого сценария:
    • startTime : время вызова функции входа.
    • duration : Продолжительность между startTime и моментом завершения обработки последующей очереди микрозадач.
    • executionStart : время после компиляции.
    • forcedStyleAndLayoutDuration : общее время, затраченное на обработку принудительного макета/стиля внутри этой функции (см. перебор ).
    • pauseDuration : общее время, потраченное на «приостановку» синхронных операций (предупреждение, синхронный XHR).
  • Детали источника скрипта:
    • sourceURL : имя ресурса скрипта, если оно доступно (или пустое, если не найдено).
    • sourceFunctionName : имя функции скрипта, если оно доступно (или пустое, если не найдено).
    • sourceCharPosition : позиция символа сценария, если она доступна (или -1, если не найдена).
  • windowAttribution : контейнер (документ верхнего уровня или <iframe> ), в котором произошел длинный кадр анимации.
  • window : ссылка на окно того же происхождения.

Исходные записи, если они предусмотрены, позволяют разработчикам точно знать, как был вызван каждый сценарий в длинном кадре анимации, вплоть до позиции символа в вызывающем сценарии. Это дает точное местоположение в ресурсе JavaScript, который привел к созданию длинного кадра анимации.

Пример записи производительности long-animation-frame

Полный пример записи производительности long-animation-frame , содержащий один сценарий:

{
  "blockingDuration": 0,
  "duration": 60,
  "entryType": "long-animation-frame",
  "firstUIEventTimestamp": 11801.099999999627,
  "name": "long-animation-frame",
  "renderStart": 11858.800000000745,
  "scripts": [
    {
      "duration": 45,
      "entryType": "script",
      "executionStart": 11803.199999999255,
      "forcedStyleAndLayoutDuration": 0,
      "invoker": "DOMWindow.onclick",
      "invokerType": "event-listener",
      "name": "script",
      "pauseDuration": 0,
      "sourceURL": "https://web.dev/js/index-ffde4443.js",
      "sourceFunctionName": "myClickHandler",
      "sourceCharPosition": 17796,
      "startTime": 11803.199999999255,
      "window": [Window object],
      "windowAttribution": "self"
    }
  ],
  "startTime": 11802.400000000373,
  "styleAndLayoutStart": 11858.800000000745
}

Как можно видеть, это дает веб-сайтам беспрецедентный объем данных, позволяющий понять причину задержек при обновлении рендеринга.

Включение API длинных кадров анимации

API длинных кадров анимации включен по умолчанию в Chrome 123.

Использование API длинных кадров анимации в полевых условиях

Такие инструменты, как Lighthouse, хотя и полезны для обнаружения и воспроизведения проблем, представляют собой лабораторные инструменты, которые могут упускать из виду важные аспекты пользовательского опыта, которые могут обеспечить только полевые данные. API Long Animation Frames можно использовать в полевых условиях для сбора важных контекстных данных для взаимодействия с пользователем, чего не удалось сделать с помощью API Long Tasks. Это может помочь вам обнаружить и воспроизвести проблемы с интерактивностью, которые иначе вы могли бы не обнаружить.

Некоторые предлагаемые стратегии перечислены ниже, но команда Chrome хотела бы услышать отзывы об этом API и о том, как разработчики и поставщики RUM видят себя в использовании информации, предоставляемой API.

Функция обнаружения поддержки API длинных кадров анимации

Вы можете использовать следующий код, чтобы проверить, поддерживается ли API:

if (PerformanceObserver.supportedEntryTypes.includes('long-animation-frame')) {
  // Monitor LoAFs
}

В этом случае можно использовать следующую альтернативу, пока длинные кадры анимации еще не поддерживаются по умолчанию и находятся в этом переходном состоянии:

if ('PerformanceLongAnimationFrameTiming' in window) {
  // Monitor LoAFs
}

Передача данных длинной анимации обратно в конечную точку аналитики

Как показано, запись о производительности LoAF содержит ценную информацию. Одной из стратегий было бы отслеживать все LoAF и направлять те из них, которые превышают определенный порог, обратно в конечную точку аналитики для последующего анализа:

const REPORTING_THRESHOLD_MS = 150;

const observer = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.duration > REPORTING_THRESHOLD_MS) {
      // Example here logs to console, but could also report back to analytics
      console.log(entry);
    }
  }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

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

Наблюдение за худшими длинными кадрами анимации

Сайты могут захотеть собирать данные о самом длинном кадре (или кадрах) анимации, чтобы уменьшить объем данных, которые необходимо передать. Таким образом, независимо от того, сколько длинных кадров анимации отображается на странице, возвращаются только данные для худшего одного, пяти или любого количества длинных кадров анимации, которые абсолютно необходимы.

MAX_LOAFS_TO_CONSIDER = 10;
let longestBlockingLoAFs = [];

const observer = new PerformanceObserver(list => {
  longestBlockingLoAFs = longestBlockingLoAFs.concat(list.getEntries()).sort(
    (a, b) => b.blockingDuration - a.blockingDuration
  ).slice(0, MAX_LOAFS_TO_CONSIDER);
});
observer.observe({ type: 'long-animation-frame', buffered: true });

В подходящее время ( в идеале при событии visibilitychange ) возвращайтесь к аналитике. Для локального тестирования вы можете периодически использовать console.table :

console.table(longestBlockingLoAFs);

Ссылка на самое продолжительное взаимодействие INP

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

В настоящее время не существует прямого API для связывания записи INP с соответствующей записью или записями LoAF , хотя это можно сделать в коде, сравнивая время начала и окончания каждой из них (см. этот пример сценария ).

Отчет о длинных кадрах анимации с взаимодействиями

Альтернативный подход, требующий меньшего количества кода, — всегда отправлять самые большие (или X самых больших) записи LoAF, где взаимодействие произошло во время кадра (что можно обнаружить по наличию значения firstUIEventTimestamp ). В большинстве случаев это будет включать взаимодействие INP для данного посещения, а в редких случаях, когда этого не произойдет, все равно будут отображаться длительные взаимодействия, которые важно исправить, поскольку они могут быть взаимодействием INP для других пользователей.

Следующий код регистрирует все записи LoAF длительностью более 150 миллисекунд, в которых произошло взаимодействие во время кадра. Здесь выбрано значение 150, поскольку оно немного меньше «хорошего» порога INP в 200 миллисекунд. Вы можете выбрать большее или меньшее значение в зависимости от ваших потребностей.

const REPORTING_THRESHOLD_MS = 150;

const observer = new PerformanceObserver(list => {
    for (const entry of list.getEntries()) {
      if (entry.duration > REPORTING_THRESHOLD_MS &&
        entry.firstUIEventTimestamp > 0
      ) {
        // Example here logs to console, but could also report back to analytics
        console.log(entry);
      }
    }
});
observer.observe({ type: 'long-animation-frame', buffered: true });

Выявление общих закономерностей в длинных кадрах анимации.

Альтернативной стратегией было бы рассмотреть распространенные сценарии, чаще всего встречающиеся в длинных записях кадров анимации. Данные могут передаваться на уровне сценария и/или позиции персонажа для выявления рецидивистов.

Это может работать особенно хорошо для настраиваемых платформ, где темы или плагины, вызывающие проблемы с производительностью, можно легче обнаружить на ряде сайтов.

Время выполнения общих сценариев (или сторонних источников) в длинных кадрах анимации можно суммировать и сообщать об этом для выявления общих участников длинных кадров анимации на сайте или в группе сайтов. Например, чтобы посмотреть URL-адреса:

const observer = new PerformanceObserver(list => {
  const allScripts = list.getEntries().flatMap(entry => entry.scripts);
  const scriptSource = [...new Set(allScripts.map(script => script.sourceURL))];
  const scriptsBySource= scriptSource.map(sourceURL => ([sourceURL,
      allScripts.filter(script => script.sourceURL === sourceURL)
  ]));
  const processedScripts = scriptsBySource.map(([sourceURL, scripts]) => ({
    sourceURL,
    count: scripts.length,
    totalDuration: scripts.reduce((subtotal, script) => subtotal + script.duration, 0)
  }));
  processedScripts.sort((a, b) => b.totalDuration - a.totalDuration);
  // Example here logs to console, but could also report back to analytics
  console.table(processedScripts);
});

observer.observe({type: 'long-animation-frame', buffered: true});

И пример этого вывода:

(index) sourceURL count totalDuration
0 'https://example.consent.com/consent.js' 1 840
1 'https://example.com/js/analytics.js' 7 628
2 'https://example.chatapp.com/web-chat.js' 1 5

Использование API длинных кадров анимации в инструментах

API также может предоставить дополнительные инструменты разработчика для локальной отладки. Хотя некоторые инструменты, такие как Lighthouse и Chrome DevTools, смогли собрать большую часть этих данных, используя детали трассировки более низкого уровня, наличие этого API более высокого уровня может позволить другим инструментам получить доступ к этим данным.

Отображение данных длинных кадров анимации в DevTools

Вы можете отображать длинные кадры анимации в DevTools с помощью API performance.measure() , которые затем отображаются на дорожке пользовательского времени DevTools в трассировках производительности, чтобы показать, на чем сосредоточить свои усилия для повышения производительности:

const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    performance.measure('LoAF', {
      start: entry.startTime,
      end: entry.startTime + entry.duration,
    });
  }
});

observer.observe({ type: 'long-animation-frame', buffered: true });

Если этот API окажется полезным в долгосрочной перспективе, он, скорее всего, будет включен в сам DevTools, но предыдущий фрагмент кода позволяет пока разместить его там.

Использование данных длинных кадров анимации в других инструментах разработчика.

Расширение Web Vitals продемонстрировало ценность регистрации сводной отладочной информации для диагностики проблем с производительностью. Теперь, когда API запущен, подобные инструменты могут более легко получать данные, помогая разработчикам понять, на чем сосредоточить свои усилия. Мы также планируем добавить это в библиотеку JavaScript Web Vitals в версии 4.

Использование данных длинных кадров анимации в инструментах автоматического тестирования

Аналогичным образом инструменты автоматического тестирования, возможно, в конвейерах CI/CD, могут выявить подробную информацию о потенциальных проблемах с производительностью, измеряя длинные кадры анимации во время выполнения различных наборов тестов.

Часто задаваемые вопросы

Некоторые из часто задаваемых вопросов по этому API включают в себя:

Почему бы просто не расширить или не усовершенствовать API длинных задач?

Это альтернативный взгляд на отчетность по схожим, но, в конечном счете, другим показателям потенциальных проблем с реагированием. Важно обеспечить, чтобы сайты, использующие существующий API длинных задач, продолжали функционировать, чтобы не нарушать существующие варианты использования.

Хотя API длинных задач может извлечь выгоду из некоторых функций LoAF (например, улучшенной модели атрибуции), мы считаем, что сосредоточение внимания на кадрах, а не на задачах, дает множество преимуществ, которые делают этот API фундаментально отличным от существующего API длинных задач.

Заменит ли это API длинных задач?

Хотя мы считаем, что API Long Animation Frames является лучшим и более полным API для измерения длинных задач, в настоящее время мы не планируем прекращать поддержку API Long Tasks.

Требуется обратная связь

Отзывы можно оставить в списке проблем GitHub , а ошибки в реализации API Chrome можно зарегистрировать в системе отслеживания проблем Chrome .

Заключение

API Long Animation Frames — это потрясающий новый API со многими потенциальными преимуществами по сравнению с предыдущим API Long Tasks.

Это оказывается ключевым инструментом для решения проблем реагирования, измеряемых INP. INP — это сложный показатель для оптимизации, и этот API — один из способов, с помощью которого команда Chrome стремится упростить выявление и решение проблем для разработчиков.

Однако сфера применения API Long Animation Frames выходит за рамки просто INP и может помочь выявить другие причины медленных обновлений, которые могут повлиять на общую плавность взаимодействия с пользователем веб-сайта.

Благодарности

Миниатюрное изображение Генри Би на Unsplash .