Engineering

PWA 把網頁遊戲變成 可安裝 app

2026 年 5 月 5 日約 9 分鐘閱讀作者:Hao0321 Studio

瀏覽器遊戲的最大限制:使用者每次都要打開瀏覽器、輸入網址。PWA(Progressive Web App)解決這個問題 — 加 2 個檔案就能讓網站變成可加到桌面的 app,有 icon、可離線、跟原生 app 體驗極接近。這篇講工作室怎麼把遊戲大廳變成 PWA。

PWA 三件套

  1. manifest.json:app metadata(名稱、icon、主題色)
  2. service-worker.js:背景 script(離線快取、推播)
  3. HTTPS 部署:所有 PWA 必要條件

manifest.json

{
  "name": "Hao0321 遊戲大廳",
  "short_name": "Hao0321",
  "description": "14 款原創瀏覽器遊戲,無需註冊。",
  "start_url": "/",
  "display": "standalone",
  "orientation": "portrait",
  "background_color": "#F4F4F7",
  "theme_color": "#4A7BF5",
  "icons": [
    {
      "src": "/icons/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    },
    {
      "src": "/icons/icon-512.png",
      "sizes": "512x512",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

關鍵欄位:

HTML 引用

<link rel="manifest" href="/manifest.json">
<meta name="theme-color" content="#4A7BF5">
<link rel="apple-touch-icon" href="/icons/icon-192.png">

service-worker.js — 快取核心檔案

const CACHE = 'hao0321-v1';
const ASSETS = [
  '/',
  '/play.html',
  '/dodge-master-v5.html',
  '/styles.css',
  '/app.js',
  '/icons/icon-192.png',
];

self.addEventListener('install', (e) => {
  e.waitUntil(
    caches.open(CACHE).then((cache) => cache.addAll(ASSETS))
  );
});

self.addEventListener('activate', (e) => {
  e.waitUntil(
    caches.keys().then((keys) =>
      Promise.all(keys.filter(k => k !== CACHE).map(k => caches.delete(k)))
    )
  );
});

self.addEventListener('fetch', (e) => {
  e.respondWith(
    caches.match(e.request).then((cached) =>
      cached || fetch(e.request).then((res) => {
        // 同時更新 cache
        return caches.open(CACHE).then((cache) => {
          if (res.ok) cache.put(e.request, res.clone());
          return res;
        });
      })
    )
  );
});

註冊 service worker

// 主頁面 JS
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/service-worker.js')
      .then((reg) => console.log('SW registered:', reg.scope))
      .catch((err) => console.error('SW error:', err));
  });
}

離線體驗設計

PWA 的「離線」不是只 cache 檔案。要設計:

window.addEventListener('offline', () => {
  showBanner('目前離線,分數會在恢復連線時上傳');
});
window.addEventListener('online', () => {
  hideBanner();
  flushScoreQueue();
});

「Add to Home Screen」提示

使用者用 Chrome 瀏覽多次後,瀏覽器會自動跳「加到主畫面」。也可以主動觸發:

let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
  e.preventDefault();
  deferredPrompt = e;
  document.getElementById('installBtn').style.display = 'block';
});

document.getElementById('installBtn').addEventListener('click', () => {
  deferredPrompt.prompt();
  deferredPrompt.userChoice.then((choice) => {
    console.log(choice.outcome);  // 'accepted' or 'dismissed'
    deferredPrompt = null;
  });
});

iOS 的特殊處理

iOS Safari 對 PWA 支援不完整:

更新策略

PWA 最頭痛的是「使用者裝舊版本」。解法:

// service-worker.js 加版本號
const CACHE = 'hao0321-v3';  // 改版時更新

新版本 install 後通知使用者:

navigator.serviceWorker.addEventListener('controllerchange', () => {
  if (confirm('新版本可用,要重新整理嗎?')) {
    window.location.reload();
  }
});

Lighthouse PWA 分數

Lighthouse 跑 PWA tab 會給分。要拿滿分需要:

結語

PWA 是被低估的「免費 app store 替代」。對獨立工作室是巨大的賦能 — 不用 App Store / Play Store 審核、不用 30% 抽成、不用懂 Swift / Kotlin。本工作室遊戲大廳變成 PWA 後,回流率(重複玩家)提升 25%。