Установленное на домашний экран веб-приложение может выглядеть как настоящий продукт с фирменной иконкой, тёмной темой и быстрым ярлыком для частого действия. А может выглядеть как чужая страница, случайно попавшая на экран: скриншот вместо логотипа, белая вспышка при запуске и адресная строка поверх интерфейса. Разница между двумя картинами почти целиком живёт в одном файле, манифесте веб-приложения, коротком JSON описании того, как сайт должен представляться системе. Ниже разобраны поля манифеста, которые формируют узнаваемый облик PWA: имена и иконки, цветовая схема, стартовый экран, режимы отображения, ярлыки и запуск, с примерами кода и порядком отладки.
Манифест как паспорт веб-приложения для установки
Манифест подключается одной строкой в разделе head страницы и отдаётся сервером с MIME типом application/manifest+json:
<link rel="manifest" href="/manifest.json">
Минимальный каркас, с которого браузер начинает считать сайт устанавливаемым приложением, выглядит так:
{
"name": "Кофейные зерна",
"short_name": "Зерна",
"id": "/",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#f5efe6",
"theme_color": "#3c2a21",
"lang": "ru",
"dir": "ltr"
}
Поле name задаёт полное имя в диалоге установки и в диспетчере приложений, а short_name достаётся сетке рабочего экрана, поэтому его держат в пределах дюжины знаков. Поле id закрепляет личность приложения: система опирается на него при обновлениях, и смена id превращает одно приложение в другое. Пара start_url и scope определяет, какая страница открывается по ярлыку и какие ссылки остаются внутри окна приложения, а какие уходят в браузер. Для признания сайта приложением браузеру ещё нужен сервис-воркер с обработчиком сетевых запросов и защищённое соединение, но визуальная часть полностью управляется манифестом.
Имя и иконки которые видит пользователь на рабочем столе
Иконка это первое, что выдаёт самодельное приложение. Система по очереди перебирает предложенные размеры и берёт самый близкий к нужному, поэтому в манифесте обязаны присутствовать как минимум два формата, 192 и 512 пикселей, иначе установка не пройдёт проверку. Для аккуратного вида на Android добавляют отдельную маскируемую версию, ведь система обрезает иконку под свою фигуру:
"icons": [
{ "src": "/icons/icon-192.png", "sizes": "192x192", "type": "image/png", "purpose": "any" },
{ "src": "/icons/icon-512.png", "sizes": "512x512", "type": "image/png", "purpose": "any" },
{ "src": "/icons/maskable-512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" },
{ "src": "/icons/mono-512.png", "sizes": "512x512", "type": "image/png", "purpose": "monochrome" }
]
Пометка purpose со значением maskable означает, что рисунок допускает обрезку по любой форме, от круга до скруглённого квадрата. Безопасная зона такой иконки это круг радиусом сорок процентов стороны: всё важное, буквы, знак, логотип, обязано вписываться в него, а фон заливаться до краёв. Вариант monochrome подхватывает тема Android, раскрашивая монохромный силуэт в цвет системы, что особенно эффектно в тёмных темах. В мобильном Safari своя история: браузер исторически игнорирует часть манифеста, и иконку для домашнего экрана задают отдельной ссылкой:
<link rel="apple-touch-icon" href="/icons/apple-180.png">
Манифест дополнительно принимает массив screenshots, отвечающий за диалог установки: на Android вместо безликой строчки система показывает галерею экранов. Каждому снимку приписывают форм-фактор, узкий или широкий:
"screenshots": [
{ "src": "/shots/phone.png", "sizes": "1080x1920", "type": "image/png", "form_factor": "narrow" },
{ "src": "/shots/desktop.png", "sizes": "1920x1080", "type": "image/png", "form_factor": "wide" }
]
Снимки честнее делать с реального интерфейса, а не с макетов: обещание витрины должно совпадать с тем, что откроется после установки. Проверить узнаваемость иконки стоит на реальном устройстве: на светлом фоне рабочего стола и на тёмном, в сетке среди соседей и крупно в диспетчере приложений. Идеальная на белом макете иконка нередко тонет на тёмных обоях, и лучше узнать это до релиза, а не из скриншотов.
Цветовая схема background_color theme_color и стартовый экран
Два цветовых поля манифеста делают разную работу, и их путаница портит вид приложения чаще всего. Значение background_color система подставляет в стартовый экран, короткую заставку между касанием ярлыка и первой отрисовкой страницы: на Android заставка собирается автоматически из этого цвета, иконки 512 и полного имени. Поле theme_color красит элементы интерфейса уже запущенного приложения, строку состояния и заголовок окна, поэтому туда ставят фирменный акцент, а не нейтральный фон. Оба цвета обязаны быть сплошными, без прозрачности.
Для обычного просмотра в браузере цвет вкладки задаёт отдельный тег theme-color, и ему доступны медиавыражения под светлую и тёмную темы системы:
<meta name="theme-color" content="#3c2a21" media="(prefers-color-scheme: dark)">
<meta name="theme-color" content="#f5efe6" media="(prefers-color-scheme: light)">
Так вкладка в тёмной системе окрашивается тёмным акцентом, а в светлой остаётся тёплой, и резкий переход между темами не бьёт по глазам при открытии. На заставке фон берётся из манифеста, поэтому цвет там обычно берут равным фону первой экрана приложения: заставка и первый кадр сливаются, и запуск выглядит мгновенным, без белой вспышки. На настольных системах строка заголовка окна подхватывает theme_color из манифеста, а в режиме с собственным заголовком, о нём ниже, цветом управляет уже CSS. Фирменная палитра в связке тема, заставка и фон первой страницы превращает запуск из постороннего события в продолжение бренда.
Режимы отображения display standalone и тонкая настройка display_override
Поле display решает, сколько браузера останется вокруг интерфейса. Значение standalone прячет адресную строку и кнопки навигации, окно неотличимо от нативного приложения. Значение minimal-ui оставляет минимальный набор элементов, fullscreen отделяет всю хромировку и подходит играм и киоскам, а browser открывает страницу как обычную вкладку. Когда нужны особые режимы, очередь предпочтений задаёт display_override: система берёт первый поддерживаемый вариант из списка и лишь при неудаче откатывается к display:
{
"display": "standalone",
"display_override": ["window-controls-overlay", "standalone", "browser"]
}
Режим window-controls-overlay для настольных систем позволяет нарисовать фирменную строку заголовка поверх контента, оставив системные кнопки на месте и заняв свободное место своим меню. Координаты зоны задаются в CSS окружениями:
@media (display-mode: window-controls-overlay) {
.brand-bar {
position: absolute;
left: env(titlebar-area-x);
width: env(titlebar-area-width);
height: env(titlebar-area-height);
}
}
Текущий режим полезно знать в скрипте, чтобы не показывать баннер установки тем, кто уже установил приложение:
const installed = matchMedia("(display-mode: standalone)").matches;
Так одна и та же кодовая база ведёт себя по-разному во вкладке и в окне, оставаясь при этом единой страницей без веток для нативных сборок.
Ярлыки shortcuts и обработчики протоколов для быстрых действий
Ярлыки это меню быстрого доступа по долгому касанию иконки, аналог контекстного меню нативных приложений. Рекомендованный объём, до четырёх пунктов, заставляет выбрать действительно частые сценарии вместо свалки ссылок. Каждый ярлык несёт имя, короткое имя, описание для доступности, адрес и собственную иконку:
"shortcuts": [
{
"name": "Новый заказ",
"short_name": "Заказ",
"description": "Открыть форму нового заказа",
"url": "/orders/new",
"icons": [{ "src": "/icons/shortcut-96.png", "sizes": "96x96" }]
}
]
Для магазина кофейных зёрен ярлыком станет страница свежей обжарки, для сервиса рассылок окно новой кампании: попадание в реальный сценарий ценится выше креатива. Связанная возможность, обработчики протоколов, позволяет зарегистрировать собственную схему адресов:
"protocol_handlers": [
{ "protocol": "web+beans", "url": "/roast?sort=%s" }
]
После установки ссылка вида web+beans:arabica откроется в приложении, что выручает внутренние порталы и инструкции, где адреса собираются в строках текста. Обработчики срабатывают только после установки приложения: браузер без него просто не поймёт такой адрес, поэтому схемой нельзя злоупотреблять на критичных путях.
Приём файлов и запуск из ссылок в свежих настольных браузерах
Манифест описывает и то, что происходит после запуска. Поле file_handlers в свежих настольных сборках Chromium позволяет зарегистрировать приложение обработчиком файлов с конкретными расширениями:
"file_handlers": [
{
"action": "/open",
"accept": { "application/json": [".json"] },
"launch_type": "single-client"
}
]
Значение single-client открывает все выбранные файлы в одном окне, а multiple-client создаёт отдельное окно на каждый файл. Страница по адресу action принимает файлы через глобальный объект launchQueue и сразу отдаёт их в редактор: Поведение при повторных запусках задаёт launch_handler: список client_mode описывает, что делать с уже открытым окном, например перейти по новому адресу в существующем клиенте и просто вернуть ему фокус:
"launch_handler": { "client_mode": ["navigate-existing", "focus-existing"] }
if ("launchQueue" in window) {
launchQueue.setConsumer(async params => {
if (params.files.length === 0) return;
const file = await params.files[0].getFile();
const text = await file.text();
openEditor(text);
});
}
Родственное поле handle_links управляет судьбой обычных ссылок на область приложения: значение preferred просит систему открывать их в окне приложения, а не в новой вкладке браузера. Вместе эти три настройки делают поведение запущенного окна предсказуемым: файлы открываются в единственном клиенте, повторный запуск не плодит копии, внутренние переходы остаются внутри. Для фирменного опыта эта предсказуемость значит не меньше, чем цвет строки состояния.
Отладка манифеста в панели разработчика и порядок проверки установки
Ошибки манифеста молчаливы: страница работает, а установка не предлагается, и без инструментов причину не найти. Панель Application в инструментах разработчика показывает разобранный манифест, все поля с фактическими значениями, предупреждения о битых иконках и кнопку установки для теста. Аудит Lighthouse проверяет устанавливаемость списком: защищённое соединение, манифест с именем и иконками, сервис-воркер с обработчиком запросов. Типовые провалы известны: сервер отдаёт манифест с типом text/plain, стартовая страница отвечает ошибкой, область scope не покрывает start_url, иконка 512 нарисована, но не открывается.
Раз установка управляема, ей стоит заняться и на странице: перед показом собственной подсказки браузер выбрасывает событие beforeinstallprompt, которое можно перехватить и показать свою кнопку в обдуманном месте.
let installEvent = null;
addEventListener("beforeinstallprompt", event => {
event.preventDefault();
installEvent = event;
installButton.hidden = false;
});
installButton.onclick = async () => {
installEvent.prompt();
const choice = await installEvent.userChoice;
if (choice.outcome === "accepted") {
installButton.hidden = true;
}
};
Так кнопка установки появляется там, где она уместна, а не в момент случайного захода, и исчезает после согласия.
Проверку перед релизом удобно гнать коротким списком шагов:
- разобрать манифест в панели Application и убедиться в отсутствии предупреждений о полях и иконках;
- проверить отдачу файла с MIME типом application/manifest+json и доступность каждой иконки;
- установить приложение на чистый профиль и осмотреть заставку из фона, иконки и имени;
- сверить тему и строку состояния в светлой и тёмной системных темах;
- пройтись по ярлыкам, обработчику файлов и повторному запуску из ссылки.
Фирменный облик PWA собирается из полей, каждое из которых по отдельности выглядит мелочью: сплошной цвет, безопасная зона иконки, аккуратный short_name. Сложенные вместе, они дают то, за что пользовательские команды любят нативные приложения: узнаваемость с первого касания, мгновенный запуск без вспышек и предсказуемое окно. Манифест здесь играет роль паспорта, и чем честнее он заполнен, тем меньше система дорисовывает за сайт сама.