Узнайте, как находить медленные взаимодействия в полевых данных сайта, чтобы улучшить показатель Interaction to Next Paint.
Данные наблюдений показывают, как реальные пользователи взаимодействуют с вашим сайтом. Они позволяют выявить проблемы, которые невозможно обнаружить только с помощью лабораторных данных. Если речь идет о показателе Interaction to Next Paint (INP), то данные по полям необходимы для выявления медленных взаимодействий и предоставляют важные подсказки, которые помогут вам их исправить.
В этом руководстве рассказывается, как быстро оценить показатель INP сайта, используя данные из отчета Chrome User Experience Report (CrUX). Затем вы узнаете, как использовать сборку атрибуции библиотеки JavaScript web-vitals и новые данные, которые она предоставляет из Long Animation Frames API (LoAF), чтобы собирать и интерпретировать полевые данные о медленных взаимодействиях на вашем сайте.
Начните с CrUX, чтобы оценить INP сайта
Если вы не собираете данные о полях от пользователей сайта, CrUX может стать хорошей отправной точкой. В CrUX собираются данные наблюдений от реальных пользователей Chrome, которые согласились отправлять телеметрические данные.
Данные CrUX представлены в разных разделах в зависимости от того, какая информация вам нужна. CrUX может предоставлять данные об INP и других Core Web Vitals для:
- отдельные страницы и целые источники с помощью PageSpeed Insights;
- Типы страниц. Например, на многих сайтах электронной торговли есть страницы товаров и страницы со списками товаров. Данные CrUX для уникальных типов страниц можно получить в Search Console.
Для начала вы можете ввести URL своего сайта в PageSpeed Insights. После ввода URL вы увидите полевые данные для него (если они доступны) по нескольким показателям, включая INP. Вы также можете использовать переключатели, чтобы посмотреть значения INP для мобильных устройств и компьютеров.
Эти данные полезны, потому что они говорят вам, есть ли у вас проблема. Однако CrUX не может сообщить вам, что вызывает проблемы. Существует множество решений для мониторинга реальных пользователей (RUM), которые помогут вам собрать собственные полевые данные от пользователей вашего сайта, чтобы помочь вам ответить на этот вопрос. Один из вариантов – собрать эти полевые данные самостоятельно с помощью библиотеки JavaScript web-vitals.
Сбор данных наблюдений с помощью библиотеки JavaScript web-vitals
Библиотека JavaScript web-vitals – это скрипт, который можно загрузить на сайт, чтобы собирать данные о его пользователях. С его помощью можно регистрировать ряд показателей, в том числе INP в браузерах, которые его поддерживают.
Стандартная сборка библиотеки web-vitals позволяет получать базовые данные INP от пользователей:
import {onINP} from 'web-vitals';
onINP(({name, value, rating}) => {
console.log(name); // 'INP'
console.log(value); // 512
console.log(rating); // 'poor'
});
Чтобы анализировать полевые данные пользователей, их нужно куда-то отправлять.
import {onINP} from 'web-vitals';
onINP(({name, value, rating}) => {
// Prepare JSON to be sent for collection. Note that
// you can add anything else you'd want to collect here:
const body = JSON.stringify({name, value, rating});
// Use `sendBeacon` to send data to an analytics endpoint.
// For Google Analytics, see https://github.com/GoogleChrome/web-vitals#send-the-results-to-google-analytics.
navigator.sendBeacon('/analytics', body);
});
Однако сами по себе эти данные не дают вам больше информации, чем CrUX. Вот тут-то и пригодится сборка атрибуции библиотеки web-vitals.
Используйте сборку атрибуции библиотеки web-vitals
Сборка атрибуции библиотеки web-vitals предоставляет дополнительные данные, которые вы можете получить от пользователей в полевых условиях, чтобы помочь вам лучше устранять неполадки с проблемными взаимодействиями, которые влияют на INP вашего сайта. Доступ к этим данным можно получить через объект attribution, который возвращает метод onINP() библиотеки:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, rating, attribution}) => {
console.log(name); // 'INP'
console.log(value); // 56
console.log(rating); // 'good'
console.log(attribution); // Attribution data object
});
Помимо самого показателя INP, сборка атрибуции предоставляет множество данных, которые помогут вам понять причины медленных взаимодействий, в том числе то, на какой части взаимодействия следует сосредоточиться. С его помощью вы сможете ответить на следующие вопросы:
- "Взаимодействовал ли пользователь со страницей во время ее загрузки?"
- "Долго ли выполнялись обработчики событий взаимодействия?"
- "Была ли задержка в запуске кода обработчика событий взаимодействия? Если да, то что ещё происходило в основном потоке в это время?"
- "Привело ли взаимодействие к большому объему работы по рендерингу, из-за чего отрисовка следующего кадра была отложена?"
В таблице ниже приведены некоторые базовые данные атрибуции, которые можно получить из библиотеки. Они помогут вам определить основные причины медленного взаимодействия на вашем сайте.
Ключ объекта attribution
|
Данные |
|---|---|
interactionTarget
|
Селектор CSS, указывающий на элемент, который создал значение INP страницы, например button#save.
|
interactionType
|
Тип взаимодействия: клик, нажатие или ввод с клавиатуры. |
inputDelay*
|
Задержка ввода при взаимодействии. |
processingDuration*
|
Время с момента, когда первый прослушиватель событий начал работать в ответ на взаимодействие с пользователем, до момента, когда все прослушиватели событий завершили обработку. |
presentationDelay*
|
Задержка отображения – время от завершения работы обработчиков событий до отрисовки следующего кадра. |
longAnimationFrameEntries*
|
Записи из LoAF, связанные с взаимодействием. Дополнительную информацию вы найдете в следующем разделе. |
Начиная с версии 4 библиотеки Web Vitals, вы можете получать ещё более подробную статистику о проблемных взаимодействиях благодаря данным с разбивкой по фазам INP (задержка ввода, длительность обработки и задержка отрисовки) и Long Animation Frames API (LoAF).
Long Animation Frames API (LoAF)
Отладка взаимодействий с использованием полевых данных – сложная задача. Однако с помощью данных LoAF теперь можно получить более полное представление о причинах медленного взаимодействия, поскольку LoAF предоставляет ряд подробных временных интервалов и других данных, которые можно использовать для точного определения причин и, что более важно, того, где находится источник проблемы. в коде вашего сайта.
Сборка атрибуции библиотеки web-vitals предоставляет массив записей LoAF в ключе longAnimationFrameEntries объекта attribution. В следующей таблице перечислены некоторые ключевые фрагменты данных, которые можно найти в каждой записи LoAF:
| Ключ объекта записи LoAF | Данные |
|---|---|
duration
|
Длительность кадра анимации до завершения макета, не включая отрисовку и компоновку. |
blockingDuration
|
Общее время, в течение которого браузер не мог быстро ответить из-за длительных задач. В это время могут выполняться длительные задачи JavaScript, а также последующие длительные задачи отрисовки во фрейме. |
firstUIEventTimestamp
|
Временная метка, указывающая, когда событие было поставлено в очередь во время кадра. Полезно для определения начала задержки ввода взаимодействия. |
startTime
|
Начальная временная метка кадра. |
renderStart
|
Время начала отрисовки кадра. Это включает любые обратные вызовы requestAnimationFrame (и ResizeObserver, если применимо), но потенциально до начала работы со стилем/макетом.
|
styleAndLayoutStart
|
Когда в кадре происходит работа со стилем/макетом. Может быть полезно для определения продолжительности работы над стилем/макетом при учете других доступных временных меток. |
scripts
|
Массив элементов, содержащих информацию об атрибуции скриптов, которые влияют на INP страницы. |
blockingDuration).
Вся эта информация может многое рассказать о том, что замедляет взаимодействие, но массив scripts, который отображается в записях LoAF, должен представлять особый интерес:
| Ключ объекта атрибуции скрипта | Данные |
|---|---|
invoker
|
Инициатор. Это зависит от типа вызывающего объекта, описанного в следующей строке. Примеры значений: 'IMG#id.onload', 'Window.requestAnimationFrame', 'Response.json.then'. |
invokerType
|
Тип вызывающего объекта. Допустимые значения: 'user-callback', 'event-listener', 'resolve-promise', 'reject-promise', 'classic-script', 'module-script'.
|
sourceURL
|
URL скрипта, в котором возник длительный кадр анимации. |
sourceCharPosition
|
Позиция символа в скрипте, определяемая параметром sourceURL.
|
sourceFunctionName
|
Название функции в указанном скрипте. |
Каждая запись в этом массиве содержит данные, показанные в этой таблице, которая дает вам информацию о скрипте, ответственном за медленное взаимодействие, и о том, как он был ответственен.
Как измерить и определить распространенные причины медленного взаимодействия
Чтобы вы могли понять, как использовать эту информацию, в этом руководстве мы рассмотрим, как с помощью данных LoAF, полученных в библиотеке web-vitals, определить некоторые причины медленного взаимодействия.
Длительная обработка
Длительность обработки взаимодействия – это время, которое требуется для выполнения зарегистрированных обратных вызовов обработчика событий взаимодействия и всего остального, что может произойти между ними. Библиотека web-vitals сообщает о длительной обработке:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {processingDuration} = attribution; // 512.5
});
Естественно думать, что основной причиной медленного взаимодействия является то, что код обработчика событий выполнялся слишком долго, но это не всегда так! Убедившись, что проблема в этом, вы можете глубже изучить данные LoAF:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {processingDuration} = attribution; // 512.5
// Get the longest script from LoAF covering `processingDuration`:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
// Get attribution for the long-running event handler:
const {invokerType} = script; // 'event-listener'
const {invoker} = script; // 'BUTTON#update.onclick'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
Как видно из приведенного выше фрагмента кода, вы можете использовать данные LoAF, чтобы отследить точную причину взаимодействия с высокими значениями длительности обработки, в том числе:
- Элемент и его зарегистрированный прослушиватель событий.
- Файл скрипта и позиция символа в нем, содержащие код долго выполняющегося обработчика событий.
- Название функции.
Такие данные бесценны. Вам больше не нужно вручную искать, какое именно взаимодействие или какой из его обработчиков событий привели к высокой длительности обработки. Кроме того, поскольку сторонние скрипты часто могут регистрировать собственные обработчики событий, вы можете определить, был ли ваш код ответственным за это! Если у вас есть доступ к коду, попробуйте оптимизировать длительные задачи.
Длительные задержки ввода
Хотя обработчики событий, выполняющиеся долго, встречаются часто, следует учитывать и другие части взаимодействия. Первая часть – это задержка ввода, которая происходит до обработки. Это время с момента, когда пользователь инициирует взаимодействие, до момента, когда начинают выполняться обратные вызовы обработчика событий. Задержка возникает, когда основной поток уже обрабатывает другую задачу. Сборка атрибуции библиотеки web-vitals может сообщить вам длину задержки ввода для взаимодействия:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
});
Если вы заметили, что некоторые взаимодействия имеют высокую задержку ввода, вам нужно выяснить, что происходило на странице во время взаимодействия, которое вызвало длительную задержку ввода. Часто это сводится к тому, произошло ли взаимодействие во время загрузки страницы или после нее.
Произошла ли она во время загрузки страницы?
Основной поток часто бывает наиболее загружен во время загрузки страницы. В это время в очередь добавляются и обрабатываются самые разные задачи. Если пользователь попытается взаимодействовать со страницей, пока все это происходит, взаимодействие может быть отложено. На страницах, где загружается много кода JavaScript, может запускаться компиляция и оценка скриптов, а также выполнение функций, которые подготавливают страницу к взаимодействию с пользователем. Это может помешать пользователю взаимодействовать со страницей. Чтобы узнать, происходит ли это на вашем сайте, выполните следующие действия:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
// Get the longest script from the first LoAF entry:
const loaf = attribution.longAnimationFrameEntries[0];
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
// Invoker types can describe if script eval blocked the main thread:
const {invokerType} = script; // 'classic-script' | 'module-script'
const {sourceLocation} = script; // 'https://example.com/app.js'
}
});
Если вы регистрируете эти данные в поле и видите большие задержки ввода и типы инициаторов 'classic-script' или 'module-script', то можно сказать, что скрипты на вашем сайте оцениваются долго и блокируют основной поток достаточно долго, чтобы задерживать взаимодействия. Чтобы сократить время блокировки, разбейте скрипты на более мелкие пакеты, отложите загрузку неиспользуемого кода и удалите ненужный код.
После загрузки страницы?
Задержки ввода часто возникают во время загрузки страницы, но они также могут возникать и после ее загрузки по совершенно другой причине. Распространенными причинами задержек ввода после загрузки страницы могут быть код, который периодически выполняется из-за более раннего вызова setInterval, или даже обратные вызовы событий, которые были поставлены в очередь на выполнение ранее и все ещё обрабатываются.
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {inputDelay} = attribution; // 125.59439536
// Get the longest script from the first LoAF entry:
const loaf = attribution.longAnimationFrameEntries[0];
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
if (script) {
const {invokerType} = script; // 'user-callback'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
Как и в случае с устранением неполадок, связанных с высокой длительностью обработки, высокие задержки ввода, вызванные упомянутыми ранее причинами, предоставят вам подробные данные об атрибуции скриптов. Однако тип вызывающего объекта будет меняться в зависимости от характера работы, которая задерживает взаимодействие:
'user-callback'указывает, что блокирующая задача была изsetInterval,setTimeoutили дажеrequestAnimationFrame.'event-listener'указывает, что блокирующая задача была из более ранних входных данных, которые были поставлены в очередь и все ещё обрабатываются.'resolve-promise'и'reject-promise'означают, что блокирующая задача была связана с асинхронной работой, которая была запущена ранее и разрешена или отклонена в то время, когда пользователь пытался взаимодействовать со страницей, задерживая взаимодействие.
В любом случае данные атрибуции скрипта подскажут, с чего начать поиск и связана ли задержка ввода с вашим собственным кодом или со сторонним скриптом.
Длительные задержки вывода на экран
Задержки отрисовки – это последний этап взаимодействия, который начинается, когда обработчики событий взаимодействия завершают работу, и продолжается до момента отрисовки следующего кадра. Они возникают, когда работа обработчика событий, вызванная взаимодействием, меняет визуальное состояние пользовательского интерфейса. Как и в случае с длительностью обработки и задержкой ввода, библиотека web-vitals может сообщить вам, какова была задержка отрисовки для взаимодействия:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
});
Если вы записываете эти данные и видите большие задержки отрисовки для взаимодействий, влияющих на INP вашего сайта, причины могут быть разными, но вот пара причин, на которые следует обратить внимание.
Дорогостоящая работа со стилями и макетами
Длительные задержки отрисовки могут быть вызваны ресурсоемким пересчетом стилей и компоновкой макета, которые возникают по ряду причин, включая сложные селекторы CSS и большие размеры DOM. Вы можете измерить продолжительность этой работы с помощью таймингов LoAF, отображаемых в библиотеке web-vitals:
import {onINP} from 'web-vitals/attribution';
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 113.32307691
// Get the longest script from the last LoAF entry:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
// Get necessary timings:
const {startTime} = loaf; // 2120.5
const {duration} = loaf; // 1002
// Figure out the ending timestamp of the frame (approximate):
const endTime = startTime + duration; // 3122.5
// Get the start timestamp of the frame's style/layout work:
const {styleAndLayoutStart} = loaf; // 3011.17692309
// Calculate the total style/layout duration:
const styleLayoutDuration = endTime - styleAndLayoutStart; // 111.32307691
if (script) {
// Get attribution for the event handler that triggered
// the long-running style and layout operation:
const {invokerType} = script; // 'event-listener'
const {invoker} = script; // 'BUTTON#update.onclick'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
LoAF не сообщает, сколько времени ушло на работу со стилями и макетом для кадра, но указывает, когда она началась. Используя эту временную метку начала, вы можете использовать другие данные из LoAF, чтобы рассчитать точную продолжительность этой работы, определив время окончания кадра и вычитая из него временную метку начала работы со стилем и макетом.
Долго выполняющиеся обратные вызовы requestAnimationFrame
Одной из возможных причин задержек может быть слишком большой объем работы, выполняемой в обратном вызове requestAnimationFrame. Содержимое этого обратного вызова выполняется после завершения работы обработчиков событий, но непосредственно перед пересчетом стиля и работой с макетом.
Если в обратных вызовах выполняется сложная работа, то они могут занимать много времени. Если вы подозреваете, что высокие значения задержки отрисовки связаны с работой, которую вы выполняете с помощью requestAnimationFrame, вы можете использовать данные LoAF, полученные библиотекой web-vitals, чтобы выявить эти сценарии:
onINP(({name, value, attribution}) => {
const {presentationDelay} = attribution; // 543.1999999880791
// Get the longest script from the last LoAF entry:
const loaf = attribution.longAnimationFrameEntries.at(-1);
const script = loaf?.scripts.toSorted((a, b) => b.duration - a.duration)[0];
// Get the render start time and when style and layout began:
const {renderStart} = loaf; // 2489
const {styleAndLayoutStart} = loaf; // 2989.5999999940395
// Calculate the `requestAnimationFrame` callback's duration:
const rafDuration = styleAndLayoutStart - renderStart; // 500.59999999403954
if (script) {
// Get attribution for the event handler that triggered
// the long-running requestAnimationFrame callback:
const {invokerType} = script; // 'user-callback'
const {invoker} = script; // 'FrameRequestCallback'
const {sourceURL} = script; // 'https://example.com/app.js'
const {sourceCharPosition} = script; // 83
const {sourceFunctionName} = script; // 'update'
}
});
Если вы видите, что значительная часть задержки показа приходится на обратный вызов requestAnimationFrame, убедитесь, что в этих обратных вызовах выполняется только работа, которая приводит к фактическому обновлению пользовательского интерфейса. Любая другая работа, которая не касается DOM или обновления стилей, без необходимости задержит отрисовку следующего кадра, поэтому будьте осторожны!
Заключение
Полевые данные – лучший источник информации о том, какие взаимодействия вызывают проблемы у реальных пользователей. Используя инструменты сбора полевых данных, такие как библиотека JavaScript web-vitals (или поставщик RUM), вы можете с большей уверенностью определить, какие взаимодействия являются наиболее проблематичными, а затем перейти к воспроизведению проблемных взаимодействий в лаборатории и их исправлению.