動態

詳情 返回 返回

PWA離線優先策略:提升用户體驗的關鍵步驟 - 動態 詳情

Progressive Web Apps (PWA) 的離線優先策略是通過Service Worker和Cache API實現的,它允許在沒有網絡連接時仍然可以訪問網站的部分或全部內容。

1. 創建Service Worker註冊文件(service-worker.js):

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('my-cache-v1').then((cache) => {
      return cache.addAll([
        '/index.html',
        '/style.css',
        '/script.js',
        // 添加其他需要預緩存的文件路徑
      ]);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      if (response) {
        return response;
      }
      return fetch(event.request).then((networkResponse) => {
        caches.open('my-cache-v1').then((cache) => {
          cache.put(event.request.url, networkResponse.clone());
        });
        return networkResponse;
      }).catch(() => {
        // 如果所有嘗試都失敗,可以返回一個備用響應,比如錯誤頁面
        return caches.match('/offline.html');
      });
    })
  );
});

2. 註冊Service Worker:

在你的主應用中註冊Service Worker:

if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker
      .register('/service-worker.js')
      .then((registration) => {
        console.log('Service Worker registered:', registration);
      })
      .catch((error) => {
        console.error('Service Worker registration failed:', error);
      });
  });
}

3. 更新策略:

當有新版本的應用時,需要更新Service Worker和緩存內容。可以在Service Worker中監聽activate事件:

self.addEventListener('activate', (event) => {
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.filter((cacheName) => cacheName !== 'my-cache-v1').map((cacheName) => caches.delete(cacheName))
      );
    })
  );
});

4. 更新Service Worker:

更新Service Worker時,需要改變Service Worker文件名(如增加版本號),這樣瀏覽器會認為這是新的SW並觸發安裝過程。

5. 更新Service Worker生命週期管理:

確保在Service Worker更新時,舊版本的Service Worker不會影響用户體驗。通常,你可能希望舊版本Service Worker完成所有請求後再關閉:

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'SKIP_WAITING') {
    self.skipWaiting();
  }
});

6. 配置manifest文件:

創建一個manifest.json文件,定義應用的元數據和離線圖標:

{
  "short_name": "My App",
  "name": "My Awesome Progressive Web App",
  "icons": [
    {
      "src": "icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ],
  "start_url": "/index.html",
  "display": "standalone",
  "background_color": "#ffffff",
  "theme_color": "#000000"
}

在HTML中引用manifest文件:

<link rel="manifest" href="/manifest.json">

7. 離線通知和重新加載提示

當用户離線後重新上線時,可以通過Service Worker發送通知提醒用户重新加載頁面以獲取更新內容:

self.addEventListener('online', (event) => {
  clients.matchAll({ type: 'window' }).then((clients) => {
    clients.forEach((client) => {
      client.postMessage({ type: 'RELOAD' });
    });
  });
});

self.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'RELOAD') {
    clients.matchAll({ type: 'window' }).then((clients) => {
      clients.forEach((client) => {
        if (client.url === self.registration.scope && 'focus' in client) {
          client.focus();
          client.reload();
        }
      });
    });
  }
});

在主應用中監聽消息:

navigator.serviceWorker.addEventListener('message', (event) => {
  if (event.data && event.data.type === 'RELOAD') {
    alert('網絡已恢復,刷新頁面獲取最新內容。');
    location.reload();
  }
});

8. 離線提示和體驗

當用户離線時,提供友好的離線頁面或提示:

<!DOCTYPE html>
<html lang="en">
<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;
      background-color: #f2f2f2;
    }

    h1 {
      font-size: 2rem;
      color: #333;
    }
  </style>
</head>
<body>
  <h1>您已離線,稍後再試。</h1>
</body>
</html>

然後在Service Workerfetch事件中處理:

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      if (response) {
        return response;
      }
      // 檢查網絡請求失敗的情況
      return fetch(event.request).catch(() => {
        // 返回離線頁面
        return caches.match('/offline.html');
      });
    })
  );
});

9. 更新緩存策略

有時你可能希望緩存特定版本的資源,而不是始終使用最新的。這可以通過在Service Worker中添加版本控制來實現:

const CACHE_NAME = 'my-cache-v2';
const urlsToCache = [
  // ...
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open(CACHE_NAME).then((cache) => {
      return cache.addAll(urlsToCache);
    })
  );
});

self.addEventListener('fetch', (event) => {
  event.respondWith(
    caches.match(event.request).then((response) => {
      if (response) {
        return response;
      }
      return fetch(event.request).then((networkResponse) => {
        caches.open(CACHE_NAME).then((cache) => {
          cache.put(event.request.url, networkResponse.clone());
        });
        return networkResponse;
      });
    })
  );
});

10. 使用App Shell架構

App Shell模型是一種常見的PWA設計模式,它提供一個基本的用户界面框架,即使在離線狀態下也能加載。App Shell通常包括導航、頭部、側邊欄等非動態內容,這樣即使在離線時,用户也能看到應用的基本結構。

首先,創建一個App Shell HTML文件(如app-shell.html),包含基本的佈局和樣式。然後,在Service Worker中預緩存App Shell:

const appShellUrls = [
  '/app-shell.html',
  '/app-style.css',
  // 其他App Shell相關的資源
];

self.addEventListener('install', (event) => {
  event.waitUntil(
    caches.open('app-shell-cache').then((cache) => {
      return cache.addAll(appShellUrls);
    })
  );
});

fetch事件中,優先從緩存中獲取App Shell資源:

self.addEventListener('fetch', (event) => {
  if (event.request.mode === 'navigate') {
    event.respondWith(caches.match('/app-shell.html'));
  } else {
    event.respondWith(
      caches.match(event.request).then((response) => {
        if (response) {
          return response;
        }
        return fetch(event.request);
      })
    );
  }
});

11. 使用Service Worker攔截網絡請求

Service Worker還可以用於攔截特定類型的網絡請求,例如API調用。這使得你可以在離線時返回默認值或存儲的響應,以提供一致的用户體驗:

self.addEventListener('fetch', (event) => {
  if (event.request.url.startsWith('https://api.example.com')) {
    event.respondWith(
      caches.match(event.request).then((response) => {
        if (response) {
          return response;
        }
        return fetch(event.request).then((networkResponse) => {
          caches.open('api-cache').then((cache) => {
            cache.put(event.request.url, networkResponse.clone());
          });
          return networkResponse;
        });
      })
    );
  } else {
    // 處理其他非API請求
  }
});

12. 集成WebSocket支持

如果你的應用使用WebSocket進行實時通信,可以使用workbox-websocket庫在Service Worker中處理WebSocket連接,確保在離線時能夠接收和發送消息:

importScripts('https://unpkg.com/workbox-sw@latest/runtime-caching/workbox-sw.prod.v2.js');
importScripts('https://unpkg.com/workbox-websocket@latest/workbox-websocket.prod.v2.js');

workbox.webSocket.register('wss://your-websocket-endpoint.com', {
  onConnect: (client) => {
    console.log('WebSocket connected:', client);
  },
  onClose: (client) => {
    console.log('WebSocket disconnected:', client);
  },
});

13. 測試和監控

確保在不同網絡條件下測試你的PWA,包括2G、3G和離線狀態。可以使用Chrome開發者工具的模擬網絡條件功能。同時,使用Lighthouse等工具定期評估PWA的性能和離線體驗。

14. 總結

通過這些策略,可以創建一個高度可用且用户體驗優秀的PWA,即使在離線或弱網絡環境下也能正常工作。PWA的目標是提供接近原生應用的體驗,因此持續優化和測試是關鍵。

user avatar toopoo 頭像 huajianketang 頭像 banana_god 頭像 Dream-new 頭像 xiaoxxuejishu 頭像 yqyx36 頭像 yuzhihui 頭像 Z-HarOld 頭像 kitty-38 頭像 Poetwithapistol 頭像 gaozhipeng 頭像 yuxl01 頭像
點贊 81 用戶, 點贊了這篇動態!
點贊

Add a new 評論

Some HTML is okay.