Реализация установки PWA на устройство (Add to Home Screen)
Add to Home Screen — механизм, позволяющий пользователю установить веб-приложение на экран устройства без магазина приложений. Это не просто ярлык: установленное PWA запускается без браузерной строки, получает отдельную иконку, появляется в списке приложений и, на Android, в установщике. Разница в удержании пользователей измеримая: в среднем CTR у установленных PWA на 50–70% выше, чем у обычных закладок.
Что требуется для появления промпта
Браузер показывает системный промпт установки только при одновременном выполнении нескольких условий:
- Сайт работает на HTTPS (localhost — исключение при разработке)
- Зарегистрирован Service Worker, который перехватывает минимум fetch-событие
- Присутствует
manifest.jsonс обязательными полями:name,short_name,start_url,display, и иконками 192px и 512px - Пользователь провёл на сайте достаточно времени (Chrome применяет собственную эвристику, точные пороги не документированы публично)
На iOS никакого автоматического промпта нет — только ручное добавление через «Поделиться → На экран Домой». Это принципиальное ограничение Safari, которое никак не обойти.
Перехват события beforeinstallprompt
Chrome и Edge генерируют событие beforeinstallprompt до того, как показать системный диалог. Его нужно перехватить и показать собственный UI в нужный момент:
let deferredPrompt = null;
window.addEventListener('beforeinstallprompt', (e) => {
// Предотвращаем автоматический показ
e.preventDefault();
deferredPrompt = e;
// Показываем собственную кнопку установки
showInstallBanner();
});
async function triggerInstall() {
if (!deferredPrompt) return;
deferredPrompt.prompt();
const { outcome } = await deferredPrompt.userChoice;
// Логируем результат
gtag('event', 'pwa_install', {
event_category: 'PWA',
event_label: outcome, // 'accepted' | 'dismissed'
});
deferredPrompt = null;
hideInstallBanner();
}
window.addEventListener('appinstalled', () => {
// Приложение установлено — скрываем любые баннеры
hideInstallBanner();
deferredPrompt = null;
});
Важно: prompt() можно вызвать только один раз. После вызова deferredPrompt становится невалидным. Если пользователь отклонил — следующего события beforeinstallprompt придётся ждать до нескольких недель (Chrome снова предложит не раньше чем через 90 дней после отказа).
Определение контекста запуска
Нужно понимать, в каком режиме запущено приложение:
function getDisplayMode() {
if (window.matchMedia('(display-mode: standalone)').matches) {
return 'standalone';
}
if (window.matchMedia('(display-mode: fullscreen)').matches) {
return 'fullscreen';
}
if (window.navigator.standalone === true) {
// iOS Safari
return 'standalone-ios';
}
return 'browser';
}
// Проверяем при загрузке
const mode = getDisplayMode();
if (mode === 'browser') {
// Можно показывать подсказку про установку
}
// Слушаем изменение (если пользователь установил в фоне)
window.matchMedia('(display-mode: standalone)').addEventListener('change', (e) => {
if (e.matches) {
console.log('App is now in standalone mode');
}
});
Баннер для iOS
Safari на iOS не поддерживает beforeinstallprompt. Приходится показывать собственную инструкцию:
function shouldShowIOSPrompt() {
const isIOS = /iphone|ipad|ipod/i.test(navigator.userAgent);
const isInStandaloneMode = window.navigator.standalone === true;
const hasSeenPrompt = localStorage.getItem('ios-install-prompt-shown');
return isIOS && !isInStandaloneMode && !hasSeenPrompt;
}
if (shouldShowIOSPrompt()) {
showIOSInstructionBanner(); // показываем кастомный UI с иконкой Share
localStorage.setItem('ios-install-prompt-shown', 'true');
}
Баннер должен показывать иконку кнопки «Поделиться» (которая может различаться между версиями iOS) и текст «Нажмите Share → Добавить на экран Домой». Иконку лучше брать в формате SVG, совпадающем с реальной системной иконкой.
Манифест
Минимальный рабочий manifest.json:
{
"name": "Название приложения",
"short_name": "Приложение",
"description": "Описание для магазина и установщика",
"start_url": "/?source=pwa",
"scope": "/",
"display": "standalone",
"orientation": "any",
"background_color": "#ffffff",
"theme_color": "#1a73e8",
"icons": [
{
"src": "/icons/icon-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "any maskable"
},
{
"src": "/icons/icon-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "any maskable"
}
],
"screenshots": [
{
"src": "/screenshots/desktop.png",
"sizes": "1280x720",
"type": "image/png",
"form_factor": "wide"
},
{
"src": "/screenshots/mobile.png",
"sizes": "390x844",
"type": "image/png",
"form_factor": "narrow"
}
]
}
purpose: "maskable" важен для Android — система может обрезать иконку под различные формы (круг, квадрат со скруглёнными углами). Иконка должна иметь безопасную зону: контент не ближе 10% от краёв.
screenshots появляются в диалоге установки в Chrome начиная с версии 108 — делает промпт более убедительным.
Аналитика воронки установки
Без аналитики непонятно, где пользователи отваливаются:
const INSTALL_FUNNEL = {
BANNER_SHOWN: 'install_banner_shown',
BANNER_CLICKED: 'install_banner_clicked',
PROMPT_SHOWN: 'install_prompt_shown',
PROMPT_ACCEPTED: 'install_prompt_accepted',
PROMPT_DISMISSED: 'install_prompt_dismissed',
APP_INSTALLED: 'app_installed',
};
// Каждый шаг — отдельное событие в GA4 / Amplitude
function trackInstallStep(step, params = {}) {
gtag('event', step, {
event_category: 'pwa_install_funnel',
...params,
});
}
Стандартная воронка: показ баннера → клик → показ системного промпта → принятие. Типичный конверт промпта — 20–40% у мотивированной аудитории.
Сроки реализации
- Манифест + базовый Service Worker + перехват события: 1 день
- Кастомный баннер установки с аналитикой: 1 день
- Поддержка iOS (инструкция + определение режима): 0.5 дня
- Иконки в форматах maskable + адаптация дизайна: 0.5 дня
Итого: 2–3 дня от нуля до полной реализации с аналитикой.







