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

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

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

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

Начните с CrUX, чтобы оценить потенциальную целевую аудиторию вашего сайта.

Если вы не собираете данные от пользователей вашего сайта, 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 ​​Insights. В этом примере показатель INP данной веб-страницы нуждается в улучшении.

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

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

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

Browser Support

  • Chrome: 96.
  • Край: 96.
  • Firefox: 144.
  • Safari: не поддерживается.

Source

Стандартная сборка библиотеки 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
});
Как выглядят логи консоли из библиотеки 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)

Browser Support

  • Chrome: 123.
  • Edge: 123.
  • Firefox: не поддерживается.
  • Safari: не поддерживается.

Source

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

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

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

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