اندازه‌گیری زمان تعامل و ترسیم ناوبری نرم

سنجه‌های استاندارد رنگ‌آمیزی و پاسخ‌گویی پس‌از بار شدن اولیه صفحه، شکافی در اندازه‌گیری ایجاد می‌کنند:

  • LargestContentfulPaint (largest-contentful-paint) فقط بارگیری اولیه («سخت») سند را اندازه‌گیری می‌کند و به‌محض اینکه کاربر برای اولین‌بار با صفحه تعامل برقرار می‌کند، انتشار ورودی‌ها را متوقف می‌کند.
  • PerformanceEventTiming و event / Interaction to Next Paint (INP) پاسخ‌دهی ورودی را تا فریم بعدی (مانند حالت فعال دکمه یا نشانگر بارگیری) اندازه‌گیری می‌کند، اما ترسیم‌های ناهمگام بعدی را ردیابی نمی‌کند—مانند محتوای جدیدی که پس‌از حل شدن fetch() یا تکمیل انتقال مسیر سمت کلاینت ارائه می‌شود.
  • مشاهده تغییرات مسیر در «برنامه تک‌صفحه‌ای» (SPA) می‌تواند خارج از کد چارچوب دشوار باشد. فقط نظارت بر به‌روزرسانی‌های نشانی وب می‌تواند تغییرات نشانی وب را که به تغییرات مسیر مرتبط نیستند بیش‌ازحد گزارش کند (برای مثال، تغییرات پیوند ثابت، «هدایت‌های مجدد» متعدد برای یک تغییر مسیر).

انواع ورودی عملکرد InteractionContentfulPaint (interaction-contentful-paint) و PerformanceSoftNavigation (soft-navigation) این شکاف را پر می‌کنند. این APIها علیت تعامل را در سراسر تکالیف ناهمگام (fetch، await، setTimeout) منتشر می‌کنند تا ترسیم‌های محتوایی جدید و انتقال‌های مسیر «برنامه تک‌صفحه‌ای» (SPA) را مستقیماً به تعامل کاربری که آن‌ها را راه‌اندازی کرده است نسبت دهند.

نحوه پیاده‌سازی

۱. مشاهده رنگ‌آمیزی‌های محتوایی که با تعامل‌ها راه‌اندازی شده‌اند

از PerformanceObserver با type: 'interaction-contentful-paint' و buffered: true استفاده کنید تا رنگ‌آمیزی‌های محتوایی بزرگ‌تر را که به‌تدریج به هر تعامل کاربر نسبت داده می‌شود مشاهده کنید.

هر ورودی InteractionContentfulPaint این موارد را ارائه می‌دهد:

  • interactionId: شناسه یکتای تعامل کاربر آغازگر (مطابق با PerformanceEventTiming.interactionId و PerformanceSoftNavigation.interactionId).
  • startTime: مُهر زمان شروع تعامل کاربر آغازگر، نسبت به مبدأ زمان پیمایش سخت اولیه.
  • paintTime و presentationTime: زمانی که فاز پردازش پایان یافت و زمانی که پیکسل‌های رنگ‌شده روی صفحه ترسیم شدند.
  • duration: زمان سپری‌شده از شروع تعامل تا ارائه رنگ (presentationTime - startTime).
  • ‫largestContentfulPaint: شیء عکس آنی LargestContentfulPaint حاوی جزئیات مربوط به بزرگ‌ترین عنصر رنگ‌شده برای آن تعامل تاکنون (element، size، url، id، renderTime، loadTime).
/**
 * Observes contentful paints caused by user interactions and invokes a callback
 * whenever an interaction produces a larger contentful paint.
 *
 * @param {(entry: PerformanceEntry) => void} onInteractionPaint
 */
export function observeInteractionPaints(onInteractionPaint) {
  // Feature-detect support before calling observe() so unsupported browsers
  // do not throw or log console warnings.
  if (
    typeof PerformanceObserver === 'undefined' ||
    !PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint')
  ) {
    return null;
  }

  const observer = new PerformanceObserver((list) => {
    for (const entry of list.getEntries()) {
      // entry.duration is already calculated as (entry.presentationTime - entry.startTime),
      // giving the total time from the user interaction to this contentful paint.
      onInteractionPaint(entry);
    }
  });

  // MANDATORY: Pass buffered: true to capture interaction paints that occurred
  // before this observer was registered.
  observer.observe({ type: 'interaction-contentful-paint', buffered: true });
  return observer;
}

۲. سنجش «بزرگ‌ترین نقاشی دارای محتوا» (LCP) برای پیمایش‌های نرم SPA

پیمایش نرم زمانی توسط مرورگر منتشر می‌شود که تعامل کاربر مطمئن باعث تغییر نشانی وب قابل‌مشاهده و ترسیم محتوا شود. برای محاسبه LCP برای هر پیمایش نرم:

  1. هر دو ورودی 'soft-navigation' و 'interaction-contentful-paint' را با buffered: true مشاهده کنید.
  2. اجباری: وقتی ورودی 'soft-navigation' دریافت شد، با فراخوانی softNavEntry.getLargestInteractionContentfulPaint()، نامزد اولیه LCP آن را مقداردهی اولیه کنید. اگر برنامه محتوا را قبل‌از به‌روزرسانی نشانی وب رنگ‌آمیزی کرده باشد (برای مثال، رندر کردن یک نما و سپس فراخوانی history.pushState())، آن ورودی‌های اولیه interaction-contentful-paint قبل‌از ورودی soft-navigation منتشر شده‌اند و همچنان navigationId قبلی را دارند.
  3. اجباری: ورودی‌های 'interaction-contentful-paint' بعدی را با مطابقت دادن entry.interactionId === softNavEntry.interactionId به پیمایش نرم فعال مرتبط کنید، با entry.navigationId مطابقت ندهید. استفاده از interactionId تضمین می‌کند که رنگ‌آمیزی‌هایی را که قبل‌از به‌روزرسانی نشانی وب رخ داده‌اند لحاظ کنید و رنگ‌آمیزی‌های ناشی از تعاملات نامرتبط بعدی در مسیر جدید را مستثنی کنید.
/**
 * Tracks FCP and LCP for each client-side soft navigation and reports finalized
 * route metrics when the user navigates to a new route or hides the page.
 *
 * @param {(report: {
 *   url: string,
 *   navigationId: number,
 *   interactionId: number,
 *   fcpDuration: number,
 *   lcpDuration: number | null,
 *   lcpElement: Element | null,
 *   lcpSize: number | null
 * }) => void} onSoftNavReport
 */
export function trackSoftNavigationLCP(onSoftNavReport) {
  const supported =
    typeof PerformanceObserver !== 'undefined' &&
    PerformanceObserver.supportedEntryTypes?.includes('soft-navigation') &&
    PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint');

  if (!supported) {
    return null;
  }

  let activeSoftNav = null;
  let activeLcpEntry = null;

  function finalizeCurrentSoftNav() {
    if (!activeSoftNav) return;

    onSoftNavReport({
      // activeSoftNav.name holds the destination URL of the soft navigation.
      url: activeSoftNav.name,
      navigationId: activeSoftNav.navigationId,
      interactionId: activeSoftNav.interactionId,
      // activeSoftNav.duration is (activeSoftNav.presentationTime - activeSoftNav.startTime),
      // which represents First Contentful Paint (FCP) relative to interaction start.
      fcpDuration: activeSoftNav.duration,
      // activeLcpEntry.duration is (activeLcpEntry.presentationTime - activeSoftNav.startTime),
      // representing the soft navigation's LCP duration relative to interaction start.
      lcpDuration: activeLcpEntry ? activeLcpEntry.duration : null,
      lcpElement: activeLcpEntry?.largestContentfulPaint?.element ?? null,
      lcpSize: activeLcpEntry?.largestContentfulPaint?.size ?? null,
    });

    activeSoftNav = null;
    activeLcpEntry = null;
  }

  const softNavObserver = new PerformanceObserver((list) => {
    for (const softNavEntry of list.getEntries()) {
      // Finalize the previous route's metrics before resetting state for the new route.
      finalizeCurrentSoftNav();

      activeSoftNav = softNavEntry;
      // MANDATORY: Seed the LCP candidate with getLargestInteractionContentfulPaint()
      // to capture any attributed contentful paints that occurred before the URL updated.
      activeLcpEntry = softNavEntry.getLargestInteractionContentfulPaint();
    }
  });

  const icpObserver = new PerformanceObserver((list) => {
    for (const icpEntry of list.getEntries()) {
      // MANDATORY: Match by interactionId (not navigationId) so only paints caused
      // by the soft navigation's initiating interaction update the route's LCP.
      if (activeSoftNav && icpEntry.interactionId === activeSoftNav.interactionId) {
        activeLcpEntry = icpEntry;
      }
    }
  });

  softNavObserver.observe({ type: 'soft-navigation', buffered: true });
  icpObserver.observe({ type: 'interaction-contentful-paint', buffered: true });

  // Finalize the active route's LCP when the page is backgrounded or unloaded.
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden') {
      finalizeCurrentSoftNav();
    }
  });

  return { softNavObserver, icpObserver, finalizeCurrentSoftNav };
}

۳. اندازه‌گیری کردن «معیارهای کلیدی اصلی وب» در پیمایش‌های ملایم با web-vitals

اگر هدفتان گزارش کردن «شاخص‌های کلیدی عملکرد وب» استاندارد (LCP،‏ CLS، و INP) در پیمایش‌های سخت و نرم به نقطه پایانی تجزیه‌وتحلیل است، به‌جای تقسیم دستی هر خط زمان سنجه، از کتابخانه web-vitals (نسخه ۶.۰.۰+) با { reportSoftNavs: true } استفاده کنید.

import { onLCP, onFCP, onCLS, onINP } from 'web-vitals';

function sendToAnalytics(metric) {
  // Example-only endpoint; replace with your real telemetry URL.
  navigator.sendBeacon(
    '/analytics',
    JSON.stringify({
      name: metric.name,
      value: metric.value,
      navigationType: metric.navigationType,
      navigationURL: metric.navigationURL,
    }),
  );
}

// Pass { reportSoftNavs: true } to report metrics per soft navigation in addition
// to the initial hard navigation.
onLCP(sendToAnalytics, { reportSoftNavs: true });
onFCP(sendToAnalytics, { reportSoftNavs: true });
onCLS(sendToAnalytics, { reportSoftNavs: true });
onINP(sendToAnalytics, { reportSoftNavs: true });

روال‌های مطلوب

  • باید 'interaction-contentful-paint' و 'soft-navigation' را بااستفاده از PerformanceObserver.supportedEntryTypes?.includes(...) قبل‌از فراخوانی observer.observe() تشخیص ویژگی کنید.
  • باید { buffered: true } را هنگام مشاهده 'interaction-contentful-paint' و 'soft-navigation' ارسال کنید تا اگر کد Analytics به‌صورت ناهمگام مقداردهی اولیه شد، ورودی‌های اولیه ازدست نرود.
  • باید softNavEntry.getLargestInteractionContentfulPaint() را هنگام رسیدگی به ورودی جدید soft-navigation فراخوانی کنید تا نقاشی‌هایی که قبل‌از به‌روزرسانی نشانی وب رخ داده‌اند در LCP گنجانده شوند.
  • باید ورودی‌های interaction-contentful-paint را بااستفاده از interactionId، نه navigationId، با پیمایش ملایم مطابقت دهید.
  • باید از entry.duration (یا entry.presentationTime - entry.startTime) برای اندازه‌گیری تأخیر نقاشی تعامل استفاده کنید، نه اینکه مستقیماً entry.presentationTime را گزارش کنید. همه مُهرهای زمان PerformanceEntry از مبدأ زمان بارگیری اولیه صفحه سخت اندازه‌گیری می‌شوند، نه از شروع پیمایش ملایم.
  • برای نظارت بر برنامه‌های تک‌صفحه‌ای با عمر طولانی، به performance.getEntriesByType('soft-navigation') اتکا نکنید، زیرا میان‌گیر مرورگر به ۵۰ ورودی پیمایش نرم اول محدود است.

راهبردهای جایگزین

اگر هدف «خط پایه» شما شامل مرورگرهایی است که از InteractionContentfulPaint یا PerformanceSoftNavigation پشتیبانی نمی‌کنند، این میاناهای برنامه‌سازی کاربردی را به‌عنوان ارتقای پیش‌رو برای «پایش کاربر واقعی» (RUM) درنظر بگیرید:

  1. تشخیص ویژگی و تنزل متین: ثبت ناظر محافظ با PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint') و PerformanceObserver.supportedEntryTypes?.includes('soft-navigation'). در مرورگرهایی که پشتیبانی نمی‌کنند، از ثبت ناظران صرف‌نظر کنید تا برنامه‌تان بدون ایجاد خطا اجرا شود.
  2. ناظران ناوبری سخت استاندارد را فعال نگه دارید: به نظارت بر ورودی‌های استاندارد 'largest-contentful-paint'، 'event'، و 'layout-shift' در همه مرورگرها ادامه دهید. چون LargestContentfulPaint در اولین تعامل کاربر بدون‌توجه به پشتیبانی از پیمایش نرم نهایی می‌شود، اندازه‌گیری پیمایش‌های نرم در مرورگرهای پشتیبانی‌کننده، اندازه‌گیری‌های LCP بارگذاری سخت اولیه را تغییر نمی‌دهد یا خراب نمی‌کند.