# PWA:讓 Web 彷彿手機原生 App 最近遇到一個專案,需要利用 PWA 的技術, 讓網站能建立捷徑到手機主畫面上,而且使用起來像手機 App, 藉著這次專案的機會,來整理一下 PWA 的技巧與更進階的樣式設定。 # 漸進式網頁是什麼? PWA 的全名是 Progressive Web Apps(漸進式網頁應用程式,以下皆簡稱 PWA), 有 PWA 功能的網頁,會跳出「將 App 新增置主畫面」、「安裝 App」的按鈕, 安裝後從主選單點開,同一網頁就不會有網址列, 看起來就像一個原生的 App,還可設定推播通知與離線存取功能。 ### ▍PWA 是什麼時候發展的? 2015 年由 [設計師 Frances Berriman 和 Google Chrome 工程師 Alex Russell](https://infrequently.org/2015/06/progressive-apps-escaping-tabs-without-losing-our-soul/) 提出。 目的是希望瀏覽手機網頁時,能夠像原生 App 一樣有使用者友善的體驗。 2014 年 Google 開發者大會開始提出相關技術,隨後 Google 大力推動 Android 的 PWA 開發。 Firefox 在 2016 年開始支援 PWA 的核心技術(Service Worker)。 Microsoft Edge 和 Apple Safari 在 2018 年隨後跟上。 現在所有主要系統上都可使用(參考自 [vuestore 的整理](https://vuestorefront.io/blog/pwa))。 ### ▍PWA 字面上意思是? 漸進式網路應用程式(Progressive web app)的譯名,可能讓第一次看到的人較難直接理解意涵。 這具有「逐漸增強功能」的意涵,說明這種網站可以是電腦版網頁、行動版網頁, 也可以成為桌面應用程式,甚至離線使用、推播訊息。 讓網站「逐漸成為『應用程式』([They progressively become "apps"](https://infrequently.org/2015/06/progressive-apps-escaping-tabs-without-losing-our-soul/#:~:text=They%20progressively%20become%20%22apps%22))」。 ### ▍PWA 具有哪些特徵? 傳統上,網站不太像是使用者「擁有的東西」而更像是「訪問的地方」。 當使用者不訪問該網站時,該網站不會出現在使用者的裝置上; 當使用者訪問網站時,只能藉由開啟瀏覽器並依賴網路連線到該網站。 雖然與原生 App 相比,網站具有這樣的限制,這樣的限制,但網站也有另一些優勢,例如: * 單一程式碼: * 由於網路是跨平台的,因此網站可藉由單一程式碼,在不同的作業系統和裝置上運行。 * 透過網路分發: * 網路是很好的分發平台。只需使用網址,即可共享和存取網站,無需透過應用程式商店。 此外,PWA 也結合了原生 App 的優點,例如: * 可以安裝在裝置上: * 可以從平台的應用程式商店安裝,也可以直接從網路安裝。 * 可以像原生 App 一樣安裝,並且可以自訂安裝過程。 * 安裝後,PWA 會在裝置上獲得一個應用程式圖示以及原生 App。 * 安裝後,PWA 可以作為獨立應用程式啟動,而不是瀏覽器中的網站。 * 可以在背景和離線狀態下運作: * 在設備沒有網路連線時工作。 * 後台更新內容。 * 響應來自伺服器的推播訊息。 * 使用作業系統通知系統顯示通知。 * 使用整個螢幕,而不是在瀏覽器 UI 中運行。 * 整合到設備中,註冊為共享目標和來源,並存取設備功能。 (參考自:[What is a progressive web app, MDN](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/What_is_a_progressive_web_app)) 整體來說,PWA 有以下數點特性: 1. Progressive 漸進式 - 使用者於瀏覽器中即可操作 (註:各瀏覽器於各平台上支援度不一) 2. Responsive 響應式 - 可操作於桌機、手機或平板等裝置上 3. Connectivity independent 連結獨立 - 可基於service workers架構執行,於離線或在有限網路下操作 4. App-like 近似APP - 類似APP的操作介面 5. Fresh 維持新版 - 因 service worker 架構,讓應用程式隨時都是在更新狀態 6. Safe 安全性 - 必須於加密模式之下進行,因此安全較受到保障 7. Discoverable 可被搜尋 - 透過 manifest 設定檔案及 service worker 使搜尋引擎可正常搜尋到 8. Re-engageable 有互動性 - 透過類似推播方式與使用者更加互動 9. Installable 可安裝 - 可以拉存到手機的桌面,感覺就像是傳統的APP (註:非每種瀏覽器均支援) 10. Linkable 可連結 - 可經由連結輕易分享 (取自:[PWA介紹 (Progressive Web App),優、缺點及範例介紹,Arshire](https://www.arshire.com/blog/pwa)) ### ▍PWA 運行在瀏覽器嗎? 當你在瀏覽器中造訪某個網站時,瀏覽器 UI 讓你很明顯感受到「網頁正在瀏覽器中運行」。 PWA 可以在沒有瀏覽器 UI 的情況下使用,但從技術角度來看,它們仍然是網站。 這意味著 PWA 需要藉由瀏覽器引擎(如 Chrome、Firefox )來管理和運行它們。 相較之下,原生 App 通常由平台作業系統管理該應用程序,提供其運行的環境。 ![image](https://hackmd.io/_uploads/H12W8-YVT.png) ### ▍PWA 有哪些優缺點? ##### 優點 只要將網站製作完成,再透過PWA技術,就可以將網頁轉換成原生App的效果。 對企業來說,不用花大錢額外製作原生App,節省成本。 * 迅速進入手機市場: * Progressive Web Apps(PWA)是最簡單的進入手機市場的方式,可在幾個月內設置完成,並且適用於所有設備。 * 一次提供所有功能: * 使用PWA技術,團隊只需構建一個無縫運行於任何設備的應用程序,無需單獨開發iOS和Android應用。 * 成本效益最佳化: * 由於PWA的全功能,節省時間和降低開發成本,無需支付應用商店費用。 * 降低顧客獲取成本(CAC): * PWA允許用戶直接從移動瀏覽器安裝應用程序,提高試用機會,且無需下載更新。 * 發揮無頭商務的優勢: * PWA使用無頭架構提供卓越的靈活性,並分離前端和後端,使營銷團隊獨立進行更改。 * 優化的SEO結果: * PWA加速Google索引,並且採用標準URL和完整的伺服器端渲染(SSR),有助於提高搜尋引擎排名。 * 降低跳出率: * PWA無論網絡條件如何,均可即時加載,並在離線狀態下工作,降低跳出率。 * 提高參與度、轉換率和收入: * PWA提供優越性能、移動優化和優秀的UX,通過全屏功能、易於訪問以及推送通知提高用戶參與度。 (取自:https://vuestorefront.io/blog/pwa) ##### 缺點 * 瀏覽器/平台的支援度不一 * * [iOS很多不支援](https://www.youtube.com/watch?v=eoUvIm8Pl6I&ab_channel=DevTrends) * 大部分消費者並不清楚如何操作 * 並不是說不懂如何操作網站,而是說不知道該如何把該網站轉成類應用程式的操作模式 * PWA 會比原生 App 耗電量來的高 * 由於它們是用複雜的程式編寫的,手機必須更加努力的轉譯程式碼 (參考自:[PWA,Arshire](https://www.arshire.com/blog/pwa)) # 基礎設定 ### ▍讓網頁可安裝 PWA 最基礎的設定,就是讓網頁可安裝下來,從主畫面進入、沒有瀏覽器 UI。 基礎設定上,我們只需要增加一個 App 用的圖示,以及一個設定檔。 ![image](https://hackmd.io/_uploads/Bk9h_TFVT.png) ##### 程式碼 在你的網頁的 head 中引入 manifest.json,就完成 PWA 的設定。 ```htmlembedded <!doctype html> <html lang="en"> <head> <link rel="manifest" href="manifest.json" /> </head> <body></body> </html> ``` ##### 應用程式圖示 放入自己想要的 Icon,個人建議一開始用 192x192(手機版適用)。 * 圖示大小要正方形,並且與實際大小相同,不然Chrome會無法下載。 ##### 設定檔:manifest.json 瀏覽器是透過這個檔案,來知道如何將網頁安裝在用戶的電腦或行動裝置上。 裡面是對 PWA 顯示的一些設定,有四項必填:name、start_url、display、icons。 通常會放在根目錄。 ``` json { "name": "PWA 範例網", // App 名稱,■■必填■■ "short_name": "PWA", // App 名稱縮寫,顯示空間不足時使用 "description": "這是一個簡單的 PWA 範例。", // App 的描述 "start_url": ".", // 首頁路徑,依 manifest 檔案位置來看,■■必填■■ "display": "standalone", // 顯示模式,■■必填■■ "orientation": "portrait", // 定義預設的顯示方向 "background_color": "#5a0fc8", // 預設背景色 "icons": [ // 圖示,■■必填■■ { "src": "icon.png", // 圖示路徑 "sizes": "192x192", // 圖示尺寸 "type": "image/png" // 圖示格式,可省略 } ] } ``` * display * fullscreen 全螢幕 * standalone 原生 App 模式 * minimal-ui 最基本的瀏覽器 UI * browser 瀏覽器樣式 * orientation * Arial 不限制 * naturl 設備預設的方向 * portrait 直向 * landscape 橫向 ##### 成果 設定完成後,瀏覽器點開網頁,會看到一個下載的圖示,就完成設定。 可以嘗試下載看看,就會變成一個沒有網址列的介面。 手機上的話也可以有類似效果,然而 iOS 使用者需要用 Safari 開啟,並手動將網頁加到主畫面。 (參考自:[Making PWAs installable, MDN](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable)) ### ▍離線存取功能 離線存取功能是 PWA 的一個重要技術,利用 Service Worker 監控前端事件與回應。 Service Worker 本質上充當位於 Web 應用程式、瀏覽器和網路(如果可用)之間的代理伺服器。 除此之外,它們的目的是創建有效的離線體驗、攔截網路請求並根據網路是否可用採取適當的操作, 以及更新伺服器上的資產。它們還允許存取推播通知和後台同步 API。 Server Worker 是大型商業網展常使用的技術,可以將部分的 JS 程式放在瀏覽器背景執行,以做到像是推播的功能。 ##### 原理 JS 程式是由瀏覽器的主執行緒(Main Thread)負責執行, Service Worker 則是在不同執行緒非同步運行,不會影響網頁的渲染。 一般網頁:客戶端發送http請求給伺服器,伺服器再回應給客戶端。 Service Worker:聽fetch事件,攔截網頁的http請求,藉由 Service Worker 的快取(Catch)功能, 可以選擇由快取取得回應,因此就算離線,使用者也能夠正常離覽網頁。 ![image](https://hackmd.io/_uploads/rkRoTDd4a.png) Service Worker 有自己的生命週期,從下載、安裝到啟用,在不同的生命週期可以利用監聽事件,進行相對應的處理。 **Service Worker 不能直接操作DOM物件**,如果有需要可以透過postMessage()的方法發送訊息,然後透過message事件來溝通。 ##### 主要功能 * 離線瀏覽網頁 * 前面提到的,由快取回應開啟網頁的請求。 * 離線送出表單 * 離線狀態下無法送出表單,這種情況可以先將資料存在IndexedDB,並註冊sync事件,SW 監聽 Background Sync 事件,網路重新連線時,再將 IndexedDB 資料上傳到伺服器,達到離線送出表單的功能。 * 推播通知 * SW 會監聽 PUSH 事件,當收到伺服器發出的推播通知時,會顯示給用戶。 * 加入主畫面(Add to Home Screen, A2HS)」 * serviceWorkerContainer.register() 註冊 SW 檔案 * 註冊成功的話,SW 會運行在全域環境 ##### 操作方法 先提供監聽方法,讓你了解 Service Worker 運作過程 ``` javascript // 監聽 install 事件 self.addEventListener('install', (e) => { console.log('安裝') self.skipWaiting(); }); // 監聽 activate 事件 self.addEventListener('activate', (e) => { console.log('啟用') }); // 監聽 fetch 事件 self.addEventListener('fetch', (e) => { console.log('fetch') }); // 引入 Workbox,一個用於簡化 Service Worker 開發的函式庫 importScripts('https://cdnjs.cloudflare.com/ajax/libs/workbox-sw/7.0.0/workbox-sw.js'); // 註冊 Service Worker 路由,使用 Workbox 的 registerRoute 方法,需要帶入兩個參數 workbox.routing.registerRoute( new RegExp('.*'), // 使用正規表達式匹配所有 URL,.* 表示所有字符零次或多次 new workbox.strategies.NetworkFirst(), // 使用 NetworkFirst 快取策略,優先嘗試從網路獲取資源 ); ``` index.html 寫下這些來註冊 Service Worker。 ```javascript navigator.serviceWorker.register(scriptURL, options) .then(() => { // 註冊成功時執行 }).catch((error) =>{ // 註冊失敗時執行 }); // scriptURL 是 SW 檔案,在這個情況下是 sw.js // options 是 SW 的 scope,預設為「./」,也就是根目錄 // 如果 sw.js 放在網站根目錄,options 可以不用寫 // scriptURL 的相對路徑是相對於 sw.js ``` 註冊前要檢查瀏覽器(navigator)是否支援 Service Worker,等到網頁資源都載入再註冊 Service Worker。 ```javascript if('serviceWorker' in navigator) { window.addEventListener('load', () => { navigator.serviceWorker.register('sw.js') .then(() => console.log('註冊成功')) .catch((err) => console.log('註冊失敗')); }); } ``` 如果有多個 html 可以把註冊程式拉出來,再引入每個 html ##### 成果 可以離線載入。 # 進階技巧 ### ▍各尺寸 Icon 與啟動畫面 主要是 iOS 目前不支援 Web App Manifest API 規範,需要引入自訂 html 標籤來為您的 PWA 設定圖示和啟動畫面。 您需要為 Apple 建立每個尺寸的啟動畫面(splash),並為每個影像建立各自的 html 標籤。 可以藉由套件 [pwa-asset-generator](https://github.com/elegantapp/pwa-asset-generator) 來產生各尺寸的 icon、啟動畫面。 ##### 特性 * 生成類型:圖示、啟動畫面 * 自動更新您的 manifest.json 和 index.html 文件 * 提供 iOS 的深色、淺色啟動畫面選擇 * 因為要生成不同尺寸,建議使用SVG檔 * 用CSS設定樣式 ##### 指令 ```htmlmixed // npx pwa-asset-generator [圖片檔名] -i [index.html路徑] -m [manifest.json路徑] npx pwa-asset-generator logo.svg -i ./index.html -m ./manifest.json ``` 終端機輸入後,在同一層目錄就可以得到: * 圖示:192x192、512x512、180x180(iOS用) * 啟動畫面:30種尺寸 * 在 index.hmlt 寫好引入的標籤 會產出這麼多的圖片! ![image](https://hackmd.io/_uploads/Byzd0RCNT.png) 實際使用時,有時專案的 index.html 與 logo.svg 不會放在同一層, 我會創一個新資料夾與空的 html,把資料都放在同一層,產生檔案與標籤後再移回專案, 就不用一直研究指令如何在不同資料夾取放資料的問題。 以下是一些好用的指令: * 生成類型 * `--icon-only` 圖示 * `--splash-only` 啟動畫面 * `--landscape-only‵` 啟動畫面(僅橫的) * `--portrait-only` 啟動畫面(僅直的) * 帶有透明度的png:`--opaque false` * 設定padding,預設`--padding "calc(50vh - 10%) calc(50vw - 10%)"`,可自行調整 * 設定漸層底色 * `-b "linear-gradient(90deg, rgba(207, 234, 255, 1) 0%, rgba(240, 243, 255, 1) 50%, rgba(223, 205, 255, 1) 100%)"` ![apple-splash-2778-1284](https://hackmd.io/_uploads/Byb0-JyBT.jpg) 另外,他們提供 iOS 的深色、淺色啟動畫面設定。 以下是語法說明,基本上就是想要設定什麼就一直往後加。 ```htmlmixed= // npx pwa-asset-generator [圖片檔名] [圖片資料夾路徑] [iOS啟動畫面:深色] --background [背景色] [僅啟動畫面] --type [輸出類型] --qality [圖片品質] --index [index.html路徑] npx pwa-asset-generator light-logo.svg ./assets --dark-mode --background dimgrey --splash-only --type jpeg --quality 80 --index ./src/app/index.html // npx pwa-asset-generator [圖片檔名] [圖片資料夾路徑] --background [背景色] [僅啟動畫面] --type [輸出類型] --qality [圖片品質] --index [index.html路徑] npx pwa-asset-generator dark-logo.svg ./assets --background lightgray --splash-only --type jpeg --quality 80 --index ./src/app/index.html ``` ### ▍樣式設定(iOS) * Add a Short Name 在 manifest.json 設定 PWA 的短名稱,如果使用者的介面不允許太多字時可自動調整。 * Make the Status Bar transparent 預設情況下,iOS 的 PWA 頂部狀態列會為黑底,可用 meta 標籤使其透明。 ```htmlmixed <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"/> <meta name="viewport" content="initial-scale=1, viewport-fit=cover" /> ``` 而 iPhone 的瀏海可能會擋到 Header 文字,可以做以下設定。 ```css header { padding-top: env(safe-area-inset-top); } ``` * 固定 header 在使用手機App時,header 通常會固定在畫面上方, 讓 PWA 網站加入這樣的設定可以增進使用者體驗。 ```css @media screen and (display-mode: standalone) { position: fixed; } ``` * 禁止使用者縮放 手機瀏覽器可以輕易縮放網頁大小, 但一般原生 App 沒有這樣的功能,我們可以停用縮放功能來達到這個效果。 ```htmlmixed <meta name="viewport" content="initial-scale=1, viewport-fit=cover, user-scalable=no" /> ``` * Set the tap highlight color to transparent 預設情況下,iOS 的 PWA 會像網頁一樣以灰色方塊突出顯示所有連結點擊。 關掉灰色顯示會更接近原生 App 的樣式。 ```css body { -webkit-tap-highlight-color: transparent; } ``` ### ▍自動安裝功能(Android) 若您的網站是可安裝的 PWA 網頁,那麼瀏覽器將顯示按鈕來顯示此網頁可安裝。 ![image](https://hackmd.io/_uploads/HJnCiN1Ba.png) 但是,我們也可以自己製作觸發安裝的按鈕,也能讓使用者更容易發現。 ```htmlmixed <button class="addBtn">安裝程式</button> ``` ```javascript // 檢查是否安裝 window.addEventListener("beforeinstallprompt", async (event) => { const relatedApps = await navigator.getInstalledRelatedApps(); // Search for a specific installed platform-specific app const psApp = relatedApps.find((app) => app.id === "com.example.myapp"); if (psApp) { event.preventDefault(); // Update UI as appropriate } }); // 安裝 let deferredPrompt; // 將事件隱藏用(??) const addBtn = document.querySelector('.addBtn'); addBtn.style.display = 'none'; window.addEventListener('beforeinstallprompt', (e) => { // 防止在 Chrome 67 之前的版本中自動顯示安裝提示 e.preventDefault(); // 將事件隱藏以便稍後觸發 deferredPrompt = e; // 更新UI以通知用戶可以將應用添加到主屏幕 addBtn.style.display = 'block'; addBtn.addEventListener('click', (e) => { // 顯示安裝提示 deferredPrompt.prompt(); // 等待用戶響應安裝提示 deferredPrompt.userChoice.then((choiceResult) => { if (choiceResult.outcome === 'accepted') { console.log('使用者接受 A2HS 的請求。'); // 隱藏顯示 A2HS 按鈕的用戶界面 addBtn.style.display = 'none'; } else { console.log('使用者拒絕 A2HS 的請求。'); } deferredPrompt = null; }); }); }); ``` (參考自:[Trigger installation from your PWA, MDN](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/How_to/Trigger_install_prompt)、[ホーム画面に追加, MDN](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable)) ### ▍分享功能 也可以觸發瀏覽器的分享功能,讓別人看看你的 PWA 網站。 ```javascript // sharing.js const shareBtn = document.querySelector('.shareBtn'); shareBtn.onclick = async (filesArray) => { if (navigator.canShare) { navigator.share({ url: 'https://charliewuuu.github.io/PWA/', title: 'PWA 超酷!', text: 'PWA 超酷!我學會怎麼建立一個 PWA 程式了!', }); } }; ``` <!-- # 可能提問 ### 如果我有很多頁要怎麼辦? *在每一頁都要引入server-worker?* ### 有沒有效能問題? *畢竟這個app是藉由瀏覽器運行,等於多一層系統,會不會很吃資源?* ### 還有什麼其他功能? 推播功能等。 ### PWA會是未來的趨勢嗎? 不一定。根據國外的報導(2021的下半年),雖然GOOGLE努力推廣多年,但是PWA架構並沒有很普及,對大多數人來說還是相對較新的概念。2018年曾有許多科技網站大力吹捧PWA將會取代傳統APP,不過許多之前用PWA開發模式的網站已變更回單純網站。 --> # 參考資料 * 網站 * 文件 * MDN:[Progressive web apps](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps) * MDN:[What is a progressive web app](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/What_is_a_progressive_web_app) * MDN:[Making PWAs installable](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable) * MDN:[ホーム画面に追加](https://developer.mozilla.org/ja/docs/Web/Progressive_web_apps/Guides/Making_PWAs_installable) * 樣式教學文 * Sam Selikoff:[8 Tips to Make Your Website Feel Like an iOS App](https://samselikoff.com/blog/8-tips-to-make-your-website-feel-like-an-ios-app) * 提出 PWA 此名詞的文章 * Alex Russell:[Progressive Web Apps: Escaping Tabs Without Losing Our Soul](https://infrequently.org/2015/06/progressive-apps-escaping-tabs-without-losing-our-soul/) * 首次提出 service worker 與 menifest 的開發者大會 * [Chrome Dev Summit 2014](https://web.dev/case-studies/chrome-dev-summit) * PWA 的發展歷史以及商業上的好處 * [vuestore:PWA](https://vuestorefront.io/blog/pwa) * PWA 介紹,寫得很清楚,優缺點、普及性與對發展性的存疑都很清楚 * [Arshire:PWA](https://www.arshire.com/blog/pwa) * Youtube * PWA 初步介紹,有很簡單的教學步驟 * Fireship:[Progressive Web Apps in 100 Seconds // Build a PWA from Scratch](https://www.youtube.com/watch?v=sFsRylCQblw) * PWA 的進階功能,例如分享功能 * Fireship:[7 Web Features You Didn’t Know Existed](https://www.youtube.com/watch?v=ppwagkhrZJs&ab_channel=Fireship) * 書 * [HTML/CSS/JavaScript與前端框架的完美結合:使用Bootstrap與PWA技術, 新手從這開始!](https://www.tenlong.com.tw/products/9786263333109?list_name=c-css)