Определять начальное состояние видимости

Чтобы точно отслеживать производительность, важно определить, была ли страница изначально загружена в фоновом режиме (например, открыта в новой фоновой вкладке). Страницы, загруженные в фоновом режиме, часто имеют задержку рендеринга и более длительное время метрик (например, First Contentful Paint). Выявив такие страницы, вы сможете отфильтровать их в аналитике эффективности, чтобы избежать искажения данных.

Наиболее точный способ измерения – использовать API VisibilityStateEntry, который надежно регистрирует изменения видимости на временной шкале производительности браузера, независимо от того, когда фактически выполняется ваш скрипт.

Определение начальной видимости и времени в фоновом режиме

ОБЯЗАТЕЛЬНО. Используйте performance.getEntriesByType('visibility-state'), чтобы получить доступ к точной истории видимости. Не полагайтесь только на проверку document.visibilityState во время выполнения, так как это может привести к состоянию гонки.

/**
 * Accurately determines visibility state history using the Performance API.
 */
function getVisibilityInfo() {
  // Retrieve VisibilityStateEntry members:
  const entries = performance.getEntriesByType('visibility-state');
  
  if (entries.length > 0) {
    const firstEntry = entries[0];
    
    // If the first performance entry for visibility is 'hidden',
    // the page was loaded in the background.
    const initiallyBackgrounded = firstEntry.name === 'hidden';
    
    // Find the precise, high-resolution timestamp of when the page 
    // was first backgrounded.
    let timeBackgrounded = null;
    for (const entry of entries) {
      if (entry.name === 'hidden') {
        // entry.startTime is used because it provides the exact browser 
        // timestamp of the visibility change, which is required for precision
        timeBackgrounded = entry.startTime;
        break;
      }
    }

    return {
      initiallyBackgrounded,
      timeBackgrounded
    };
  }
}

Резервные стратегии

В неподдерживаемых средах можно использовать свойство document.visibilityState или событие visibilitychange.

ВАЖНО! Помните, что этот резервный подход часто крайне неточен при определении исходного фонового состояния. Поскольку скрипты могут загружаться и выполняться асинхронно, страница может быть открыта в фоновой вкладке, а затем пользователь может перейти на нее до того, как скрипт завершит загрузку и выполнение. Когда скрипт наконец запустится, document.visibilityState синхронно прочитает 'visible', и вы ошибочно предположите, что страница была загружена на переднем плане, полностью упустив ее исходное скрытое состояние. Кроме того, резервная временная метка не обладает внутренней точностью Performance API. Если точность имеет высокий приоритет, не используйте резервный вариант.

/**
 * Fallback implementation using document.visibilityState.
 * This approach is prone to race conditions if the script loads asynchronously.
 */
function getFallbackVisibilityInfo() {
  // Check the state exactly when this script executes.
  // This will fail to detect an initial background state if the user 
  // foregrounded the page before this script executed.
  let initiallyBackgrounded = document.visibilityState === 'hidden';
  
  // If it's hidden now, we approximate that it was hidden from load (time 0).
  let timeBackgrounded = initiallyBackgrounded ? 0 : null;

  // Listen for future visibility changes to capture if it is backgrounded later.
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden' && timeBackgrounded === null) {
      // performance.now() is used here as a fallback, but it only gives 
      // us the time the event listener fired, not the precise internal 
      // browser time the visibility actually changed.
      timeBackgrounded = performance.now();
    }
  });

  return {
    get initiallyBackgrounded() { return initiallyBackgrounded; },
    get timeBackgrounded() { return timeBackgrounded; }
  };
}

// Modern implementation using VisibilityStateEntry API.
function getVisibilityInfo() {
  // Code omitted here would be the same modern
  // implementation shown earlier in this guide
  // ...
}

// DO: Detect if the VisibilityStateEntry API is available
if ('VisibilityStateEntry' in window) {
  // DO: If VisibilityStateEntry is available, use it first:
  getVisibilityInfo();
} else {
  // DO: If VisibilityStateEntry is unavailable, fall back to `document.visibilityState`:
  getFallbackVisibilityInfo();
}