سنجههای استاندارد رنگآمیزی و پاسخگویی پساز بار شدن اولیه صفحه، شکافی در اندازهگیری ایجاد میکنند:
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 برای هر پیمایش نرم:
- هر دو ورودی
'soft-navigation'و'interaction-contentful-paint'را باbuffered: trueمشاهده کنید. - اجباری: وقتی ورودی
'soft-navigation'دریافت شد، با فراخوانیsoftNavEntry.getLargestInteractionContentfulPaint()، نامزد اولیه LCP آن را مقداردهی اولیه کنید. اگر برنامه محتوا را قبلاز بهروزرسانی نشانی وب رنگآمیزی کرده باشد (برای مثال، رندر کردن یک نما و سپس فراخوانیhistory.pushState())، آن ورودیهای اولیهinteraction-contentful-paintقبلاز ورودیsoft-navigationمنتشر شدهاند و همچنانnavigationIdقبلی را دارند. - اجباری: ورودیهای
'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) درنظر بگیرید:
- تشخیص ویژگی و تنزل متین: ثبت ناظر محافظ با
PerformanceObserver.supportedEntryTypes?.includes('interaction-contentful-paint')وPerformanceObserver.supportedEntryTypes?.includes('soft-navigation'). در مرورگرهایی که پشتیبانی نمیکنند، از ثبت ناظران صرفنظر کنید تا برنامهتان بدون ایجاد خطا اجرا شود. - ناظران ناوبری سخت استاندارد را فعال نگه دارید: به نظارت بر ورودیهای استاندارد
'largest-contentful-paint'،'event'، و'layout-shift'در همه مرورگرها ادامه دهید. چونLargestContentfulPaintدر اولین تعامل کاربر بدونتوجه به پشتیبانی از پیمایش نرم نهایی میشود، اندازهگیری پیمایشهای نرم در مرورگرهای پشتیبانیکننده، اندازهگیریهای LCP بارگذاری سخت اولیه را تغییر نمیدهد یا خراب نمیکند.