Дата публикации документа: 02-03-2026

Дата обновления документа: 02-03-2026

Performance Guard: клиентская система оптимизации производительности для веб-приложений

Контроль нагрузки на клиенте: мониторинг FPS, энергосбережение, автоматическая очистка ресурсов

Система проактивной оптимизации клиентской производительности — определяет признаки перегрузки (падение FPS, высокая активность таймеров), реагирует на разряд батареи и скрытие вкладки, автоматически отключая зависшие и ресурсоёмкие процессы.

Документ ориентирован для решения вопросов:

Содержание:

Тип приложения FPS
мониторинг
Таймеры
(setInterval/Timeout)
Observers
(MutationObserver)
WebSocket Энергосбережение
(батарея)
Видимость
вкладки
Аналитика
(события)
Критические
страницы
SPA (React, Vue, Angular)
OpenCart / WooCommerce
Laravel / Symfony + JS
Коробочные CMS

Встроенные API браузера:

Совместимость: Chrome, Firefox, Edge, Android Browser, Safari (с ограничениями). navigator.getBattery() доступен только в Chromium-браузерах.

Без внешних зависимостей. Не требует сборки. Встраивается как скрипт перед </body>.

Ориентирована на использование: на мобильных устройствах, в SPA, интерактивных виджетах и системах с высокой динамикой.

Схема PerfGuard: цепочка контроля производительности
Критическая страница?
Да → Отключить систему
Нет → Запустить через 10с
Запуск мониторинга
FPS < 40? + порог превышен?
→ perf.guard.low
Скрытие вкладки?
→ perf.guard.low
Низкий заряд батареи?
→ perf.guard.low
Событие: perf.guard.low
Очистка: setInterval, setTimeout
Отключение: MutationObserver
Закрытие: WebSocket
Остановка: requestAnimationFrame
Восстановление?
Да → perf.guard.high
Событие: perf.guard.high
Система возвращается в ожидание
Проверка
FPS
Событие
Действие
Мониторинг
Энергия
Фокус

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

1. Описание системы: PerfGuard — проактивная защита клиентской производительности

PerfGuard — это легковесная клиентская система контроля ресурсоёмкости на стороне клиента, ориентированная на работу в условиях ограниченных ресурсов: слабые мобильные устройства, низкий заряд батареи, фоновые вкладки.

Система не требует серверной инфраструктуры, работает полностью в браузере и использует стандартные API. Её задача — предотвратить ухудшение UX, вызванное перегрузкой CPU, тепловым троттлингом и высоким энергопотреблением, что может привести к просадке FPS, снижению отзывчивости и перегреву устройства.

Ключевые принципы:

2. Компоненты системы

2.1. Модуль мониторинга FPS

Использует requestAnimationFrame для точного измерения частоты кадров. Расчёт FPS происходит каждую секунду. При падении ниже порога и повторном срабатывании активируется режим низкой производительности.

// === МОДУЛЬ МОНИТОРИНГА FPS ===
(function () {
  // Внутренние переменные состояния
  let isLowPerformance = false;        // Текущее состояние системы
  let lowFpsCount = 0;                 // Счётчик последовательных падений FPS
  let frameCount = 0;                  // Количество кадров за текущую секунду
  let lastTime = performance.now();    // Время начала отсчёта для расчёта FPS
  let currentFps = 60;                 // Текущее значение FPS (начальное)
  let lastLowPerformanceTime = 0;      // Время последнего включения режима экономии

  /**
   * Основной цикл измерения FPS
   * Использует requestAnimationFrame для точного отслеживания частоты обновления экрана
   */
  const measureFps = () => {
    frameCount++; // Увеличиваем счётчик кадров
    const currentTime = performance.now();

    // Каждую секунду пересчитываем FPS
    if (currentTime - lastTime >= 1000) {
      currentFps = Math.round((frameCount * 1000) / (currentTime - lastTime));
      frameCount = 0;
      lastTime = currentTime;
    }

    // Рекурсивный вызов через следующий кадр
    requestAnimationFrame(measureFps);
  };

  // Запускаем измерение FPS
  requestAnimationFrame(measureFps);

  /**
   * Генерация пользовательских событий для внешних подписчиков
   * Позволяет подключать аналитику, UI-индикаторы и другие реактивные элементы
   */
  const dispatchPerfEvent = (eventType) => {
    window.dispatchEvent(new CustomEvent('perf.guard.' + eventType, {
      detail: { fps: currentFps },
      bubbles: false,
      cancelable: false
    }));
  };

  /**
   * Активация режима низкой производительности
   * Защита от дребезга — проверка интервала между срабатываниями
   */
  const setLowPerformance = () => {
    const now = Date.now();
    if (isLowPerformance || now - lastLowPerformanceTime < CONFIG.BATTERY_CHECK_INTERVAL) {
      return;
    }

    lastLowPerformanceTime = now;
    isLowPerformance = true;
    dispatchPerfEvent('low'); // Уведомляем внешние системы
  };

  /**
   * Сброс режима низкой производительности
   * Вызывается при восстановлении FPS или заряда батареи
   */
  const resetLowPerformance = () => {
    if (!isLowPerformance) return;

    isLowPerformance = false;
    dispatchPerfEvent('high'); // Уведомляем о восстановлении
  };

  /**
   * Основной цикл проверки производительности
   * Запускается с заданным интервалом из конфигурации
   */
  const checkPerformance = () => {
    const now = Date.now();
    const isMobileDevice = CONFIG.MOBILE_REGEX.test(navigator.userAgent);
    const isLowFps = currentFps < CONFIG.MIN_FPS && isMobileDevice;

    // Проверяем стабильность низкого FPS
    if (isLowFps) {
      lowFpsCount++;
      if (lowFpsCount >= CONFIG.LOW_PERF_THRESHOLD) {
        setLowPerformance();
      }
    } else {
      lowFpsCount = 0; // Сбрасываем счётчик при нормализации
    }

    // Автоматический сброс после длительного периода
    if (isLowPerformance && now - lastLowPerformanceTime >= CONFIG.LOW_PERF_DURATION) {
      resetLowPerformance();
    }

    setTimeout(checkPerformance, CONFIG.CHECK_INTERVAL);
  };

  // Запуск основного цикла
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', checkPerformance, { once: true });
  } else {
    checkPerformance();
  }

  // Инициализация времени последнего срабатывания
  lastLowPerformanceTime = Date.now();
})();

2.2. Модуль управления ресурсами

Переопределяет глобальные методы (setInterval, setTimeout, MutationObserver, WebSocket, requestAnimationFrame), чтобы отслеживать их использование. При срабатывании события perf.guard.low все активные экземпляры принудительно останавливаются.

// === МОДУЛЬ УПРАВЛЕНИЯ РЕСУРСАМИ ===
(function () {
  // Хранилища для отслеживания всех активных ресурсов
  const intervalIds = new Set();
  const timeoutIds = new Set();
  const observers = new Set();
  const websockets = new Set();
  window.__perf_cleaner_raf_ids = new Set(); // Отдельное хранилище для rAF

  // Сохраняем оригинальные реализации для последующего вызова
  const originalSetInterval = window.setInterval;
  const originalSetTimeout = window.setTimeout;
  const OriginalMutationObserver = window.MutationObserver;
  const OriginalWebSocket = window.WebSocket;
  const originalRequestAnimationFrame = window.requestAnimationFrame;

  /**
   * Переопределение setInterval с отслеживанием ID
   * Все создаваемые интервалы автоматически попадают в реестр
   */
  window.setInterval = (callback, delay) => {
    const id = originalSetInterval(callback, delay);
    intervalIds.add(id);
    return id;
  };

  /**
   * Переопределение setTimeout с отслеживанием ID
   * Позволяет очистить даже неочищенные таймауты
   */
  window.setTimeout = (callback, delay) => {
    const id = originalSetTimeout(callback, delay);
    timeoutIds.add(id);
    return id;
  };

  /**
   * Переопределение MutationObserver для контроля наблюдателей DOM
   * Автоматически отключает observer при срабатывании системы
   */
  window.MutationObserver = class extends OriginalMutationObserver {
    constructor(callback) {
      super(callback);
      observers.add(this);
    }

    disconnect() {
      observers.delete(this);
      super.disconnect();
    }
  };

  /**
   * Переопределение WebSocket для контроля соединений
   * Добавляет отслеживание и безопасное закрытие
   */
  window.WebSocket = function (url) {
    const ws = new OriginalWebSocket(url);
    websockets.add(ws);

    const originalClose = ws.close;
    ws.close = function () {
      websockets.delete(ws);
      originalClose.call(this);
    };

    return ws;
  };

  /**
   * Переопределение requestAnimationFrame с отслеживанием
   * Использует замыкание для проверки актуальности перед выполнением
   */
  window.requestAnimationFrame = function (callback) {
    const id = originalRequestAnimationFrame(function () {
      if (window.__perf_cleaner_raf_ids.has(id)) {
        window.__perf_cleaner_raf_ids.delete(id);
        callback.apply(this, arguments);
      }
    });

    window.__perf_cleaner_raf_ids.add(id);
    return id;
  };

  /**
   * Главный обработчик события низкой производительности
   * Принудительно останавливает все зарегистрированные ресурсы
   */
  window.addEventListener('perf.guard.low', () => {
    // Очистка всех интервалов
    for (const id of intervalIds) {
      clearInterval(id);
    }
    intervalIds.clear();

    // Очистка всех таймаутов
    for (const id of timeoutIds) {
      clearTimeout(id);
    }
    timeoutIds.clear();

    // Отключение всех наблюдателей
    for (const observer of observers) {
      observer.disconnect();
    }
    observers.clear();

    // Закрытие всех WebSocket соединений
    for (const ws of websockets) {
      ws.close();
    }
    websockets.clear();

    // Очистка requestAnimationFrame
    for (const id of window.__perf_cleaner_raf_ids) {
      originalRequestAnimationFrame(() => {});
    }
    window.__perf_cleaner_raf_ids.clear();
  });
})();

2.3. Детектор критических страниц

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

// === ДЕТЕКТОР КРИТИЧЕСКИХ СТРАНИЦ ===
// Проверка, соответствует ли текущий путь одному из критических

const isCriticalPage = () => {
  const pathname = location.pathname;
  const search = location.search;

  // Проверяем частичное вхождение пути в списке критических
  return CRITICAL_PAGES.some(page => pathname.includes(page));
};

// Устанавливаем флаг, если находимся на критической странице
if (isCriticalPage()) {
  window.__perfGuard_criticalPage = true;
}

2.4. Система энергосбережения

Интегрируется с navigator.getBattery() для отслеживания уровня заряда. При разряде ниже заданного порога (по умолчанию 15%) активируется режим экономии.

// === СИСТЕМА ЭНЕРГОСБЕРЕЖЕНИЯ ===
// Проверка поддержки API батареи и установка слушателей

if (navigator.getBattery) {
  navigator.getBattery().then(battery => {
    /**
     * Функция проверки состояния батареи
     * Вызывается при изменении уровня или режима зарядки
     */
    const checkBattery = () => {
      // Активируем режим экономии при низком заряде и отсутствии зарядки
      if (!battery.charging && battery.level < CONFIG.BATTERY_LOW_LEVEL) {
        setLowPerformance();
      }
    };

    // Подписываемся на изменения состояния
    battery.addEventListener('chargingchange', checkBattery, { passive: true });
    battery.addEventListener('levelchange', checkBattery, { passive: true });

    // Проверяем состояние сразу после инициализации
    checkBattery();
  }).catch(() => {
    // Игнорируем ошибки (например, при отсутствии поддержки)
  });
}

2.5. Обработчик видимости вкладки

Реагирует на событие visibilitychange. При переходе вкладки в фоновый режим сразу активируется низкая производительность. При возврате — проверяется восстановление FPS.

// === ОБРАБОТЧИК ВИДИМОСТИ ВКЛАДКИ ===
// Реагирует на сворачивание/разворачивание окна браузера

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    // При переходе во вкладку в фон активируем режим экономии
    setLowPerformance();
  } else {
    // При возвращении проверяем восстановление производительности
    setTimeout(() => {
      if (currentFps >= CONFIG.MIN_FPS) {
        resetLowPerformance();
      }
    }, 800); // Небольшая задержка для стабилизации
  }
}, { passive: true });

2.6. Генератор событий

Выпускает кастомные события perf.guard.low и perf.guard.high с деталями (текущий FPS). Это позволяет подключать внешние обработчики: аналитику, логирование, UI-индикаторы.

// === ГЕНЕРАТОР СОБЫТИЙ ===
// Централизованная функция для отправки событий в систему

const dispatchPerfEvent = (eventType) => {
  window.dispatchEvent(new CustomEvent('perf.guard.' + eventType, {
    detail: { 
      fps: currentFps,
      timestamp: Date.now(),
      userAgent: navigator.userAgent
    },
    bubbles: false,
    cancelable: false
  }));
};

// Пример использования в других модулях:
// dispatchPerfEvent('low');   // При активации режима экономии
// dispatchPerfEvent('high');  // При восстановлении производительности

/**
 * Пример подписки на события (можно использовать в другом коде):
 * 
 * window.addEventListener('perf.guard.low', (e) => {
 *   console.log('Производительность снижена:', e.detail.fps);
 *   // Отправить в аналитику, показать индикатор и т.д.
 * });
 * 
 * window.addEventListener('perf.guard.high', (e) => {
 *   console.log('Производительность восстановлена:', e.detail.fps);
 * });
 */

2.7. Отложенный запуск

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

// === ОТЛОЖЕННЫЙ ЗАПУСК СИСТЕМЫ ===
// Запуск через 10 секунд после начала загрузки страницы

const timeoutId = setTimeout(() => {
  // Здесь вставляется весь основной код системы
  // Модуль мониторинга FPS
  // Модуль управления ресурсами
  // Система энергосбережения
  // ...
}, 10000); // 10 секунд — достаточно для завершения основной инициализации

// Отмена запуска, если вкладка была скрыта до истечения таймаута
document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    clearTimeout(timeoutId);
  }
}, { once: true });

3. Заключение: полная конфигурация системы PerfGuard

Код размещается в конце <body> вашего приложения. Параметры настраиваются под приложение.


/*
 * Performance Guard — система проактивной оптимизации клиентской производительности
 * 
 * Назначение:
 * Автоматически снижает нагрузку на устройстве при:
 * - Падении FPS ниже порога
 * - Низком заряде батареи
 * - Переходе вкладки в фон
 * 
 * Реакция: отключение тяжёлых процессов (таймеры, observers, WebSocket, анимации)
 * 
 * Особенности:
 * - Работает только на мобильных устройствах (по умолчанию)
 * - Не вмешивается в критические страницы (корзина, оплата)
 * - Запускается через 10 секунд после загрузки
 * - Генерирует события для аналитики и внешних реакций
 * 
 * Как настроить под конкретное приложение:
 * 1. Измените URL страниц в массиве CRITICAL_PAGES
 * 2. Настройте параметры в разделе конфигурации под характеристики вашего приложения
 * 3. Добавьте обработчики событий 'perf.guard.low' и 'perf.guard.high' для
 *    выполнения собственных действий при изменении производительности
 */


// === КОНФИГУРАЦИЯ ===
// Все настраиваемые параметры собраны здесь — легко менять под задачи проекта.
const CRITICAL_PAGES = [
  '/cart',
  '/checkout'
];
// Зачем: чтобы система НЕ активировалась на ключевых страницах, где важна максимальная предсказуемость.
// Как работает: если текущий URL содержит любой из этих путей — PerfGuard не запускается.
// Можно изменить: добавить /payment, /profile, /order.

const CONFIG = {
  MIN_FPS: 40,
  // Минимальный порог FPS. Ниже — начинается проверка на "низкую производительность".
  // Рекомендуется: 30–45. 60 — слишком строго, 20 — слишком мягко.

  CHECK_INTERVAL: 3000,
  // Интервал (в мс) между проверками состояния производительности.
  // Часто — больше нагрузки. Редко — медленная реакция. 3–5 сек — баланс.

  LOW_PERF_DURATION: 8000,
  // Сколько времени система будет находиться в режиме "низкой производительности", после чего сбросится.
  // Нужно, чтобы избежать постоянного "дребезга" состояний.

  LOW_PERF_THRESHOLD: 2,
  // Сколько раз подряд FPS должен быть ниже MIN_FPS, чтобы активировать режим экономии.
  // Защита от ложных срабатываний при кратковременных просадках.

  MOBILE_REGEX: /Android|iPhone|iPad|iPod|IEMobile|Opera Mini/i,
  // Регулярное выражение для определения мобильных устройств.
  // Можно расширить: добавить `BlackBerry`, `webOS`, если нужно.

  BATTERY_CHECK_INTERVAL: 15000,
  // Защита от частых срабатываний при низком заряде.
  // После активации режима экономии повторная активация возможна только через этот интервал.

  BATTERY_LOW_LEVEL: 0.15
  // Уровень заряда (15%), при котором включается энергосбережение.
  // Диапазон: 0.05 (5%) — очень агрессивно, 0.25 (25%) — консервативно.
};

// === ИНИЦИАЛИЗАЦИЯ ГЛОБАЛЬНЫХ ХРАНИЛИЩ ===
if (!window.__perf_cleaner_raf_ids) {
  window.__perf_cleaner_raf_ids = new Set();
}
// Зачем: хранить ID всех requestAnimationFrame, чтобы можно было их принудительно остановить.
// Почему Set: уникальность, быстрое удаление.
// Важно: используется в переопределённом rAF и при очистке.

// === ПРОВЕРКА КРИТИЧЕСКОЙ СТРАНИЦЫ ===
const isCriticalPage = () => {
  const pathname = location.pathname;
  return CRITICAL_PAGES.some(page => pathname.includes(page));
};
// Зачем: определить, находится ли пользователь на заданной критической странице.
// Как: проверяет, содержится ли один из CRITICAL_PAGES в текущем пути.
// Можно улучшить: использовать точное совпадение или регулярные выражения для гибкости.

if (isCriticalPage()) {
  window.__perfGuard_criticalPage = true;
}
// Флаг, который блокирует запуск системы, если мы на критической странице.

// === ЗАПУСК СИСТЕМЫ ===
if (!window.__perfGuard_criticalPage) {
  // Если НЕ на критической странице — можно запускать.
  const timeoutId = setTimeout(() => {
    // Отложенный запуск через 10 секунд — даёт время на инициализацию основного приложения.
	
    // === МОДУЛЬ МОНИТОРИНГА FPS И УПРАВЛЕНИЯ ПРОИЗВОДИТЕЛЬНОСТЬЮ ===
    (function () {
      // Запускается в IIFE для изоляции переменных и предотвращения утечек.

      let isLowPerformance = false;
      // Флаг: находится ли система в режиме "низкой производительности".
      // Определяет, нужно ли генерировать perf.guard.high при восстановлении.

      let lowFpsCount = 0;
      // Счётчик количества раз подряд, когда FPS был ниже порога.
      // Нужен для защиты от ложных срабатываний (например, кратковременная нагрузка).

      let frameCount = 0;
      // Считает количество вызовов rAF за последнюю секунду — основа расчёта FPS.

      let lastTime = performance.now();
      // Время начала текущего измерения (для расчёта интервала в 1 секунду).

      let currentFps = 60;
      // Текущее значение FPS. Обновляется каждую секунду. По умолчанию — 60.

      let lastLowPerformanceTime = 0;
      // Время последнего включения режима низкой производительности.
      // Нужно для ограничения частоты повторной активации (защита от "дребезга").

      // --- РАСЧЁТ FPS ---
      const measureFps = () => {
        frameCount++;
        // Каждый кадр увеличиваем счётчик.

        const currentTime = performance.now();
        // Точное время (с микросекундной точностью) — лучше, чем Date.now().

        if (currentTime - lastTime >= 1000) {
          // Если прошло >= 1 секунда — рассчитываем FPS.
          currentFps = Math.round((frameCount * 1000) / (currentTime - lastTime));
          // Формула: (количество кадров / время в мс) * 1000 = FPS
          // Например: 58 кадров за 998 мс → ~58 FPS.

          frameCount = 0;
          // Сбрасываем счётчик для следующего цикла.

          lastTime = currentTime;
          // Обновляем начальное время.
        }

        // Рекурсивный вызов через rAF — синхронизация с рендерингом браузера.
        requestAnimationFrame(measureFps);
      };
      requestAnimationFrame(measureFps);
      // Запускаем цикл измерения FPS.

      // --- ГЕНЕРАЦИЯ СОБЫТИЙ ДЛЯ АНАЛИТИКИ ---
      const dispatchPerfEvent = (eventType) => {
        window.dispatchEvent(new CustomEvent('perf.guard.' + eventType, {
          detail: { fps: currentFps },
          // Передаёт текущий FPS — полезно для анализа.
          bubbles: false,
          // Событие не всплывает — только на window.
          cancelable: false
          // Нельзя отменить — это уведомление, а не запрос.
        }));
      };

      // --- ПЕРЕКЛЮЧЕНИЕ В РЕЖИМ НИЗКОЙ ПРОИЗВОДИТЕЛЬНОСТИ ---
      const setLowPerformance = () => {
        const now = Date.now();

        // Защита от повторной активации слишком часто.
        if (isLowPerformance || now - lastLowPerformanceTime < CONFIG.BATTERY_CHECK_INTERVAL) {
          return;
        }

        lastLowPerformanceTime = now;
        isLowPerformance = true;
        dispatchPerfEvent('low');
        // Генерируем событие — внешние системы могут реагировать.
      };

      // --- ВОССТАНОВЛЕНИЕ НОРМАЛЬНОЙ ПРОИЗВОДИТЕЛЬНОСТИ ---
      const resetLowPerformance = () => {
        if (!isLowPerformance) return;
        // Только если мы были в режиме экономии.

        isLowPerformance = false;
        dispatchPerfEvent('high');
        // Уведомляем, что нагрузка снижена, можно возвращать функции.
      };

      // --- ПРОВЕРКА СОСТОЯНИЯ ПРОИЗВОДИТЕЛЬНОСТИ ---
      const checkPerformance = () => {
        const now = Date.now();
        const isMobileDevice = CONFIG.MOBILE_REGEX.test(navigator.userAgent);
        // Определяем, мобильное ли устройство — PerfGuard работает только на них.

        const isLowFps = currentFps < CONFIG.MIN_FPS && isMobileDevice;
        // Только на мобильных и только если FPS ниже порога.

        if (isLowFps) {
          lowFpsCount++;
          // Увеличиваем счётчик просадок.

          if (lowFpsCount >= CONFIG.LOW_PERF_THRESHOLD) {
            // Если порог превышен — активируем режим экономии.
            setLowPerformance();
          }
        } else {
          lowFpsCount = 0;
          // Если FPS восстановился — сбрасываем счётчик.
        }

        // Автоматический сброс режима экономии через LOW_PERF_DURATION
        if (isLowPerformance && now - lastLowPerformanceTime >= CONFIG.LOW_PERF_DURATION) {
          resetLowPerformance();
        }

        // Повторная проверка через CHECK_INTERVAL
        setTimeout(checkPerformance, CONFIG.CHECK_INTERVAL);
      };

      // --- РЕАКЦИЯ НА ПОТЕРЮ ФОКУСА ВКЛАДКИ ---
      document.addEventListener('visibilitychange', () => {
        if (document.visibilityState === 'hidden') {
          // При переходе во вкладку в фон — сразу активируем режим экономии.
          setLowPerformance();
        } else {
          // При возврате — ждём 800 мс, чтобы интерфейс стабилизировался.
          setTimeout(() => {
            if (currentFps >= CONFIG.MIN_FPS) {
              // Только если FPS в норме — снимаем режим.
              resetLowPerformance();
            }
          }, 800);
        }
      }, { passive: true });
      // passive: true — чтобы не блокировать ввод.

      // --- РЕАКЦИЯ НА НИЗКИЙ ЗАРЯД БАТАРЕИ ---
      if (navigator.getBattery) {
        navigator.getBattery().then(battery => {
          const checkBattery = () => {
            if (!battery.charging && battery.level < CONFIG.BATTERY_LOW_LEVEL) {
              // Если не заряжается и уровень ниже 15% — экономим энергию.
              setLowPerformance();
            }
          };

          // Подписываемся на изменения состояния зарядки и уровня.
          battery.addEventListener('chargingchange', checkBattery, { passive: true });
          battery.addEventListener('levelchange', checkBattery, { passive: true });

          // Проверяем сразу при запуске.
          checkBattery();
        }).catch(() => {});
        // catch() — игнорируем ошибки (например, если API недоступно).
      }

      // --- ЗАПУСК ПРОВЕРКИ ---
      if (document.readyState === 'loading') {
        // Если страница ещё загружается — ждём DOMContentLoaded.
        document.addEventListener('DOMContentLoaded', checkPerformance, { once: true });
      } else {
        // Если уже загружена — запускаем сразу.
        checkPerformance();
      }

      // Инициализация: фиксируем время старта.
      lastLowPerformanceTime = Date.now();
    })();
	
    // === МОДУЛЬ УПРАВЛЕНИЯ РЕСУРСАМИ ===
    (function () {
      // Запускается в IIFE для изоляции переменных.

      const intervalIds = new Set();
      // Хранит все ID, возвращённые переопределённым setInterval.
      // Нужно, чтобы можно было их массово отключить через clearInterval.

      const timeoutIds = new Set();
      // Аналогично — для setTimeout.

      const observers = new Set();
      // Хранит все экземпляры MutationObserver, созданные после запуска PerfGuard.
      // При срабатывании режима экономии — вызывается .disconnect() для каждого.

      const websockets = new Set();
      // Хранит все WebSocket-соединения, созданные после старта системы.
      // При необходимости — закрываются через .close().

      // --- ПЕРЕОПРЕДЕЛЕНИЕ setInterval ---
      const originalSetInterval = window.setInterval;
      // Сохраняем оригинальную функцию, чтобы вызывать её внутри обёртки.

      window.setInterval = (callback, delay) => {
        const id = originalSetInterval(callback, delay);
        // Вызываем настоящий setInterval и получаем ID.

        intervalIds.add(id);
        // Добавляем ID в реестр — чтобы потом можно было очистить.

        return id;
        // Возвращаем тот же ID — поведение не нарушается.
      };

      // --- ПЕРЕОПРЕДЕЛЕНИЕ setTimeout ---
      const originalSetTimeout = window.setTimeout;
      // Сохраняем оригинал.

      window.setTimeout = (callback, delay) => {
        const id = originalSetTimeout(callback, delay);
        // Выполняем настоящий setTimeout.

        timeoutIds.add(id);
        // Добавляем в реестр.

        return id;
      };

      // --- ПЕРЕОПРЕДЕЛЕНИЕ MutationObserver ---
      const OriginalMutationObserver = window.MutationObserver;
      // Сохраняем оригинальный класс.

      window.MutationObserver = class extends OriginalMutationObserver {
        // Создаём прокси-класс, который расширяет оригинальный.

        constructor(callback) {
          super(callback);
          // Вызываем родительский конструктор.

          observers.add(this);
          // Добавляем экземпляр в реестр при создании.
        }

        disconnect() {
          observers.delete(this);
          // Удаляем из реестра при отключении.

          super.disconnect();
          // Вызываем настоящий disconnect.
        }
      };

      // --- ПЕРЕОПРЕДЕЛЕНИЕ WebSocket ---
      const OriginalWebSocket = window.WebSocket;
      // Сохраняем оригинальный конструктор.

      window.WebSocket = function (url) {
        // Создаём обёртку над WebSocket.

        const ws = new OriginalWebSocket(url);
        // Создаём настоящее соединение.

        websockets.add(ws);
        // Добавляем в реестр.

        const originalClose = ws.close;
        // Сохраняем оригинальный метод close.

        ws.close = function () {
          websockets.delete(ws);
          // Удаляем из реестра при закрытии.

          originalClose.call(this);
          // Вызываем настоящий close.
        };

        return ws;
      };

      // --- ПЕРЕОПРЕДЕЛЕНИЕ requestAnimationFrame ---
      const originalRequestAnimationFrame = window.requestAnimationFrame;
      // Сохраняем оригинал.

      window.requestAnimationFrame = function (callback) {
        // Обёртка над rAF.

        const id = originalRequestAnimationFrame(function () {
          // Регистрируем анимацию.

          if (window.__perf_cleaner_raf_ids.has(id)) {
            // Если ID всё ещё в списке (не был "очищен") — выполняем колбэк.
            window.__perf_cleaner_raf_ids.delete(id);
            // Удаляем ID, чтобы не накапливать мусор.
            callback.apply(this, arguments);
          }
          // Если ID нет — колбэк не выполняется → анимация остановлена.
        });

        window.__perf_cleaner_raf_ids.add(id);
        // Добавляем ID в глобальный реестр для последующей очистки.

        return id;
      };

      // --- РЕАКЦИЯ НА СОБЫТИЕ perf.guard.low ---
      window.addEventListener('perf.guard.low', () => {
        // Когда система определяет низкую производительность — очищаем всё.

        for (const id of intervalIds) clearInterval(id);
        intervalIds.clear();
        // Очищаем все setInterval.

        for (const id of timeoutIds) clearTimeout(id);
        timeoutIds.clear();
        // Очищаем все setTimeout.

        for (const observer of observers) observer.disconnect();
        observers.clear();
        // Отключаем всех MutationObserver.

        for (const ws of websockets) ws.close();
        websockets.clear();
        // Закрываем все WebSocket.

        for (const id of window.__perf_cleaner_raf_ids) {
          // Имитируем выполнение rAF, чтобы "освободить" очередь браузера.
          originalRequestAnimationFrame(() => {});
        }
        window.__perf_cleaner_raf_ids.clear();
        // Очищаем реестр rAF.
      });
    })();
  }, 10000);
  // Задержка 10 секунд — позволяет завершиться инициализации приложения.

  // --- ОТМЕНА ЗАПУСКА ПРИ ПОТЕРЕ ФОКУСА ---
  document.addEventListener('visibilitychange', () => {
    if (document.visibilityState === 'hidden') {
      // Если пользователь сразу перешёл на другую вкладку — отменяем запуск.
      clearTimeout(timeoutId);
    }
  }, { once: true });
  // once: true — слушатель сработает только один раз.
}
// Конец: if (!window.__perfGuard_criticalPage)

Пользователь не обязан понимать, почему приложение тормозит.
Он не знает, что это — снижение FPS, перегрузка CPU или нагрузка от 27 фоновых вкладок с включённым режимом энергосбережения, которые сводят производительность к минимуму.

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

Забота о его комфорте — на нас:
на командах, которые проектируют UX,
на инженерах, которые строят производительные системы,
и на системах вроде PerfGuard.

Документы