Найдите медленные взаимодействия в полевых условиях

Узнайте, как находить медленные взаимодействия в полевых данных вашего веб-сайта, чтобы найти возможности улучшить взаимодействие с Next Paint.

Данные полей — это данные, которые говорят вам, как реальные пользователи взаимодействуют с вашим веб-сайтом. Он выявляет проблемы, которые невозможно найти только в лабораторных данных . Что касается взаимодействия с следующей отрисовкой (INP) , полевые данные необходимы для выявления медленных взаимодействий и предоставляют важные подсказки, которые помогут вам их исправить.

В этом руководстве вы узнаете, как быстро оценить INP вашего веб-сайта, используя полевые данные из отчета об опыте пользователей Chrome (CrUX), чтобы определить, есть ли на вашем веб-сайте проблемы с INP. Впоследствии вы узнаете, как использовать сборку атрибуции библиотеки JavaScript web-vitals — и новые идеи, которые она предоставляет из API длинных анимационных кадров (LoAF) — для сбора и интерпретации данных полей для медленного взаимодействия на вашем веб-сайте.

Начните с CrUX, чтобы оценить INP вашего сайта.

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

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

  • Отдельные страницы и целые источники с использованием PageSpeed ​​Insights .
  • Типы страниц. Например, многие веб-сайты электронной коммерции имеют типы «Страница сведений о продукте» и «Страница со списком продуктов». Вы можете получить данные CrUX для уникальных типов страниц в Search Console .

В качестве отправной точки вы можете ввести URL-адрес своего веб-сайта в PageSpeed ​​Insights. После ввода URL-адреса данные полей для него (если они доступны) будут отображаться для нескольких показателей, включая INP. Вы также можете использовать переключатели, чтобы проверить значения INP для размеров мобильных устройств и настольных компьютеров.

Полевые данные, показанные CrUX в PageSpeed ​​Insights, показывающие LCP, INP, CLS в трех основных веб-показателях, а также TTFB, FCP в качестве диагностических показателей и FID в качестве устаревших показателей основных веб-показателей.
Считывание данных CrUX, как видно из статистики PageSpeed. В этом примере INP данной веб-страницы нуждается в улучшении.

Эти данные полезны, поскольку они сообщают вам, есть ли у вас проблемы. Однако CrUX не может сказать вам, что вызывает проблемы. Существует множество доступных решений для мониторинга реальных пользователей (RUM), которые помогут вам собрать собственные данные полей от пользователей вашего веб-сайта, чтобы помочь вам ответить на этот вопрос, и один из вариантов — собрать эти данные полей самостоятельно с помощью библиотеки JavaScript web-vitals.

Собирайте полевые данные с помощью библиотеки JavaScript web-vitals .

Библиотека JavaScript web-vitals — это скрипт, который вы можете загрузить на свой веб-сайт для сбора данных о полях от пользователей вашего веб-сайта. Вы можете использовать его для записи ряда показателей, включая INP в браузерах, которые его поддерживают.

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

  • Хром: 96.
  • Край: 96.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Источник

Стандартную сборку библиотеки 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.

Сборка атрибуции библиотеки веб-показателей предоставляет дополнительные данные, которые вы можете получить от пользователей на местах, чтобы помочь вам лучше устранять проблемные взаимодействия, влияющие на 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
});
Как появляются консольные логи из библиотеки web-vitals. Консоль в этом примере показывает имя метрики (INP), значение INP (56), если это значение находится в пределах пороговых значений INP (хорошо), а также различные биты информации, отображаемые в объекте атрибуции, включая записи из API длинных кадров анимации.
Как данные из библиотеки web-vitals отображаются в консоли.

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

  • «Взаимодействовал ли пользователь со страницей во время ее загрузки?»
  • «Длительно ли работали обработчики событий взаимодействия?»
  • «Был ли задержан запуск кода обработчика событий взаимодействия? Если да, что еще происходило в основном потоке в это время?»
  • «Вызвало ли это взаимодействие большую работу по рендерингу, которая задержала прорисовку следующего кадра?»

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

ключ объекта attribution Данные
interactionTarget Селектор CSS, указывающий на элемент, создавший значение INP страницы, например button#save .
interactionType Тип взаимодействия: щелчки, касания или ввод с клавиатуры.
inputDelay * Задержка ввода взаимодействия.
processingDuration * Время с момента запуска первого прослушивателя событий в ответ на взаимодействие с пользователем до завершения всей обработки прослушивателя событий.
presentationDelay * Задержка представления взаимодействия, которая происходит с момента завершения обработчиков событий до момента рисования следующего кадра.
longAnimationFrameEntries * Записи из LoAF, связанные с взаимодействием. Дополнительную информацию смотрите далее.
*Новое в версии 4.

Начиная с версии 4 библиотеки web-vitals, вы можете получить еще более глубокое понимание проблемных взаимодействий с помощью данных, которые она предоставляет с разбивкой по фазам INP (задержка ввода, продолжительность обработки и задержка представления) и API длинных анимационных кадров (LoAF) .

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

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

  • Хром: 123.
  • Край: 123.
  • Firefox: не поддерживается.
  • Сафари: не поддерживается.

Источник

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

Сборка атрибуции библиотеки web-vitals предоставляет массив записей LoAF под ключом longAnimationFrameEntries объекта attribution . В следующей таблице перечислены несколько ключевых битов данных, которые вы можете найти в каждой записи LoAF:

Ключ объекта входа LoAF Данные
duration Продолжительность длинного кадра анимации до завершения макета, но исключая рисование и композицию.
blockingDuration Общее количество времени в кадре, в течение которого браузер не мог быстро отреагировать из-за длительных задач. Это время блокировки может включать в себя длительные задачи, выполняющие JavaScript, а также любые последующие длительные задачи рендеринга в кадре.
firstUIEventTimestamp Временная метка того, когда событие было поставлено в очередь во время кадра. Полезно для определения начала задержки ввода взаимодействия.
startTime Начальная временная метка кадра.
renderStart Когда началась работа по рендерингу кадра. Сюда входят любые обратные вызовы requestAnimationFrame (и обратные вызовы ResizeObserver если применимо), но, возможно, до начала какой-либо работы со стилем/макетом.
styleAndLayoutStart Когда происходит работа над стилем/макетом в кадре. Может быть полезно при определении продолжительности работы над стилем/макетом при определении других доступных временных меток.
scripts Массив элементов, содержащих информацию об атрибуции сценария, вносящую вклад в INP страницы.
Визуализация длинного кадра анимации по модели LoAF.
Диаграмма таймингов длинного кадра анимации по LoAF API (за 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 , для определения некоторых причин медленного взаимодействия.

Длительная продолжительность обработки

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

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.sort((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.sort((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.sort((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' означают, что задача блокировки возникла в результате какой-то асинхронной работы, которая была запущена ранее и решена или отклонена в тот момент, когда пользователь пытался взаимодействовать со страницей, что задерживает взаимодействие.

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

Длительные задержки презентации

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

import {onINP} from 'web-vitals/attribution';

onINP(({name, value, attribution}) => {
  const {presentationDelay} = attribution; // 113.32307691
});

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

Дорогая стилистика и верстка.

Длительные задержки представления могут быть дорогостоящими пересчетами стилей и работой по макету , что возникает по ряду причин, включая сложные селекторы CSS и большие размеры DOM . Вы можете измерить продолжительность этой работы с помощью таймингов LoAF, доступных в библиотеке веб-виталов:

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.sort((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.sort((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), вы можете быть более уверены в том, какие взаимодействия являются наиболее проблематичными, а затем перейти к воспроизведению проблемных взаимодействий в лаборатории , а затем приступить к исправлению. их.

Героическое изображение из Unsplash , автор Федерико Респини .