Перейти к содержимому

PWA — технология (для Прогрессивных веб-приложение)

ранее писал тут — https://1.cbm.ua/?p=4454

Инструкция как создать PWA приложение.

на примере простого PWA приложение с базовым функционалом. Это будет список задач с возможностью работы офлайн.

Создать фал manifest.json — это манифест.

{
  "name": "Название вашего приложения",
  "short_name": "Короткое название",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000",
  "icons": [
    {
      "src": "//fjngqp1mvftjzxfzrdiggafze9wxueam.cdn-freehost.com.ua/icon.png",
      "sizes": "192x192",
      "type": "image/png"
    }
  ]
}

2. Добавить ссылку на манифест в HTML-код вашей страницы.

<link rel="manifest" href="//fjngqp1mvftjzxfzrdiggafze9wxueam.cdn-freehost.com.ua/manifest.json">
<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PWA Todo List</title>
    <link rel="manifest" href="data:application/json;base64,eyJuYW1lIjoiUFdBIFRvZG8gTGlzdCIsInNob3J0X25hbWUiOiJUb2RvUFdBIiwic3RhcnRfdXJsIjoiLyIsImRpc3BsYXkiOiJzdGFuZGFsb25lIiwiYmFja2dyb3VuZF9jb2xvciI6IiNmZmZmZmYiLCJ0aGVtZV9jb2xvciI6IiM0Mjg1ZjQiLCJpY29ucyI6W3sic3JjIjoiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCM2FXUjBhRDBpTVRrd0lpQm9aV2xuYUhROUlqRTVNQ0lpSUhabGNuTnBiMjQ5SWpFdU1TSWdlRzFzYm5NOUltaDBkSEE2THk5M2QzY3Vkek11YjNKbkx6SXdNREF2YzNabklqNGdJRHhqYVhKamJHVWdZM2c5SWprMUlpQmplVDBpT1RVaUlISTlJamMxSWlCbWFXeHNQU0lqTkRJNE5XWTBJaTgrSUR4MFpYaDBJSGc5SWprMUlpQjVQU0k1TlNJZ2RHVjRkQzFoYm1Ob2IzSTlJbTFwWkdSc1pTSWdabTl1ZEMxbVlXMXBiSGs5SW1GdWFXRnNJaUJtYVd4c1BTSWpabVptSWo1VVQwUlBQQzkwWlhoMFBpQThMM04yWno0PSIsInNpemVzIjoiMTkweDIxMSIsInR5cGUiOiJpbWFnZS9zdmcreG1sIn1dfQ==">
    <meta name="theme-color" content="#4285f4">
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }

        .container {
            max-width: 600px;
            margin: 0 auto;
            background: white;
            border-radius: 12px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.1);
            overflow: hidden;
        }

        .header {
            background: #4285f4;
            color: white;
            padding: 20px;
            text-align: center;
        }

        .header h1 {
            font-size: 24px;
            margin-bottom: 5px;
        }

        .status {
            font-size: 14px;
            opacity: 0.9;
        }

        .add-task {
            padding: 20px;
            border-bottom: 1px solid #eee;
        }

        .task-input {
            width: 100%;
            padding: 12px;
            border: 2px solid #e0e0e0;
            border-radius: 6px;
            font-size: 16px;
            margin-bottom: 10px;
        }

        .task-input:focus {
            outline: none;
            border-color: #4285f4;
        }

        .add-btn {
            background: #4285f4;
            color: white;
            border: none;
            padding: 12px 24px;
            border-radius: 6px;
            cursor: pointer;
            font-size: 16px;
            transition: background 0.3s;
        }

        .add-btn:hover {
            background: #3367d6;
        }

        .tasks-list {
            min-height: 200px;
            padding: 20px;
        }

        .task-item {
            display: flex;
            align-items: center;
            padding: 15px;
            border-bottom: 1px solid #f0f0f0;
            transition: background 0.3s;
        }

        .task-item:hover {
            background: #f8f9fa;
        }

        .task-item.completed {
            opacity: 0.6;
            text-decoration: line-through;
        }

        .task-checkbox {
            margin-right: 15px;
            cursor: pointer;
        }

        .task-text {
            flex: 1;
            font-size: 16px;
        }

        .delete-btn {
            background: #ff4444;
            color: white;
            border: none;
            padding: 8px 12px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 12px;
        }

        .delete-btn:hover {
            background: #cc0000;
        }

        .empty-state {
            text-align: center;
            color: #666;
            padding: 40px 20px;
        }

        .install-prompt {
            background: #f8f9fa;
            border: 1px solid #e0e0e0;
            border-radius: 6px;
            padding: 15px;
            margin: 20px;
            text-align: center;
            display: none;
        }

        .install-btn {
            background: #34a853;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 4px;
            cursor: pointer;
            margin-top: 10px;
        }

        @media (max-width: 480px) {
            .container {
                margin: 0;
                border-radius: 0;
                min-height: 100vh;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>📝 PWA Todo List</h1>
            <div class="status" id="status">Онлайн</div>
        </div>

        <div class="install-prompt" id="installPrompt">
            <div>Установите приложение для лучшего опыта!</div>
            <button class="install-btn" id="installBtn">Установить</button>
        </div>

        <div class="add-task">
            <input type="text" class="task-input" id="taskInput" placeholder="Добавить новую задачу...">
            <button class="add-btn" id="addBtn">Добавить</button>
        </div>

        <div class="tasks-list" id="tasksList">
            <div class="empty-state">
                Нет задач. Добавьте первую задачу!
            </div>
        </div>
    </div>

    <script>
        // Основная логика приложения
        class TodoApp {
            constructor() {
                this.tasks = this.loadTasks();
                this.initEventListeners();
                this.renderTasks();
                this.updateStatus();
            }

            initEventListeners() {
                document.getElementById('addBtn').addEventListener('click', () => this.addTask());
                document.getElementById('taskInput').addEventListener('keypress', (e) => {
                    if (e.key === 'Enter') this.addTask();
                });

                // Обновление статуса подключения
                window.addEventListener('online', () => this.updateStatus());
                window.addEventListener('offline', () => this.updateStatus());
            }

            addTask() {
                const input = document.getElementById('taskInput');
                const text = input.value.trim();
                
                if (text) {
                    const task = {
                        id: Date.now(),
                        text: text,
                        completed: false,
                        createdAt: new Date().toISOString()
                    };
                    
                    this.tasks.push(task);
                    this.saveTasks();
                    this.renderTasks();
                    input.value = '';
                }
            }

            toggleTask(id) {
                const task = this.tasks.find(t => t.id === id);
                if (task) {
                    task.completed = !task.completed;
                    this.saveTasks();
                    this.renderTasks();
                }
            }

            deleteTask(id) {
                this.tasks = this.tasks.filter(t => t.id !== id);
                this.saveTasks();
                this.renderTasks();
            }

            renderTasks() {
                const container = document.getElementById('tasksList');
                
                if (this.tasks.length === 0) {
                    container.innerHTML = '<div class="empty-state">Нет задач. Добавьте первую задачу!</div>';
                    return;
                }

                container.innerHTML = this.tasks.map(task => `
                    <div class="task-item ${task.completed ? 'completed' : ''}">
                        <input type="checkbox" class="task-checkbox" 
                               ${task.completed ? 'checked' : ''} 
                               onchange="app.toggleTask(${task.id})">
                        <div class="task-text">${task.text}</div>
                        <button class="delete-btn" onclick="app.deleteTask(${task.id})">Удалить</button>
                    </div>
                `).join('');
            }

            saveTasks() {
                localStorage.setItem('pwa-tasks', JSON.stringify(this.tasks));
            }

            loadTasks() {
                const saved = localStorage.getItem('pwa-tasks');
                return saved ? JSON.parse(saved) : [];
            }

            updateStatus() {
                const status = document.getElementById('status');
                status.textContent = navigator.onLine ? 'Онлайн' : 'Офлайн';
                status.style.color = navigator.onLine ? '#4CAF50' : '#FF9800';
            }
        }

        // Service Worker регистрация
        if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('data:application/javascript;base64,Y29uc3QgQ0FDSEVfTkFNRSA9ICd0b2RvLXB3YS12MSc7CmNvbnN0IHVybHNUb0NhY2hlID0gWycvJ107CgpzZWxmLmFkZEV2ZW50TGlzdGVuZXIoJ2luc3RhbGwnLCBldmVudCA9PiB7CiAgZXZlbnQud2FpdFVudGlsKAogICAgY2FjaGVzLm9wZW4oQ0FDSEVfTkFNRSkKICAgICAgLnRoZW4oY2FjaGUgPT4gY2FjaGUuYWRkQWxsKHVybHNUb0NhY2hlKSkKICApOwp9KTsKCnNlbGYuYWRkRXZlbnRMaXN0ZW5lcignZmV0Y2gnLCBldmVudCA9PiB7CiAgZXZlbnQucmVzcG9uZFdpdGgoCiAgICBjYWNoZXMubWF0Y2goZXZlbnQucmVxdWVzdCkKICAgICAgLnRoZW4ocmVzcG9uc2UgPT4gcmVzcG9uc2UgfHwgZmV0Y2goZXZlbnQucmVxdWVzdCkpCiAgKTsKfSk7');
        }

        // PWA установка
        let deferredPrompt;
        const installPrompt = document.getElementById('installPrompt');
        const installBtn = document.getElementById('installBtn');

        window.addEventListener('beforeinstallprompt', (e) => {
            e.preventDefault();
            deferredPrompt = e;
            installPrompt.style.display = 'block';
        });

        installBtn.addEventListener('click', async () => {
            if (deferredPrompt) {
                deferredPrompt.prompt();
                const { outcome } = await deferredPrompt.userChoice;
                deferredPrompt = null;
                installPrompt.style.display = 'none';
            }
        });

        // Инициализация приложения
        const app = new TodoApp();
    </script>
</body>
</html>

Создал простое PWA приложение — список задач с следующими возможностями:

Основные функции:

  • Добавление задач — можно добавлять новые задачи
  • Отметка выполнения — отмечать задачи как выполненные
  • Удаление задач — удалять ненужные задачи
  • Сохранение данных — задачи сохраняются в localStorage

PWA возможности:

  • Офлайн работа — приложение работает без интернета
  • Установка — можно установить как обычное приложение
  • Статус подключения — показывает онлайн/офлайн статус
  • Адаптивный дизайн — хорошо выглядит на мобильных устройствах

Технические особенности:

  • Service Worker для кэширования
  • Web App Manifest для установки
  • Современный UI с градиентами и анимациями
  • Работа с LocalStorage для хранения данных

Приложение готово к использованию! Вы можете открыть его в браузере и установить как PWA для получения нативного опыта использования.