• Darren
PWA 與行動裝置優化完整指南:從 Manifest 到漢堡選單
學習如何將網站轉變為 PWA,包含 Web App Manifest 設定、theme-color 配置、響應式漢堡選單實作,以及行動裝置最佳實踐
#pwa
#mobile
#responsive
#manifest
#hamburger-menu
現代網站必須在行動裝置上提供卓越體驗。本文將深入探討 PWA(漸進式網頁應用程式)的實作,以及響應式設計的最佳實踐。
目錄
什麼是 PWA
PWA 的核心特性
| 特性 | 說明 |
|---|---|
| 可安裝 | 可以加到手機主畫面,像 App 一樣啟動 |
| 離線可用 | 使用 Service Worker 快取資源 |
| 響應式 | 適應各種螢幕尺寸 |
| 安全 | 必須透過 HTTPS 提供服務 |
為什麼要做 PWA
- 更好的使用者體驗:全螢幕模式,沒有瀏覽器 UI
- 更高的參與度:主畫面圖示增加回訪率
- 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 與網站主題融合
- 更沉浸式的體驗
響應式漢堡選單實作
問題分析
傳統桌面版導覽列在手機上常見問題:
- 按鈕太小:難以點擊
- 溢出截斷:連結被切掉
- 擠壓跑版:文字擠在一起
解決方案:漢堡選單
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)
測試工具
- Chrome DevTools Device Mode:模擬各種手機尺寸
- Lighthouse PWA Audit:檢查 PWA 相容性
- 實際裝置測試:沒什麼比真機測試更準確