PWA 的离线功能是其核心特性之一,主要通过 Service Worker 和缓存机制实现。以下是实现离线功能的完整方案:
离线功能的核心组件
1. Service Worker
Service Worker 是离线功能的基础,它能够拦截网络请求并从缓存中返回资源。
2. Cache API
用于存储和管理缓存资源。
实现离线功能的步骤
步骤 1:注册 Service Worker
javascript// 在主线程中注册 if ('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('/sw.js') .then(registration => { console.log('SW registered:', registration); }) .catch(error => { console.log('SW registration failed:', error); }); }); }
步骤 2:预缓存关键资源
javascript// sw.js const CACHE_NAME = 'my-pwa-v1'; const ASSETS_TO_CACHE = [ '/', '/index.html', '/styles/main.css', '/scripts/app.js', '/images/logo.png', '/offline.html' ]; self.addEventListener('install', event => { event.waitUntil( caches.open(CACHE_NAME) .then(cache => { return cache.addAll(ASSETS_TO_CACHE); }) .then(() => { return self.skipWaiting(); }) ); });
步骤 3:实现缓存策略
javascriptself.addEventListener('fetch', event => { event.respondWith( caches.match(event.request) .then(response => { // 缓存命中,直接返回 if (response) { return response; } // 缓存未命中,请求网络 return fetch(event.request) .then(response => { // 检查响应是否有效 if (!response || response.status !== 200 || response.type !== 'basic') { return response; } // 克隆响应并缓存 const responseToCache = response.clone(); caches.open(CACHE_NAME) .then(cache => { cache.put(event.request, responseToCache); }); return response; }) .catch(() => { // 网络请求失败,返回离线页面 if (event.request.mode === 'navigate') { return caches.match('/offline.html'); } }); }) ); });
步骤 4:创建离线页面
html<!-- offline.html --> <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>离线</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; font-family: Arial, sans-serif; background: #f5f5f5; } .offline-container { text-align: center; padding: 40px; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); } .offline-icon { font-size: 64px; margin-bottom: 20px; } .offline-title { font-size: 24px; margin-bottom: 10px; color: #333; } .offline-message { color: #666; margin-bottom: 20px; } .retry-button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } </style> </head> <body> <div class="offline-container"> <div class="offline-icon">📡</div> <h1 class="offline-title">您当前处于离线状态</h1> <p class="offline-message">请检查您的网络连接后重试</p> <button class="retry-button" onclick="window.location.reload()">重新加载</button> </div> <script> // 监听网络状态变化 window.addEventListener('online', () => { window.location.reload(); }); </script> </body> </html>
步骤 5:监听网络状态
javascript// 在主线程中监听网络状态 window.addEventListener('online', () => { console.log('网络已连接'); // 可以在这里执行一些操作,比如同步数据 }); window.addEventListener('offline', () => { console.log('网络已断开'); // 显示离线提示 showOfflineNotification(); }); function showOfflineNotification() { const notification = document.createElement('div'); notification.textContent = '您当前处于离线状态'; notification.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: #ff9800; color: white; padding: 10px 20px; border-radius: 4px; z-index: 9999; `; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); }
高级离线功能
1. Background Sync(后台同步)
javascript// 注册同步事件 self.addEventListener('sync', event => { if (event.tag === 'sync-data') { event.waitUntil(syncData()); } }); async function syncData() { // 获取离线时存储的数据 const offlineData = await getOfflineData(); // 同步到服务器 for (const data of offlineData) { try { await fetch('/api/sync', { method: 'POST', body: JSON.stringify(data) }); // 同步成功,删除本地数据 await removeOfflineData(data.id); } catch (error) { console.error('同步失败:', error); } } } // 在主线程中请求同步 navigator.serviceWorker.ready.then(registration => { registration.sync.register('sync-data'); });
2. IndexedDB 存储离线数据
javascript// 打开 IndexedDB const dbPromise = idb.open('my-pwa-db', 1, upgradeDB => { upgradeDB.createObjectStore('offline-data', { keyPath: 'id' }); }); // 保存离线数据 async function saveOfflineData(data) { const db = await dbPromise; await db.add('offline-data', data); } // 获取离线数据 async function getOfflineData() { const db = await dbPromise; return await db.getAll('offline-data'); } // 删除离线数据 async function removeOfflineData(id) { const db = await dbPromise; await db.delete('offline-data', id); }
离线功能的最佳实践
- 预缓存关键资源:确保核心功能离线可用
- 提供友好的离线页面:告知用户当前状态并提供解决方案
- 监听网络状态:及时响应网络变化
- 实现数据同步:离线时存储数据,在线时同步
- 设置合理的缓存策略:平衡性能和新鲜度
- 测试离线场景:使用 Chrome DevTools 的 Offline 模式测试
- 提供网络状态指示器:让用户了解当前网络状态
测试离线功能
使用 Chrome DevTools 测试:
- 打开 DevTools(F12)
- 切换到 Network 标签
- 勾选 "Offline" 模式
- 刷新页面,测试离线功能
- 取消勾选 "Offline",测试恢复功能