PWA
Progressive Web Application
2020/03/27
2020/03/28 (補充內容)
2020/03/30 (更新內容)
簡介
Progressive Web Application (PWA)就是要讓網頁的行為,在手機上執行時,能夠跟手機App一致,在介面呈現的部分,已經可以透過RWD來處理,還有一些行為,如:安裝、可離線操作、存取手機上的資源,跟手機是不一樣的,透過PWA,可以進行安裝的動作,也可以進行離線操作,目前,已經可以透過Browser存取一些手機上的資源,未來會慢慢的能夠跟App可取得的權限幾乎一樣。
體驗一下PWA,直接用手機打開網頁:
- https://whatwebcando.today/
- 點一下Audio & Video Capture
- 點Grab Video,授權之後,可以看到手機鏡頭的影像囉!
存取手機上的資源
現在可以利用browser去存取手機上的資源,例如,可以取得目前的經緯度,先在html中加一個按鈕,並留一個顯示位置的區域,最後,引用geoFindMe.js。。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>PWA Demo</title>
</head>
<body>
<button id="askButton" onclick="geoFindMe()">顯示經緯度</button>
<div id="target"></div>
<script src="./js/geoFindMe.js"></script>
</body>
</html>
先利用navigator.geolocation,確認瀏覽器是否支援。
if (!navigator.geolocation){
output.innerHTML = "<p>Geolocation is not supported by your browser</p>";
return;
}
利用navigator.geolocation.getCurrentPosition取得位置,當成功時,呼叫success,失敗時呼叫error。(這兩個函數就是所謂的call back function)。
navigator.geolocation.getCurrentPosition(success, error);
成功的時候,將緯度與經度顯示在網頁上:
function success(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
output.innerHTML = '<p>緯度:' + latitude + '<br>經度: ' + longitude + '</p>';
};
失敗的時候,
function error() {
output.innerHTML = "未授權,無法取得位置";
};
完整的geoFindMe.js
function geoFindMe() {
var output = document.getElementById("target");
if (!navigator.geolocation){
output.innerHTML = "<p>Geolocation is not supported by your browser</p>";
return;
}
function success(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
var dateTime = date+' '+time;
output.innerHTML = '<p>緯度:' + latitude + '<br>經度:' + longitude + '</p>';
};
function error() {
output.innerHTML = "未授權,無法取得位置";
};
output.innerHTML = "<p>定位中...</p>";
navigator.geolocation.getCurrentPosition(success, error);
}
現在可以利用browser去存取手機上的資源,例如,可以取得目前的經緯度,先在html中加一個按鈕,並留一個顯示位置的區域,最後,引用location.js。
如果希望能一直更新目前的經緯度,就改用watchPosition,如果都在原地是看不出來有在更新,所以,加個時間,就可以知道其實是有在更新 (可能要等個幾分鐘):
function geoFindMe() {
var output = document.getElementById("target");
var watchid;
if (!navigator.geolocation){
output.innerHTML = "<p>Geolocation is not supported by your browser</p>";
return;
}
function success(position) {
var latitude = position.coords.latitude;
var longitude = position.coords.longitude;
var today = new Date();
var date = today.getFullYear()+'-'+(today.getMonth()+1)+'-'+today.getDate();
var time = today.getHours() + ":" + today.getMinutes() + ":" + today.getSeconds();
var dateTime = date+' '+time;
output.innerHTML = '<p>緯度:' + latitude + '<br>經度:' + longitude + '<br>'
+ dateTime + '</p>';
};
function error() {
output.innerHTML = "未授權,無法取得位置";
};
output.innerHTML = "<p>定位中...</p>";
watchid = navigator.geolocation.watchPosition(success, error);
}
參考資料
試試看,按了按鈕之後,就可以取得手機的經緯度了,並且會持續更新經緯度。利用watchPosition可以一直追蹤位置(如果正在移動中),並呼叫appendLocation。
可以利用手機試試看,當網頁要求授權「存取您的位置資訊」時,要選擇「允許」,否則就會顯示「Unable to retrieve your location」。
**注意** 在chrome下,如果存取的網站不是localhost,也不是採用https,會被直接拒絕,不會要求授權「存取您的位置資訊」,可使用其他的瀏覽器試試看。
**注意** 如果使用手機,不能連localhost,因為那只會連到手機,必須要透過電腦的IP來連線,很多同學的電腦是連家裡的網路,如果利用ipconfig查出來的ip是196.168.X.X,那就表示,這組IP是內網的IP,所以,手機也要連上家裡的網路(而不是利用4G),才連得上 (詳參: 何謂『虛擬 IP 』,與『實體 IP 』或者『固定 IP 』『動態 IP 』有啥不同? 、 [教學]IP 位址的基本介紹)。
以下是另一種寫法,主要差別是利用addEventListener,也省略了錯誤處理的部分:
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>PWA Demo</title>
</head>
<body>
<p> hi! This's PWA Demo. </p>
<button id="askButton">Ask for location</button>
<div id="target"></div>
<script src="./js/location.js"></script>
</body>
</html>
在location.js中,利用getCurrentPosition,並且將取得的位置(location)傳給appendLocation處理。
var target = document.getElementById('target');
var watchId;
function appendLocation(location, verb) {
verb = verb || 'updated';
var newLocation = document.createElement('p');
newLocation.innerHTML = 'Location ' + verb + ': ' + location.coords.latitude + ', ' + location.coords.longitude + '';
target.appendChild(newLocation);
}
if ('geolocation' in navigator) {
document.getElementById('askButton').addEventListener('click', function () {
navigator.geolocation.getCurrentPosition(function (location) {
appendLocation(location, 'fetched');
});
watchId = navigator.geolocation.watchPosition(appendLocation);
});
} else {
target.innerText = 'Geolocation API not supported.';
}
參考資料
安裝
首先,你在你的AppServ的www資料夾下,產生一個新的資料夾 (如:test-pwa),並確認你的AppServ可以執行。
第二,在test-pwa資料夾下新增一個manifest.json,並且網路上下載一個(或多個不同大小的icon)。
**如果看到 Manifest does not contain a suitable icon - PNG format of at least 144px is required, the sizes attribute must be set, and the purpose attribute, if set, must include "any" or "maskable",可能的問題是下載的ICON格式有問題,另外,一定要有一個是大於144X144的icon。
{
"short_name": "Ben",
"name": "Ben Wu",
"icons": [
{
"src": "icons/bookmark-ribbon.png",
"sizes": "96x96"
},
{
"src": "icons/fruit.png",
"sizes": "512x512"
}
],
"start_url": "http://localhost/test-pwa/",
"background_color": "#000",
"theme_color": "#536878",
"display": "standalone"
}
關於manifest.json的解釋
- PWA學習筆記-4:manifest.json
- Day 21 - 30 天 Progressive Web App 學習筆記 - To-Do List 實作 PWA - Web App Manifest File
- Add a web app manifest
- Web App Manifest
再開啟網頁,記得在head標籤中引用manifest 。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="manifest" href="./manifest.json">
<meta charset="UTF-8">
<title>PWA Demo</title>
</head>
<body>
<p> hi! This's PWA Demo. </p>
</body>
</html>
這時開啟網頁的「開發人員工具」,並切到「Application」頁籤,就會看到「Manifest」選項,就會到剛所設定應用程式的名稱、圖示與URL。這時候會看到因為目前沒有service worker,所以,還不能進行安裝。
可以利用Android手機連上你的網頁,打開網頁之後,可以在設定(...)中在「加到」項下看到「加到首頁」,可以點選「加到主畫面」或在「加到」可以選擇「桌面」,在手機的桌面上就可以看到了,也可以看到剛剛設定的icon。使用iPhone的話,希望在直接把網頁加入主畫面後,要能看到icon,請加入以下的link (詳參: PWA 實戰經驗分享 、PWA icons are not used in iOS 11.3)。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<link
rel='apple-touch-icon'
href='./icons/fruit.png'
sizes='512x512'
/>
<link rel="manifest" href="./manifest.json">
<meta charset="UTF-8">
<title>PWA Demo</title>
</head>
<body>
<p> hi! This's PWA Demo. </p>
</body>
</html>
**注意** 如果使用手機,不能連localhost,因為那只會連到手機,必須要透過電腦的IP來連線,很多同學的電腦是連家裡的網路,如果利用ipconfig查出來的ip是196.168.X.X,那就表示,這組IP是內網的IP,所以,手機也要連上家裡的網路(而不是利用4G),才連得上 (詳參: 何謂『虛擬 IP 』,與『實體 IP 』或者『固定 IP 』『動態 IP 』有啥不同? 、 [教學]IP 位址的基本介紹)。
還有很多內容 (例如自動加到桌面....),未完待續....
離線操作
PWA要能夠可以離線操作,要靠serviceWorker進行快取。接下來在index.html先加入javascript,啟用serviceWorker:
<!DOCTYPE html>
<html lang="en">
<head>
<link
rel='apple-touch-icon'
href='./icons/fruit.png'
sizes='512x512'
/>
<link rel="manifest" href="./manifest.json">
<meta charset="UTF-8">
<title>PWA Demo</title>
</head>
<body>
<p> hi! This's PWA Demo. </p>
<script>
if ('serviceWorker' in navigator) {
console.log("Will service worker register?");
navigator.serviceWorker.register("./service-worker.js")
.then(function(reg) {
console.log("Yes it did.");
}).catch(function(err) {
console.log("No it didn't", err)
});
}
</script>
</body>
</html>
service-worker.js:
// install
self.addEventListener('install', event => {
console.log('installing…');
});
// activate
self.addEventListener('activate', event => {
console.log('now ready to handle fetches!');
});
// fetch
self.addEventListener('fetch', event => {
console.log('now fetch!');
});
都完成後,再點Chrome右上的選單圖示,在「更多工具...」上面就可看到「安裝Ben Wu」。
點一下,就會出現安裝畫面,再點「安裝」。當安裝完畢後,就可以在桌面看到安裝的應用程式了。也可以利用Android手機連上你的網頁,打開網頁之後,可以在設定(...)中在「加到」項下看到「首頁」,可以點選「加到首頁」,在桌面上就可以看到了,也可以看到剛剛設定的icon。使用iPhone的話,希望在直接把網頁加入主畫面後,要能看到icon,請加入以下的link (詳參: PWA 實戰經驗分享 、PWA icons are not used in iOS 11.3)。
這時開啟網頁的「開發人員工具」,並切到「Application」頁籤,在「Application」下會看到「Service Workers」選項,就會到剛註冊的service-worker.js。如果在Status下標註「#xxxx is activated and running」,那就表示service-worker已經成功的執行了。在下面的Console,可以看到在index.html及service-worker.js所有console.log的內容了。
install及activate下的訊息只會在第一次註冊的時候出現,之後,當我們reload時,因為已經安裝也啟動了,所以,只會觸發fetch。
接下來,我們會使用到google提供的Workbox,在service-worker.js裡利用importScripts匯入workbox-sw.js。如果成功的載入,會出現「Yay! Workbox is loaded 🎉」,記得要把前一次的service worker停掉,否則新的service worker會被卡住(waiting to activate),這時候,要把先將把前一次的service worker停掉 ,按「unregister」,再重整一次瀏覽器,就可以了。每次只要改了service-worker.js就要重複這個動作。
service-worker.js:
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
if (workbox) {
console.log(`Yay! Workbox is loaded 🎉`);
} else {
console.log(`Boo! Workbox didn't load 😬`);
}
// install
self.addEventListener('install', event => {
console.log('installing…');
});
// activate
self.addEventListener('activate', event => {
console.log('now ready to handle fetches!');
});
// fetch
self.addEventListener('fetch', event => {
console.log('now fetch!');
});
我們來試試看,將index.html放到cache。
service-worker.js:
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
// 使用precache功能,在offline下也可以執行
// 要存進cache storage裡的檔案清單
var cacheFiles = [
{
url: './index.html',
revision: '00000001' // 加revision,版本改了以後,sw.js 在 application 上會更新
}
];
workbox.precaching.precacheAndRoute(cacheFiles);
這時開啟網頁的「開發人員工具」,並切到「Application」頁籤,在「Cache」下就會看到「Cache Storage」選項,就會到剛剛存進cache storage的index.html。
我們試試看是不是真的有用,我們可以把AppServ停掉,reload這個頁面,會發現這個頁面不會出現「localhost 拒絕連線」,但是,如果是連到http://localhost,是會出現「localhost 拒絕連線」。因為我們成功的把頁面放到cache storage裡了!
但是,因為index.html用到manifest.json以及圖片,所以,會出現「Precaching did not find a match for /test-pwa/icons/fruit.png」、「Precaching did not find a match for /test-pwa/icons/fruit.png」,解決的方法就是把這兩個檔案也存進cache storage裡。
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
// 使用precache功能,在offline下也可以執行
// 要存進cache storage裡的檔案清單
var cacheFiles = [
'icons/fruit.png',
'manifest.json',
{
url: './index.html',
revision: '00000001' // 加revision,版本改了以後,sw.js 在 application 上會更新
}
];
workbox.precaching.precacheAndRoute(cacheFiles);
好,啟動App Serv,這時候,這兩個檔案就會進入cache storage了。這時候會看到「Workbox is precaching URLs without revision info: icons/fruit.png, manifest.json This is generally NOT safe.」,這就是為什麼我們需要revision了。
service-worker.js:
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
// 使用precache功能,在offline下也可以執行
// 要存進cache storage裡的檔案清單
var cacheFiles = [
{ url: 'icons/fruit.png', revision: null},
{ url: 'manifest.json', revision: null},
{
url: './index.html',
revision: '00000001' // 加revision,版本改了以後,sw.js 在 application 上會更新
}
];
workbox.precaching.precacheAndRoute(cacheFiles);
記得,當內容修改之後,要改這邊的版本,例如,我們改了manifest.json,否則,會以為內容沒有更動。
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.2/workbox-sw.js');
// 使用precache功能,在offline下也可以執行
// 要存進cache storage裡的檔案清單
var cacheFiles = [
{ url: 'icons/fruit.png', revision: null},
{ url: 'manifest.json', revision: '000000001'},
{
url: './index.html',
revision: '00000001' // 加revision,版本改了以後,sw.js 在 application 上會更新
}
];
workbox.precaching.precacheAndRoute(cacheFiles);
可是,這樣不是很累,而且,很有可能會弄錯嗎? WorkBox CLI就是來幫忙解決這個問題。
剛開始喔,未完待續....
參考資料
存取手機上的資源
- WHAT WEB CAN DO TODAY?: Can I rely on the Web Platform features to build my app? An overview of the device integration HTML5 APIs
安裝
- [教學] 將網頁變成Progressive Web Application (PWA) ,漸進式的網頁應用程式 (2019)
- Day 14 - 30 天 Progressive Web App 學習筆記 - 實作 Registering the Service Worker
manifest.json
- PWA學習筆記-4:manifest.json
- Day 21 - 30 天 Progressive Web App 學習筆記 - To-Do List 實作 PWA - Web App Manifest File
- Add a web app manifest
- Web App Manifest