Web Performance API: Измерьте важное

Источник: «Web Performance API: Measure What Matters»
От медленного к быстрому: Использование JavaScript Performance API для оптимизации веб-приложений

Производительность веб-сайта — это не только быстрая загрузка. Речь идёт о понимании того, что происходит с момента, когда пользователь попадает на сайт, до того, как он сможет им воспользоваться. Performance API открывает этот чёрный ящик поведения браузера.

Скорость влияет на всё. Пользователи покидают медленные сайты. Поисковые системы накладывают на них ограничения. Мобильные пользователи расстраиваются, когда приложения разряжают их батареи. Performance API помогает измерить эти реальные последствия.

// Базовое использование Performance API
const pageLoad = performance.now();
const navigationTiming = performance.getEntriesByType('navigation')[0];
const paintTiming = performance.getEntriesByType('paint');

console.log(`Page load time: ${pageLoad}ms`);
console.log(`Time to first byte: ${navigationTiming.responseStart}ms`);
console.log(`First paint: ${paintTiming[0].startTime}ms`);

В то время как базовый API предоставляет снапшоты, PerformanceObserver позволяет наблюдать за метриками производительности по мере их возникновения:

// Создаём наблюдателя для непрерывного мониторинга производительности
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
switch(entry.entryType) {
case 'navigation':
console.log(`Page loaded in ${entry.loadEventEnd}ms`);
break;

case 'paint':
console.log(`${entry.name} at ${entry.startTime}ms`);
break;

case 'largest-contentful-paint':
console.log(`Main content visible at ${entry.startTime}ms`);
break;

case 'layout-shift':
console.log(`Layout shift score: ${entry.value}`);
break;
}
}
});

// Начинаем наблюдать за несколькими типами записей производительности
observer.observe({
entryTypes: [
'navigation',
'paint',
'largest-contentful-paint',
'layout-shift'
]
});

На практике часто используются оба метода вместе. Прямые вызовы API дают оперативные данные, а наблюдатель отслеживает текущую производительность:

class PerformanceTracker {
constructor() {
// Получение исходных данных тайминга
this.initialLoad = performance.now();
this.navigationTiming = performance.getEntriesByType('navigation')[0];

// Настройка постоянного мониторинга
this.observer = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (this.isSignificantMetric(entry)) {
this.logMetric(entry);
}
});
});

// Начинаем наблюдать
this.observer.observe({
entryTypes: [
'navigation',
'resource',
'paint',
'largest-contentful-paint'
]
});
}

isSignificantMetric(entry) {
const significantMetrics = [
'first-paint',
'first-contentful-paint',
'largest-contentful-paint'
];

return significantMetrics.includes(entry.name);
}

logMetric(entry) {
const timeSinceLoad = performance.now() - this.initialLoad;

console.log(
`${entry.name} occurred at ${entry.startTime}ms ` +
`(${timeSinceLoad}ms since tracking started)`
);
}
}

// Начинаем отслеживать
const tracker = new PerformanceTracker();

Такой комбинированный подход даёт лучшее из двух миров: немедленный доступ к данным о производительности и постоянный мониторинг ключевых показателей по мере их появления.

Performance API удобен для мониторинга Core Web Vitals — показателей, непосредственно влияющих на пользовательский опыт.

// Отслеживание Largest Contentful Paint (LCP)
let lcpValue = 0;
const lcpObserver = new PerformanceObserver((list) => {
const entries = list.getEntries();
// We only care about the most recent LCP
const lastEntry = entries[entries.length - 1];
lcpValue = lastEntry.renderTime || lastEntry.loadTime;
});

lcpObserver.observe({ entryTypes: ['largest-contentful-paint'] });

// Отслеживание First Input Delay (FID)
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
const delay = entry.processingStart - entry.startTime;
console.log(`First input delay: ${delay}ms`);
}
}).observe({ entryTypes: ['first-input'] });

// Отслеживание Cumulative Layout Shift (CLS)
let clsValue = 0;
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (!entry.hadRecentInput) {
clsValue += entry.value;
}
}
}).observe({ entryTypes: ['layout-shift'] });

Производительность загрузки ресурсов

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

Когда браузеры загружают ресурсы, они следуют определённым шаблонам:

// Отслеживание производительности загрузки ресурсов
const resourceObserver = new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
// Filter out non-critical resources
if (entry.initiatorType === 'script' ||
entry.initiatorType === 'css' ||
entry.initiatorType === 'fetch') {

const timing = {
name: entry.name,
type: entry.initiatorType,
startTime: entry.startTime,
duration: entry.duration,
transferSize: entry.transferSize,
// Время, затраченное на ожидание сервера
waitTime: entry.responseStart - entry.requestStart,
// Время, затраченное на загрузку
downloadTime: entry.responseEnd - entry.responseStart
};

if (timing.duration > 500) {
console.warn('Slow resource load:', timing);
}
}
});
});

resourceObserver.observe({
entryTypes: ['resource'],
buffered: true // Включая ресурсы, загруженные до создания наблюдателя
});

Последовательность имеет значение. Некоторые ресурсы полностью блокируют рендеринг до их загрузки (например, CSS), в то время как другие могут загружаться параллельно (например, изображения). Понимание этих закономерностей помогает выявить возможности оптимизации:

// Определение ресурсов блокирующих рендеринг и эффект водопада
const waterfall = new PerformanceObserver((list) => {
const entries = list.getEntries();
let lastBlockingResource = 0;

entries.forEach(entry => {
if (entry.renderBlockingStatus === 'blocking') {
const endTime = entry.startTime + entry.duration;
lastBlockingResource = Math.max(lastBlockingResource, endTime);

console.log(
`Blocking resource ${entry.name} delayed rendering by ${entry.duration}ms`
);
}
});

console.log(`Total render blocking time: ${lastBlockingResource}ms`);
});

waterfall.observe({ entryTypes: ['resource'] });

Понимая, как загружаются ресурсы, можно принимать взвешенные решения:

Мониторинг производительности — это не просто сбор показателей, а их использование для принятия обоснованных решений. Если вы отлаживаете медленную загрузку страницы, оптимизируете доставку ресурсов или обеспечиваете плавную работу пользователей, Performance API предоставляет информацию, необходимую для значимых улучшений.

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

Комментарии


Дополнительные материалы

Предыдущая Статья

Эксперимент: Автоматическое включение View Transitions с MutationObserver

Следующая Статья

Предзагрузка отзывчивых изображений