Дата публикации документа: 02-03-2026
Дата обновления документа: 02-03-2026
Документ ориентирован для решения вопросов:
Содержание:
requestAnimationFramesetInterval, setTimeout, MutationObserver,
WebSocket
navigator.getBattery()visibilitychange)perf.guard.low / perf.guard.high| Тип приложения | FPS мониторинг |
Таймеры (setInterval/Timeout) |
Observers (MutationObserver) |
WebSocket | Энергосбережение (батарея) |
Видимость вкладки |
Аналитика (события) |
Критические страницы |
|---|---|---|---|---|---|---|---|---|
| SPA (React, Vue, Angular) | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| OpenCart / WooCommerce | ✓ | ✓ | ✓ | – | – | – | ✓ | ✓ |
| Laravel / Symfony + JS | ✓ | ✓ | △ | – | ✓ | ✓ | ✓ | ✓ |
| Коробочные CMS | – | ✓ | ✓ | – | – | – | ✓ | ✓ |
Встроенные API браузера:
requestAnimationFrame() — синхронизация с циклом рендерингаperformance.now() — высокоточное измерение времениnavigator.getBattery() — определение уровня заряда батареиvisibilitychange — отслеживание активности вкладкиCustomEvent — генерация пользовательских событийСовместимость: Chrome, Firefox, Edge, Android Browser, Safari (с ограничениями).
navigator.getBattery() доступен только в Chromium-браузерах.
Без внешних зависимостей. Не требует сборки. Встраивается как скрипт перед </body>.
Ориентирована на использование: на мобильных устройствах, в SPA, интерактивных виджетах и системах с высокой динамикой.
Примечание: события "падение FPS", "скрытие вкладки" и "низкий заряд батареи" являются независимыми триггерами. Любое из них может вызвать активацию режима экономии. Диаграмма показывает возможные пути, а не строгую последовательность.
PerfGuard — это легковесная клиентская система контроля ресурсоёмкости на стороне клиента, ориентированная на работу в условиях ограниченных ресурсов: слабые мобильные устройства, низкий заряд батареи, фоновые вкладки.
Система не требует серверной инфраструктуры, работает полностью в браузере и использует стандартные API. Её задача — предотвратить ухудшение UX, вызванное перегрузкой CPU, тепловым троттлингом и высоким энергопотреблением, что может привести к просадке 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();
})();
Переопределяет глобальные методы (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();
});
})();
Система автоматически отключается на указанных критических страницах, таких как корзина или оформление заказа. Это предотвращает возможное вмешательство в процессы, связанные с оплатой и сохранением данных, даже если устройство находится в режиме низкой производительности.
// === ДЕТЕКТОР КРИТИЧЕСКИХ СТРАНИЦ ===
// Проверка, соответствует ли текущий путь одному из критических
const isCriticalPage = () => {
const pathname = location.pathname;
const search = location.search;
// Проверяем частичное вхождение пути в списке критических
return CRITICAL_PAGES.some(page => pathname.includes(page));
};
// Устанавливаем флаг, если находимся на критической странице
if (isCriticalPage()) {
window.__perfGuard_criticalPage = true;
}
Интегрируется с 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(() => {
// Игнорируем ошибки (например, при отсутствии поддержки)
});
}
Реагирует на событие visibilitychange. При переходе вкладки в фоновый режим сразу активируется
низкая производительность. При возврате — проверяется восстановление FPS.
// === ОБРАБОТЧИК ВИДИМОСТИ ВКЛАДКИ ===
// Реагирует на сворачивание/разворачивание окна браузера
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
// При переходе во вкладку в фон активируем режим экономии
setLowPerformance();
} else {
// При возвращении проверяем восстановление производительности
setTimeout(() => {
if (currentFps >= CONFIG.MIN_FPS) {
resetLowPerformance();
}
}, 800); // Небольшая задержка для стабилизации
}
}, { passive: true });
Выпускает кастомные события 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);
* });
*/
Система стартует через 10 секунд после загрузки страницы. Это позволяет завершиться основным процессам инициализации, не мешая первоначальному рендеру и предотвращая стихийные срабатывания из-за кратковременной нагрузки.
// === ОТЛОЖЕННЫЙ ЗАПУСК СИСТЕМЫ ===
// Запуск через 10 секунд после начала загрузки страницы
const timeoutId = setTimeout(() => {
// Здесь вставляется весь основной код системы
// Модуль мониторинга FPS
// Модуль управления ресурсами
// Система энергосбережения
// ...
}, 10000); // 10 секунд — достаточно для завершения основной инициализации
// Отмена запуска, если вкладка была скрыта до истечения таймаута
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
clearTimeout(timeoutId);
}
}, { once: true });
Код размещается в конце <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.
⇪
Документы