Установка сайтов в виде приложений PWA

index.html ```html <!DOCTYPE html> <html lang="en"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>My App PWA</title> <link rel="stylesheet" href="style.css"> <script src="script.js" defer></script> <meta property="og:image" content="images/android-chrome-256x256.png"> <meta property="og:image:width" content="256"> <meta property="og:image:height" content="256"> <meta property="og:title" content="PWA APP"> <meta property="og:description" content="Testing PWA application"> <link rel="manifest" href="manifest.json"> <link rel="apple-touch-icon" sizes="180x180" href="images/apple-touch-icon.png"> <link rel="icon" type="image/png" sizes="32x32" href="images/favicon-32x32.png"> <link rel="icon" type="image/png" sizes="16x16" href="images/favicon-16x16.png"> <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"> <meta name="msapplication-TileColor" content="#da532c"> <meta name="theme-color" content="#ffffff"> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes"> <title>PWA APP</title> <base href="https://natalia-orlova.github.io/btnUp/"> </head> <body> <button class="install-btn">Установить приложение</button> <button class="instruction-btn">Как установить приложение?</button> <div id="myModal" class="modal"> <div class="modal-content"> <span class="close">&times;</span> </div> </div> <div class="content"> Lorem 5 </div> <script type="module"> import Bowser from "/btnUp/bowser/bowser.js"; const browser = Bowser.getParser(window.navigator.userAgent); const browserName = browser.getBrowserName(); const installButton = document.querySelector('.install-btn'); const instructionButton = document.querySelector('.instruction-btn'); let installEvent; const modal = document.getElementById('myModal'); const modalContent = modal.querySelector('.modal-content'); const closeButton = document.getElementsByClassName('close')[0]; /** * регистрация service-worker */ if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/btnUp/service-worker.js').then(registration => { console.log('Service Worker registered with scope:', registration.scope); }).catch(error => { console.log('Service Worker registration failed:', error); }); }); } /** * Функции для модального окна */ function openModal() { modal.style.display = 'block'; } function closeModal() { modal.style.display = 'none'; instructionButton.style.display = 'none'; } closeButton.onclick = function () { closeModal(); }; window.onclick = function (event) { if (event.target === modal) { closeModal(); } }; /** * проверка, с мобильного устройства или нет зашел пользователь */ function isMobileDevice() { return window.innerWidth <= 768; } if (isMobileDevice()) { /*Работает на Chrome, Microsoft Edge (версия 76 и выше) */ window.addEventListener('beforeinstallprompt', (e) => { // Показываем кнопку установки только если условия для установки PWA выполнены installButton.style.display = 'block'; installEvent = e; }); installButton.addEventListener('click', () => { // Запускаем установку приложения if (installEvent) { installEvent.prompt().then((result) => { if (result.outcome === 'accepted') { // Пользователь согласился на установку, скрываем кнопку installButton.style.display = 'none'; } else if (result.outcome === 'dismissed') { // Пользователь отменил установку, оставляем кнопку активной // ничего не делаем, кнопка остается активной } }); } }) if (browserName === 'Safari' || browserName === 'Firefox' || browserName === 'Opera') { installButton.style.display = 'none'; instructionButton.style.display = 'block'; instructionButton.addEventListener('click', () => { // Показываем инструкцию по установке openModal(); }) switch (browserName) { case 'Safari': modalContent.insertAdjacentHTML('beforeend', `<p>Пожалуйста, следуйте этим шагам для установки приложения на вашем устройстве через Safari:</p> <ol> <li>Нажмите на кнопку «Поделиться» внизу экрана.</li> <li>Выберите «Добавить на экран Домой» из списка опций.</li> <li>Нажмите «Добавить» в правом верхнем углу.</li> </ol>`); break; case 'Firefox': modalContent.insertAdjacentHTML('beforeend', `<p>Пожалуйста, следуйте этим шагам для установки приложения на вашем устройстве через Mozilla Firefox:</p> <ol> <li>Нажмите на иконку « ⋮ » в правом верхнем углу экрана.</li> <li>Выберите «На домашний экран»</li> </ol>`); break; case 'Opera': modalContent.insertAdjacentHTML('beforeend', `<p>Пожалуйста, следуйте этим шагам для установки приложения на вашем устройстве через Opera:</p> <ol> <li>Нажмите на иконку « ⋮ » в правом верхнем углу экрана.</li> <li>Выберите «Добавить в...» из списка опций.</li> <li>Выберите «Главный экран»</li> </ol>`); break; default: break; } } window.addEventListener('appinstalled', () => { // Скрыть кнопки после установки PWA installButton.style.display = 'none'; instructionButton.style.display = 'none'; }); const standaloneMediaQuery = matchMedia('(display-mode: standalone)'); if (standaloneMediaQuery.matches) { // Скрыть кнопку, если приложение запущено в standalone-режиме installButton.style.display = 'none'; instructionButton.style.display = 'none'; } } </script> </body> </html> ``` manifest.json ```json { "name": "My App PWA", "short_name": "My App PWA", "description": "Test PWA application", "icons": [ { "src": "images/android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "images/android-chrome-256x256.png", "sizes": "256x256", "type": "image/png" } ], "start_url": "https://natalia-orlova.github.io/btnUp/", "display": "standalone", "background_color": "#fff", "theme_color": "#d3b971", "firefox_add_to_homescreen": true } ``` script.js ```js "use strict"; const btnUp = document.createElement('button'); btnUp.classList.add('btn-up'); btnUp.classList.add('btn-up__hide'); const btnUpStyle = document.createElement('style'); btnUpStyle.innerHTML = ` .btn-up { position: fixed; left: 100px; bottom: 40px; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 50%; background-color: #fff; box-shadow: 1px 1px 5px rgba(0,0,0,.1); opacity: 100%; transition: all 0.5s; border: none; } .btn-up:hover { transform: scale(1.2); } .btn-up::before { content: ""; width: 40px; height: 40px; background-image: url(https://armango.com/app/images/icons/pagination_arrow.png); background-repeat: no-repeat; background-size: 50% 30%; background-position: center; transform: rotate(90deg); } .btn-up__hide { opacity: 0; transition: 0.5s; } @media (max-width:900px) { .btn-up { left: 20px; bottom: 20px; } } `; document.head.appendChild(btnUpStyle); document.body.appendChild(btnUp); window.addEventListener('scroll', () => { const scrollY = window.scrollY || document.documentElement.scrollTop; if (scrollY > (document.documentElement.clientHeight)) { btnUp.classList.remove('btn-up__hide'); } else { btnUp.classList.add('btn-up__hide'); } }) btnUp.addEventListener('click', () => { window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) }) ``` service-worker.js ```js const CACHE_NAME = "my-cache"; const urlsToCache = [ "https://natalia-orlova.github.io/btnUp/", // "https://natalia-orlova.github.io/btnUp/index.html", // "https://natalia-orlova.github.io/btnUp/style.css", // "https://natalia-orlova.github.io/btnUp/script.js", "https://natalia-orlova.github.io/btnUp/images/icon-192x192.png", ]; // self.addEventListener("install", (event) => { // event.waitUntil( // caches.open("my-cache").then((cache) => { // console.log("Opened cache"); // return cache.addAll([ // '/', // '/style.css', // '/script.js', // // Другие URL ресурсов // ]); // }) // ); // }); self.addEventListener('install', function(event) { event.waitUntil( caches.open('my-cache-name').then(function(cache) { return Promise.all([ '/btnUp/', '/btnUp/style.css', '/btnUp/script.js', // Другие URL ресурсов ].map(function(url) { return fetch(url).then(function(response) { if (response.ok) { console.log('ok'); return cache.add(url); } else { console.error('Failed to fetch resource:', url); return Promise.reject(new Error('Request failed')); } }); })); }) ); }); self.addEventListener("fetch", (event) => { event.respondWith( caches.match(event.request).then((response) => { if (response) { return response; } return fetch(event.request); }) ); }); self.addEventListener("activate", (event) => { event.waitUntil( caches.keys().then((cacheNames) => { return Promise.all( cacheNames.map(name => { if (name !== "my-cache") { return caches.delete(name); } }) ); }) ); }); self.addEventListener("appinstalled", () => { // Скрыть кнопку после установки приложения document.querySelector(".install-btn").style.display = "none"; }); ``` # PWA Application 1. Создан файл manifest.json, который содержит описание приложения (имя, описание, иконки и т.д.) В тег <head> добавлено: `<link rel="manifest" href="manifest.json">` 2. Создан файл service-worker.js, который обрабатывает запросы к приложению и кэширует ресурсы. В скрипте регистрируем service worker. 3. [Здесь генератор иконок](https://realfavicongenerator.net/), с помощью которого сделала иконки, после генерации скачиваем архив с иконками и копируем код, который нужно вставить в тег `<head>` 4. Добавлена логика для установки PWA. Для браузеров, которые позволяют веб-сайтам предлагать пользователю установку PWA, добавляем на страницу кнопку "Установить приложение" (Chrome, Edge, Yandex). При клике на кнопку отображается окно подтверждения установки приложения. Эта кнопка скрывается после установки приложения, либо остается видимой, если пользователь нажал "отмена" в диалоговом окне. Лучше всего это работает в браузере Chrome, приложение устанавливается именно как приложение, а не как страница браузера с иконкой самого браузера, из которого эту страницу сохранили на главный экран. Кнопка "Установить" не появляется на странице, пока приложение установлено на устройстве пользователя. В то время, как другие браузеры дают возможность добавлять страницу на домашний экран множество раз. Но если приложение уже установлено на Android через Chrome, то в Yandex и Edge не будет появляться кнопка установки. Firefox также будет предлагать открыть страницу в этом приложении при попытке открыть ярлык на домашнем экране, установленный с помощью firefox. 5. - Браузер Safari не поддерживает событие beforeinstallprompt - Firefox и Safari поддерживают PWA только на Android и iOS, а не на настольных компьютерах. Для нескольких браузеров (Safari, Firefox, Opera) добавлена кнопка "Как установить приложение?", при клике на которую в модальном окне открывается инструкция для того браузера из списка, через который пользователь зашел на сайт. При закрытии окна с инструкцией, кнопка также скрывается, но при обновлении страницы кнопка будет отображаться снова. Для того, чтобы выяснить, с какого браузера зашел пользователь, установлена библиотека ["Bowser"](https://github.com/bowser-js/bowser/tree/master/src). ## Примеры установки приложения с помощью разных браузеров ### Установка приложения через Chrome <img src="screen/pageChrome.jpg" width="30%" alt="страница в Chrome"> <img src="screen/downloadChrome.jpg" width="30%" alt="установка приложения"> <img src="screen/pwaApp.jpg" width="30%" alt="приложение установлено"> <img src="screen/pwaWindow.jpg" width="30%" alt="страница в приложении"> ### Установка приложения через Firefox <img src="screen/pageFirefox.jpg" width="30%" alt="страница в Firefox"> <img src="screen/instrFirefox.jpg" width="30%" alt="инструкция в Firefox"> <img src="screen/downloadFirefox.jpg" width="30%" alt="установка в Firefox"> <img src="screen/pwaFirefox.jpg" width="30%" alt="страница в приложении"> *В Opera установка примерно похожа на Firefox, отличается инструкция* <img src="screen/instrOpera.jpg" width="30%" alt="инструкция в Opera"> ### Ярлыки установленного pwa через разные браузеры <img src="screen/labels.jpg" alt="ярлыки pwa">