Прогрессивные веб-приложения (PWA) уже много лет обещают разработчикам рай: пиши один раз на HTML, CSS и JavaScript, сохраняй иконку на рабочий стол смартфона — и получай полноценное приложение без 30% комиссии Apple App Store, согласований модерации и мучений со сборками под разные платформы.
Но стоит взяться за проект, требующий работы в реальном времени — интервальный таймер для тренировок, будильник, шагомер или аудио-плеер — как эта радужная картинка разбивается о суровую политику мобильных операционных систем.
Представь реальный сценарий: пользователь запускает веб-таймер, ставит 15 минут, блокирует экран телефона или переключается в Telegram, чтобы ответить на сообщение. И что происходит? Ровно через 5–10 секунд мобильный браузер (особенно iOS WebKit) отправляет вкладку в глубокий сон. Все твои таймеры setTimeout и setInterval замораживаются. Время останавливается! Пользователь достает телефон через полчаса, разблокирует экран, и только в этот момент таймер отмирает и начинает истошно звонить. Опыт испорчен, пользователь удаляет ярлык.
Разработчик мобильного PWA-таймера Kapsula столкнулся ровно с этим барьером и прошел через все круги инженерного ада, чтобы заставить приложение надежно работать в фоне на iOS и Android. Давай разберем проверенные архитектурные приемы и ограничения мобильных ОС.
Почему мобильные ОС убивают фоновый веб?
Главный враг разработчика PWA — политика энергосбережения и оптимизации оперативной памяти мобильных ОС (iOS Throttling Policy и Android Doze Mode):
- iOS Safari / WebKit: как только веб-приложение теряет фокус (
document.visibilityState === "hidden"), частота таймеров принудительно снижается до 1 тика в минуту. А если экран блокируется, фоновые сетевые сокеты и скрипты полностью замораживаются через несколько секунд. - Android Chromium: ведет себя чуть мягче, но при включении режима энергосбережения также безжалостно прибивает фоновые Service Worker через пару минут бездействия.
О том, как устроена современная среда выполнения PWA и какие ограничения накладывают платформы, мы подробно рассуждали в материале анатомия PWA в 2026 году: барьеры установки и ограничения.
Архитектурная триада: как удержать PWA на плаву
Чтобы таймер гарантированно сработал, нельзя наивно рассчитывать на то, что JavaScript будет крутить цикл каждую секунду в спящем телефоне. Надежное решение строится на трех китах:

1. Абсолютное время вместо тиков (Timestamp Delta)
Забудь про инкремент счетчика: secondsRemaining = secondsRemaining - 1. Если вкладку усыпят на 10 минут, счетчик потеряет эти 10 минут.
Вся логика строится на фиксации системной временной метки завершения:
const targetEndTime = Date.now() + durationInMs;
Когда пользователь разблокирует экран, приложение слушает событие visibilitychange и мгновенно пересчитывает оставшееся время: Math.max(0, targetEndTime - Date.now()).
2. Планирование уведомлений через Service Worker и Web Notifications
Чтобы сигнал прозвучал вовремя даже при заблокированном телефоне, уведомление необходимо зарегистрировать на уровне Service Worker через Notification API:
// sw.js — фоновый сервисный воркер
self.addEventListener('notificationclick', (event) => {
event.notification.close();
event.waitUntil(
clients.matchAll({ type: 'window' }).then((clientList) => {
for (const client of clientList) {
if (client.url === '/' && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow('/');
}
})
);
});
// Слушаем команду от основного приложения на запуск системного таймера
self.addEventListener('message', (event) => {
if (event.data.action === 'SCHEDULE_ALARM') {
const { delayMs, title, body } = event.data;
setTimeout(() => {
self.registration.showNotification(title, {
body,
icon: '/icons/timer-192.png',
badge: '/icons/badge-72.png',
vibrate: [200, 100, 200],
requireInteraction: true
});
}, delayMs);
}
});
3. Трюк с фоновым аудиопотоком (Silent Audio Hack)
Для сценариев, когда звуковой сигнал должен воспроизвестись не через тихое системное уведомление, а громко через динамик (например, звук гонга в интервальном таймере), мобильные ОС делают исключение: активное воспроизведение аудио не замораживается.
При запуске таймера Kapsula запускает зацикленный беззвучный аудиопоток (Silent Audio Track) через AudioContext или элемент <audio loop src="silence.mp3">:
// audioKeeper.js
class BackgroundAudioKeeper {
constructor() {
this.audioCtx = null;
this.oscillator = null;
}
// Запуск обязательно по пользовательскому жесту (клик "Старт")
start() {
const AudioContextClass = window.AudioContext || window.webkitAudioContext;
this.audioCtx = new AudioContextClass();
// Создаем генератор с околонулевой амплитудой (человеческое ухо не слышит)
this.oscillator = this.audioCtx.createOscillator();
const gainNode = this.audioCtx.createGain();
gainNode.gain.value = 0.001; // Почти тишина
this.oscillator.connect(gainNode);
gainNode.connect(this.audioCtx.destination);
this.oscillator.start();
}
stop() {
if (this.oscillator) {
this.oscillator.stop();
this.audioCtx.close();
}
}
}
Пока в системе висит активный звуковой контекст, операционная система считает страницу медиаплеером и не переводит вкладку в режим жесткого энергосбережения, позволяя коду отсчитать интервалы и проиграть громкий сигнал.
Удержание экрана через Screen Wake Lock API
Еще одна частая проблема таймеров: человек положил телефон на стойку тренажера, делает планку и хочет видеть оставшиеся секунды, а экран гаснет через 30 секунд.
В современных версиях Chrome и Safari доступен нативный стандарт Screen Wake Lock API:
let wakeLock = null;
async function requestWakeLock() {
try {
wakeLock = await navigator.wakeLock.request('screen');
wakeLock.addEventListener('release', () => {
console.log('Блокировка экрана снята');
});
} catch (err) {
console.error('Ошибка Wake Lock:', err);
}
}
// Перезапрашиваем блокировку при возврате вкладки в активный режим
document.addEventListener('visibilitychange', async () => {
if (wakeLock !== null && document.visibilityState === 'visible') {
await requestWakeLock();
}
});
Цена компромиссов
Применение этих хаков требует чувства меры. Бесконечное удержание беззвучного аудио сажает аккумулятор смартфона значительно быстрее обычного.
В приложении Kapsula разработчики применили гибридный подход: беззвучное аудио включается только на активных коротких интервалах тренировки (до 15–20 минут), а для длинных фоновых таймеров на несколько часов приложение полагается исключительно на расчет временных меток и системные Push-уведомления.
Этот инженерный баланс позволяет создавать PWA, которые по ощущениям ничем не уступают нативным приложениям из магазинов.
