2020/03/27
2020/03/28 (補充內容)
2020/03/30 (更新內容)
Progressive Web Application (PWA)就是要讓網頁的行為,在手機上執行時,能夠跟手機App一致,在介面呈現的部分,已經可以透過RWD來處理,還有一些行為,如:安裝、可離線操作、存取手機上的資源,跟手機是不一樣的,透過PWA,可以進行安裝的動作,也可以進行離線操作,目前,已經可以透過Browser存取一些手機上的資源,未來會慢慢的能夠跟App可取得的權限幾乎一樣。
體驗一下PWA,直接用手機打開網頁:
現在可以利用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的解釋
再開啟網頁,記得在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:
// installself.addEventListener('install', event => {    console.log('installing…');});// activateself.addEventListener('activate', event => {    console.log('now ready to handle fetches!');});// fetchself.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 😬`);}// installself.addEventListener('install', event => {    console.log('installing…');});// activateself.addEventListener('activate', event => {    console.log('now ready to handle fetches!');});// fetchself.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就是來幫忙解決這個問題。
剛開始喔,未完待續....