Skip to content
Darren

PWA 與行動裝置優化完整指南:從 Manifest 到漢堡選單

學習如何將網站轉變為 PWA,包含 Web App Manifest 設定、theme-color 配置、響應式漢堡選單實作,以及行動裝置最佳實踐

#pwa #mobile #responsive #manifest #hamburger-menu

現代網站必須在行動裝置上提供卓越體驗。本文將深入探討 PWA(漸進式網頁應用程式)的實作,以及響應式設計的最佳實踐。

目錄

  1. 什麼是 PWA
  2. Web App Manifest 設定
  3. Theme Color 主題色彩
  4. 響應式漢堡選單實作
  5. 行動版 Header 優化
  6. 常見問題與解決方案

什麼是 PWA

PWA 的核心特性

特性說明
可安裝可以加到手機主畫面,像 App 一樣啟動
離線可用使用 Service Worker 快取資源
響應式適應各種螢幕尺寸
安全必須透過 HTTPS 提供服務

為什麼要做 PWA

  1. 更好的使用者體驗:全螢幕模式,沒有瀏覽器 UI
  2. 更高的參與度:主畫面圖示增加回訪率
  3. SEO 加分:Google 偏愛 PWA 網站

Web App Manifest 設定

什麼是 manifest.json

manifest.json 是一個 JSON 檔案,告訴瀏覽器如何將你的網站「安裝」為應用程式。

基本範例

建立 public/manifest.json

{
  "name": "Personal HQ",
  "short_name": "HQ",
  "description": "Personal digital headquarters - portfolio, blog, and tools",
  "start_url": "/",
  "display": "standalone",
  "background_color": "#0a0a0f",
  "theme_color": "#00ff88",
  "icons": [
    {
      "src": "/favicon.svg",
      "sizes": "any",
      "type": "image/svg+xml"
    },
    {
      "src": "/icon-192.png",
      "sizes": "192x192",
      "type": "image/png"
    },
    {
      "src": "/icon-512.png",
      "sizes": "512x512",
      "type": "image/png"
    }
  ]
}

欄位詳解

name 與 short_name

{
  "name": "My Awesome Application",
  "short_name": "AwesomeApp"
}
  • name:完整應用程式名稱(安裝對話框中顯示)
  • short_name:簡短名稱(主畫面圖示下方顯示)

display 顯示模式

{
  "display": "standalone"
}
說明
fullscreen全螢幕,隱藏所有瀏覽器 UI
standalone像原生 App,隱藏網址列
minimal-ui最少的瀏覽器 UI
browser普通瀏覽器模式

background_color 與 theme_color

{
  "background_color": "#0a0a0f",
  "theme_color": "#00ff88"
}
  • background_color:App 啟動時的閃屏背景色
  • theme_color:瀏覽器工具列和狀態列顏色

icons 圖示

{
  "icons": [
    {
      "src": "/icon-192.png",
      "sizes": "192x192",
      "type": "image/png",
      "purpose": "any maskable"
    }
  ]
}

建議提供的尺寸:

  • 192x192(Android 安裝)
  • 512x512(啟動閃屏)
  • SVG(任意尺寸)

在 HTML 中連結 Manifest

<head>
  <!-- Favicon & PWA -->
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
  <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
  <link rel="manifest" href="/manifest.json" />
</head>

Apple Touch Icon

iOS 不完全支援 Web App Manifest,需要額外的 meta 標籤:

<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />

Theme Color 主題色彩

什麼是 theme-color

theme-color 控制行動瀏覽器的 UI 顏色(網址列、狀態列等)。

設定方法

<head>
  <meta name="theme-color" content="#0a0a0f" />
</head>

動態切換(深色/淺色模式)

<meta name="theme-color" 
      content="#0a0a0f" 
      media="(prefers-color-scheme: dark)" />
<meta name="theme-color" 
      content="#ffffff" 
      media="(prefers-color-scheme: light)" />

效果展示

設定前:

  • 瀏覽器 UI 使用預設顏色
  • 與網站風格不一致

設定後:

  • 瀏覽器 UI 與網站主題融合
  • 更沉浸式的體驗

響應式漢堡選單實作

問題分析

傳統桌面版導覽列在手機上常見問題:

  1. 按鈕太小:難以點擊
  2. 溢出截斷:連結被切掉
  3. 擠壓跑版:文字擠在一起

解決方案:漢堡選單

HTML 結構

<header class="sticky top-0 z-50">
  <nav class="flex items-center justify-between h-16 px-4">
    <!-- Logo -->
    <a href="/" class="text-lg font-bold">HQ</a>

    <!-- 桌面版導覽(手機版隱藏) -->
    <div class="hidden md:flex items-center gap-6">
      <a href="/">Home</a>
      <a href="/blog">Blog</a>
      <a href="/tools">Tools</a>
    </div>

    <!-- 漢堡按鈕(桌面版隱藏) -->
    <button
      id="mobile-menu-btn"
      class="md:hidden w-10 h-10"
      aria-label="Toggle menu"
      aria-expanded="false"
    >
      <!-- 漢堡圖示 -->
      <svg id="menu-icon-open" class="w-6 h-6">
        <path d="M4 6h16M4 12h16M4 18h16" />
      </svg>
      <!-- X 關閉圖示(預設隱藏) -->
      <svg id="menu-icon-close" class="w-6 h-6 hidden">
        <path d="M6 18L18 6M6 6l12 12" />
      </svg>
    </button>
  </nav>

  <!-- 手機版下拉選單 -->
  <div id="mobile-menu" class="hidden md:hidden">
    <nav class="flex flex-col p-4 gap-2">
      <a href="/" class="py-3 px-4 rounded-lg">Home</a>
      <a href="/blog" class="py-3 px-4 rounded-lg">Blog</a>
      <a href="/tools" class="py-3 px-4 rounded-lg">Tools</a>
    </nav>
  </div>
</header>

JavaScript 切換邏輯

// 取得元素
const menuBtn = document.getElementById('mobile-menu-btn');
const mobileMenu = document.getElementById('mobile-menu');
const iconOpen = document.getElementById('menu-icon-open');
const iconClose = document.getElementById('menu-icon-close');

// 切換選單
function toggleMenu() {
  const isOpen = !mobileMenu.classList.contains('hidden');
  
  if (isOpen) {
    // 關閉選單
    mobileMenu.classList.add('hidden');
    iconOpen.classList.remove('hidden');
    iconClose.classList.add('hidden');
    menuBtn.setAttribute('aria-expanded', 'false');
  } else {
    // 開啟選單
    mobileMenu.classList.remove('hidden');
    iconOpen.classList.add('hidden');
    iconClose.classList.remove('hidden');
    menuBtn.setAttribute('aria-expanded', 'true');
  }
}

// 事件綁定
menuBtn.addEventListener('click', toggleMenu);

// 按 Escape 關閉
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape' && !mobileMenu.classList.contains('hidden')) {
    toggleMenu();
  }
});

// 點擊連結後關閉
mobileMenu.querySelectorAll('a').forEach(link => {
  link.addEventListener('click', () => {
    if (!mobileMenu.classList.contains('hidden')) {
      toggleMenu();
    }
  });
});

關鍵設計決策

為什麼選擇下拉式而非側邊欄

方式優點缺點
側邊欄空間大,可放更多內容遮擋主內容,需要手指移動較遠
下拉式不遮擋內容,操作區域集中空間較小

對於連結數量少於 10 個的網站,下拉式通常是更好的選擇。

aria-expanded 無障礙屬性

<button aria-expanded="false">...</button>
  • 告訴螢幕閱讀器選單是否展開
  • 必須在 JavaScript 中動態更新

行動版 Header 優化

問題:資訊過多導致擠壓

桌面版 Header 可能包含:

  • Logo
  • Back 連結
  • 頁面標題
  • 狀態指示
  • 語言切換

在 375px 螢幕寬度上,這些全部顯示會很擁擠。

解決方案:響應式隱藏

<header>
  <div class="flex items-center justify-between h-14">
    <!-- 左側 -->
    <div class="flex items-center gap-2">
      <a href="/">← Back</a>
      
      <!-- 分隔線:桌面才顯示 -->
      <span class="hidden sm:inline">|</span>
      
      <!-- 標題:桌面才顯示文字 -->
      <h1 class="flex items-center gap-2">
        <span>🚀</span>
        <span class="hidden sm:inline">Launchpad</span>
      </h1>
    </div>

    <!-- 右側 -->
    <div class="flex items-center gap-2">
      <!-- 完整狀態:桌面顯示 -->
      <div class="hidden sm:flex items-center gap-2">
        <span class="status-dot"></span>
        <span>All Systems Operational</span>
      </div>
      
      <!-- 簡化狀態:手機顯示 -->
      <div class="sm:hidden" title="All Systems Operational">
        <span class="status-dot"></span>
      </div>
      
      <LanguageSwitcher />
    </div>
  </div>
</header>

Tailwind 響應式斷點

sm  → 640px+   (手機橫向/小平板)
md  → 768px+   (平板)
lg  → 1024px+  (桌面)
xl  → 1280px+  (大桌面)
2xl → 1536px+  (超大螢幕)

行動優先設計原則

/* 錯誤:桌面優先 */
.element {
  display: flex; /* 預設桌面 */
}
@media (max-width: 768px) {
  .element { display: none; } /* 手機隱藏 */
}

/* 正確:行動優先 */
.element {
  display: none; /* 預設手機 */
}
@media (min-width: 768px) {
  .element { display: flex; } /* 桌面顯示 */
}

Tailwind 預設就是行動優先,所以:

<div class="hidden md:flex">
  <!-- 手機隱藏,平板以上顯示 -->
</div>

常見問題與解決方案

問題 1:重複 ID 導致事件失效

症狀:頁面上有多個相同元件(如語言切換器),只有第一個能用。

原因:HTML 規範要求 ID 必須唯一。

解決方案:改用 class 選擇器。

// ❌ 只會綁定第一個
document.getElementById('lang-toggle').addEventListener('click', ...);

// ✅ 綁定所有實例
document.querySelectorAll('.lang-toggle').forEach(btn => {
  btn.addEventListener('click', ...);
});

問題 2:選單關閉時 Icon 沒切換回來

解決方案:確保 Icon 切換邏輯正確:

function toggleMenu() {
  const isOpen = !mobileMenu.classList.contains('hidden');
  
  // 切換選單
  mobileMenu.classList.toggle('hidden');
  
  // 切換 Icon
  iconOpen.classList.toggle('hidden', !isOpen);
  iconClose.classList.toggle('hidden', isOpen);
}

問題 3:點擊選單外區域應該關閉

document.addEventListener('click', (e) => {
  const isClickInside = mobileMenu.contains(e.target) || 
                        menuBtn.contains(e.target);
  
  if (!isClickInside && !mobileMenu.classList.contains('hidden')) {
    toggleMenu();
  }
});

總結

PWA 檢查清單

  • 建立 manifest.json
  • 設定 theme-color
  • 提供各尺寸 icon
  • 連結到 HTML
  • 設定 Apple Touch Icon

行動優化檢查清單

  • 漢堡選單在 768px 以下顯示
  • 桌面導覽在 768px 以上顯示
  • Header 資訊響應式隱藏
  • 按鈕尺寸至少 44x44px
  • 無障礙屬性 (aria-expanded)

測試工具

  1. Chrome DevTools Device Mode:模擬各種手機尺寸
  2. Lighthouse PWA Audit:檢查 PWA 相容性
  3. 實際裝置測試:沒什麼比真機測試更準確

參考資源