Временные метки длинных кадров анимации
Длинные кадры анимации (LoAF) могут повлиять на пользовательский опыт веб-сайта. Они могут вызвать медленные обновления пользовательского интерфейса (UI), что приведет к тому, что элементы будут казаться не реагирующими, и к неровным (или неплавным) анимационным эффектам и прокрутке, что вызовет разочарование пользователя. API длинных кадров анимации позволяет разработчикам получать информацию о длинных кадрах анимации и лучше понимать их первопричины. В этой статье показано, как использовать API длинных кадров анимации.
Что такое длинный кадр анимации?
Длинный кадр анимации — или LoAF — это обновление отрисовки, которое задерживается более чем на 50 мс.
Хорошая отзывчивость означает, что страница быстро реагирует на взаимодействия. Это включает своевременное отображение всех необходимых обновлений для пользователя и избегание всего, что могло бы заблокировать эти обновления. Например, метрика Google Interaction to Next Paint (INP) рекомендует, чтобы веб-сайт отвечал на взаимодействия с страницей (например, клики или нажатия клавиш) в течение 200 мс.
Для плавной анимации обновления должны быть быстрыми. Для того чтобы анимация выполнялась плавно с частотой 60 кадров в секунду, каждый кадр анимации должен отрисовываться примерно за 16 мс (1000/60).
Наблюдение за длинными кадрами анимации
Для получения информации о LoAF и выявления проблемных элементов вы можете наблюдать за записями в временной шкале производительности с типом entryType "long-animation-frame" с помощью стандартного PerformanceObserver:
const observer = new PerformanceObserver((list) => {
console.log(list.getEntries());
});
observer.observe({ type: "long-animation-frame", buffered: true });
Также можно запросить предыдущие длинные кадры анимации, используя метод, такой как Performance.getEntriesByType():
const loafs = performance.getEntriesByType("long-animation-frame");
Однако следует помнить, что максимальный размер буфера для записей типа "long-animation-frame" составляет 200, после чего новые записи отбрасываются. Поэтому рекомендуется использовать подход PerformanceObserver.
Просмотр записей "long-animation-frame"
Записи в временной шкале производительности, возвращаемые с типом "long-animation-frame", представлены объектами PerformanceLongAnimationFrameTiming. Этот объект имеет свойство scripts, содержащее массив объектов PerformanceScriptTiming, каждый из которых содержит информацию о скрипте, который повлиял на длинный кадр анимации.
Ниже приведена JSON-представление примера записи производительности "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
}
Помимо стандартных данных, возвращаемых записью PerformanceEntry, здесь содержатся следующие важные элементы:
blockingDuration-
DOMHighResTimeStamp, указывающий общее время в миллисекундах, в течение которого основной поток был заблокирован от реагирования на задачи высокого приоритета, такие как пользовательский ввод. Это рассчитывается путем взятия всех длинных задач в LoAF, которые имеютdurationболее чем50ms, вычитания50msиз каждой, добавления времени отрисовки к времени выполнения самой длительной задачи и суммирования результатов. firstUIEventTimestamp-
DOMHighResTimeStamp, указывающий время первого события UI, такого как событие мыши или клавиатуры, которое было поставлено в очередь в текущем кадре анимации. renderStart-
DOMHighResTimeStamp, указывающий начальное время цикла отрисовки, который включает вызовыWindow.requestAnimationFrame(), вычисление стилей и макета, вызовыResizeObserverи вызовыIntersectionObserver. styleAndLayoutStart-
DOMHighResTimeStamp, указывающий начало периода времени, затраченного на вычисления стилей и макета для текущего кадра анимации. -
PerformanceScriptTimingсвойства: -
Свойства, предоставляющие информацию о скрипте(ах), который(е) повлияли на LoAF:
script.executionStart-
DOMHighResTimeStamp, указывающий время завершения компиляции скрипта и начала выполнения. script.forcedStyleAndLayoutDuration-
DOMHighResTimeStamp, указывающий общее время в миллисекундах, затраченное обработкой принудительного макета/стиля скриптом. См. Избегайте избыточного пересчета макета, чтобы понять, что вызывает это. -
script.invokerиscript.invokerType -
Строковые значения, указывающие, как был вызван скрипт (например,
"IMG#id.onload"или"Window.requestAnimationFrame") и тип точки входа в скрипт (например,"event-listener"или"resolve-promise"). script.pauseDuration-
DOMHighResTimeStamp, указывающий общее время в миллисекундах, затраченное скриптом на «приостановку» синхронных операций (например, вызовыWindow.alert()или синхронныеXMLHttpRequest). -
script.sourceCharPosition,script.sourceFunctionNameиscript.sourceURL -
Значения, представляющие позицию символа скрипта, имя функции и URL скрипта соответственно. Важно отметить, что указанное имя функции будет «точкой входа» скрипта (т. е. верхним уровнем стека), а не какой-либо конкретной медленной подфункцией.
Например, если обработчик событий вызывает функцию верхнего уровня, которая, в свою очередь, вызывает медленную подфункцию, поля
source*будут сообщать имя и расположение функции верхнего уровня, а не медленной подфункции. Это связано с причинами производительности — полный стек вызовов функций является дорогостоящим. -
script.windowAttributionобъектаscript.window -
Перечислимое значение, описывающее отношение контейнера (т. е. либо верхнего уровня документа, либо
<iframe>), в котором был выполнен этот скрипт, к документу верхнего уровня, и ссылка на его объектWindow.
Примечание: Атрибуция скриптов предоставляется только для скриптов, выполняемых в главном потоке страницы, включая скрипты с одинаковым происхождением
<iframe>. Однако скрипты с разным происхождением<iframe>, веб-воркеры, рабочие процессы сервиса и код расширений не будут иметь атрибуции скриптов в длинных кадрах анимации, даже если они влияют на длительность одного из них.
Вычисление временных меток
Временные метки, предоставленные классом PerformanceLongAnimationFrameTiming, позволяют вычислить несколько других полезных временных меток для длинного кадра анимации:
| Время | Вычисление |
|---|---|
| Начальное время | startTime |
| Конечное время | startTime + duration |
| Продолжительность работы | renderStart ? renderStart - startTime : duration |
| Продолжительность отрисовки | renderStart ? (startTime + duration) - renderStart : 0 |
| Продолжительность отрисовки: до расчёта макета | styleAndLayoutStart ? styleAndLayoutStart - renderStart : 0 |
| Продолжительность отрисовки: вычисление стилей и макета | styleAndLayoutStart ? (startTime + duration) - styleAndLayoutStart : 0 |
Примеры
Обнаружение поддержки API длинных кадров анимации
Вы можете проверить, поддерживается ли API длинных кадров анимации, используя PerformanceObserver.supportedEntryTypes:
if (PerformanceObserver.supportedEntryTypes.includes("long-animation-frame")) {
// Monitor LoAFs
}
Отчет о LoAF сверх определенного порога
Хотя пороги LoAF фиксированы на 50 мс, это может привести к большому объёму отчётов, когда вы только начинаете работу по оптимизации производительности. Изначально вы можете отправлять отчёты о LoAF с более высоким пороговым значением и постепенно уменьшать его по мере улучшения сайта и устранения худших 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; real code could send to analytics endpoint
console.log(entry);
}
}
});
observer.observe({ type: "long-animation-frame", buffered: true });
Записи длинных кадров анимации могут быть довольно объёмными, поэтому тщательно обдумайте, какие данные из каждой записи следует отправлять в аналитику. Например, сводные временные метки записей и URL скриптов могут быть достаточно для ваших нужд.
Наблюдение за самыми длинными кадрами анимации
Возможно, вам нужно собирать данные только о самых длинных кадрах анимации (скажем, о 5 или 10 самых длинных), чтобы уменьшить объём данных, которые необходимо собрать. Это можно сделать следующим образом:
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 });
// Report data on visibilitychange event
document.addEventListener("visibilitychange", () => {
// Example here logs to console; real code could send to analytics endpoint
console.log(longestBlockingLoAFs);
});
Отчет о длинных кадрах анимации с взаимодействиями
Ещё один полезный метод — отправлять записи с самыми большими значениями LoAF, в которых во время кадра произошла какая-либо интеракция, что можно определить по наличию значения firstUIEventTimestamp.
Следующий код регистрирует все записи LoAF, значения которых больше 150 мс, где во время кадра произошла интеракция. Вы можете выбрать большее или меньшее значение в зависимости от ваших потребностей.
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; real code could send to analytics endpoint
console.log(entry);
}
}
});
observer.observe({ type: "long-animation-frame", buffered: true });
Выявление общих шаблонов сценариев в длинных кадрах анимации
Альтернативная стратегия — посмотреть, какие сценарии чаще всего встречаются в записях LoAF. Данные можно сообщать на уровне сценария и/или позиции символа, чтобы определить самые проблемные сценарии. Это полезно в случаях, когда темы или плагины, вызывающие проблемы с производительностью, используются на нескольких сайтах.
Время выполнения общих сценариев (или сценариев сторонних производителей) в LoAF можно суммировать и сообщать, чтобы определить общие источники проблем с LoAF на одном сайте или на нескольких сайтах.
Например, чтобы сгруппировать сценарии по 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; real code could send to analytics endpoint
console.table(processedScripts);
});
observer.observe({ type: "long-animation-frame", buffered: true });
Сравнение с API задач большой длительности
API длинных кадров анимации предшествовал API задач большой длительности Long Tasks API (см. PerformanceLongTaskTiming). Оба API имеют схожую цель и использование — предоставление информации о задачах большой длительности, которые блокируют основной поток на 50 мс или более.
Уменьшение количества задач большой длительности на вашем сайте полезно, потому что такие задачи могут привести к проблемам с отзывчивостью. Например, если пользователь нажимает на кнопку, в то время как основной поток обрабатывает задачу большой длительности, реакция интерфейса на щелчок будет задерживаться до завершения задачи большой длительности. Обычная рекомендация — разбить длинные задачи на несколько более мелких, чтобы между ними можно было обрабатывать важные взаимодействия.
Однако у API задач большой длительности есть свои ограничения:
- Один кадр анимации может состоять из нескольких задач, которые не достигают порога в 50 мс, но при этом вместе блокируют основной поток. API длинных кадров анимации решает эту проблему, рассматривая кадр анимации в целом.
- Тип записи
PerformanceLongTaskTimingпредоставляет более ограниченную информацию, чем типPerformanceLongAnimationFrameTiming— он может сообщить вам контейнер, в котором произошла задача большой длительности, но не сценарий или функцию, которые её вызвали, например. - API задач большой длительности предоставляет неполный обзор, так как он может исключать некоторые важные задачи. Некоторые обновления (например, отрисовка) происходят в отдельных задачах, которые в идеале должны быть включены вместе с предшествующим выполнением, которое вызвало это обновление, чтобы точно измерить «общую работу» для этого взаимодействия.
См. также
- Оптимизация задач большой длительности на web.dev (2024)
- Где задачи большой длительности подходят к концу, разъяснение API длинных кадров анимации (2024)
© 2005–2024 MDN contributors.
Licensed under the Creative Commons Attribution-ShareAlike License v2.5 or later.
https://developer.mozilla.org/en-US/docs/Web/API/Performance_API/Long_animation_frame_timing