摘要:基本上是使用現代技術構建的網站但是體驗上卻像一個移動,在年,谷歌工程師和創造了。此后谷歌就一直致力于讓能給用戶像原生一般的體驗。檢查谷歌瀏覽器的和現在重載你的并且打開,到選項去查看面板,確保這個選項是勾選的。
Web開發多年來有了顯著的發展。它允許開發人員部署網站或Web應用程序并在數分鐘內為全球數百萬人服務。只需一個瀏覽器,用戶可以輸入URL就可以訪問Web應用程序了。隨著 Progressive Web Apps的到來,開發人員可以使用現代Web技術向用戶提供很好體驗的應用程序。在這篇文章中,你會學習到如何構建一個離線的漸進式 web 應用程序(Progressive Web Apps),下面就叫 PWA 啦。
首先介紹一下什么是 PWA雖然很多文章已經說過了,已經理解的童鞋請跳過這個步驟。PWA基本上是使用現代Web技術構建的網站,但是體驗上卻像一個移動 app,在2015年,谷歌工程師Alex Russell和Frances Berriman創造了“ Progressive Web Apps”。此后谷歌就一直致力于讓 PWA能給用戶像原生 app一般的體驗。一個典型的PWA的應該是這樣的:
1、開始在Web瀏覽器的地址欄中訪問
2、有顯示添加到設備的主屏幕選項
3、逐步開始展示諸如離線使用、推送通知和后臺同步等應用程序屬性。
到目前為止,移動APP可以做很多Web App不能真正做的事情。PWA,一個web app嘗試去做移動app已經很長時間了。它結合最好的 web技術的和最好的app技術,可以在慢速網絡連接上快速加載,離線瀏覽,推送消息,并在Web屏幕上加載Web應用程序入口。
到現在,安卓上最心版本的Chrome瀏覽器,支持在桌面上快速打開你的 web app 了,這一切都感謝 PWA,如下圖
WPA 的特性這類新的Web應用程序具有定義它們存在的特性。沒有很難的知識點,下面這些都是 PWA具有的一些特性:
Responsive(響應式):Ui可以適配多個終端,桌面,手機,平板等等
App-like(像app):當與一個PWA交互時,它應該感覺像一個原生的應用程序。
Connectivity Independent(連接獨立): 它能離線瀏覽(通過 Service Workers) 或者在低網速上也能瀏覽
Re-engageable(重新連接):通過推送通知等功能,用戶應該能夠持續地參與和重用應用程序。
Installable(安裝):用戶可以添加在主屏幕并且從那里啟動它他們就可以重新應用程序了。
Discoverable(可發現的):用戶通過搜索應被識別發現的
Fresh(最新數據):當用戶連接到網絡時,應該能夠在應用程序中提供新的內容。
Safe(安全):該通過HTTPS提供服務,防止內容篡改和中間人攻擊。
Progressive(漸進式):不管瀏覽器的選擇如何,它應該對每個用戶都有效。
Linkable(可鏈接):通過URL分享給別人。
PWA的一些生產用例Flipkart Lite: FlipKart 是印度最大的電商之一。如下圖
AliExpress:AliExpress 是個非常受歡迎的全球在線零售市場,通過實踐 PWA之后,訪問量和瀏覽數都成倍增加這里不做詳細講解。如下圖
Service Workers是可編程代理的一個script腳本運行在你瀏覽器的后臺,它具有攔截、處理HTTP請求的能力,也能以各種方式對他們作出響應。它有響應網絡請求、推送通知、連接更改等等的功能。Service Workers不能訪問DOM,但它可以利用獲取和緩存API。您可以Service Workers緩存所有靜態資源,這將自動減少網絡請求并提高性能。 Service worker 可以顯示一個 app應用殼,通知用戶,他們與互聯網斷開了并且提供一個頁面供用戶在離線時進行交互、瀏覽。
一個Service worker文件,例如sw.js需要像這樣放置在根目錄中:
在你的PWA中開始service workers,如果你的應用程序的JS文件是app.js,你需要去注冊service workers在你的app.js文件,下面的代碼就是注冊你的service workers。
if ("serviceWorker" in navigator) { navigator.serviceWorker .register("./sw.js") .then(function() { console.log("Service Worker Registered"); }); }
上面的代碼檢查瀏覽器是否支持service workers。如果支持,就開始注冊service workers,一旦service workers注冊了,我們就開始體驗用戶第一次訪問頁面時service workers的生命周期。
service workers的生命周期Install:在用戶第一次訪問頁面時觸發安裝事件。在這個階段中,service workers被安裝在瀏覽器中。在安裝過程中,您可以將Web app的所有靜態資產緩存下來。如下面代碼所示:
// Install Service Worker self.addEventListener("install", function(event) { console.log("Service Worker: Installing...."); event.waitUntil( // Open the Cache caches.open(cacheName).then(function(cache) { console.log("Service Worker: Caching App Shell at the moment......"); // Add Files to the Cache return cache.addAll(filesToCache); }) ); });
filesToCache變量代表的所有文件要緩存數組cachename指給緩存存儲的名稱
Activate:當service worker啟動時,此事件將被觸發。
// Fired when the Service Worker starts up self.addEventListener("activate", function(event) { console.log("Service Worker: Activating...."); event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all(cacheNames.map(function(key) { if( key !== cacheName) { console.log("Service Worker: Removing Old Cache", key); return caches.delete(key); } })); }) ); return self.clients.claim(); });
在這里, service worker每當應用殼(app shell)文件更改時都更新其緩存。
Fetch:此事件作用與于從服務器端的數據緩存到 app殼中。caches.match()解析了觸發Web請求的事件,并檢查它是否在緩存中獲得數據。然后,它要么響應于緩存版本的數據,要么用fetch從網絡中獲取數據。用 e.respondWith()方法來響應返回到Web頁面。
self.addEventListener("fetch", function(event) { console.log("Service Worker: Fetch", event.request.url); console.log("Url", event.request.url); event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ); });
在寫代碼的時候。我們需要注意一下Chrome, Opera、Firefox是支持service workers 的,但是Safari 和 Edge 還沒有兼容到service workers
Service Worker Specification 和 primer 都是關于Service Workers的一下非常有用的學習資料。
Application Shell(應用殼)在文章的前面,我曾多次提到過應用殼app shell。應用程序殼是用最小的HTML,CSS和JavaScript驅動應用程序的用戶界面。一個PWA確保應用殼被緩存,以對應app多次快速訪問和快速加載。
下面我們將逐步寫一個 PWA例子我們將構建一個簡單的PWA。這個app只跟蹤來自特定開源項目的最新提交。作為一個 PWA,他應該具具有:
離線應用,用戶應該能夠在沒有Internet連接的情況下查看最新提交。
應用程序應立即加載重復訪問
打開按鈕通知按鈕后,用戶將獲得對最新提交到開放源代碼項目的通知。
可安裝(添加到主屏幕)
有一個Web應用程序清單
光說不做瞎扯淡,開始吧!創建index.html和latest.html文件在你的代碼文件夾里面。
index.html
Commits PWA PWA - Home Stay Up to Date with R-I-L
latest.html
Commits PWA PWA - Commits Latest Commits!
創建一個 css 文件夾,并且在這個文件下載創建一個style.css文件(可以點擊這里查看),創建一個js文件夾,并在這個文件下創建app.js, menu.js, offline.js, latest.js,toast.js。
js/offline.js
(function () { "use strict"; var header = document.querySelector("header"); var menuHeader = document.querySelector(".menu__header"); //After DOM Loaded document.addEventListener("DOMContentLoaded", function(event) { //On initial load to check connectivity if (!navigator.onLine) { updateNetworkStatus(); } window.addEventListener("online", updateNetworkStatus, false); window.addEventListener("offline", updateNetworkStatus, false); }); //To update network status function updateNetworkStatus() { if (navigator.onLine) { header.classList.remove("app__offline"); menuHeader.style.background = "#1E88E5"; } else { toast("You are now offline.."); header.classList.add("app__offline"); menuHeader.style.background = "#9E9E9E"; } } })();
上面的代碼幫助用戶在 ui視覺上區分離線和在線狀態。
js/menu.js
(function () { "use strict"; var menuIconElement = document.querySelector(".header__icon"); var menuElement = document.querySelector(".menu"); var menuOverlayElement = document.querySelector(".menu__overlay"); //Menu click event menuIconElement.addEventListener("click", showMenu, false); menuOverlayElement.addEventListener("click", hideMenu, false); menuElement.addEventListener("transitionend", onTransitionEnd, false); //To show menu function showMenu() { menuElement.style.transform = "translateX(0)"; menuElement.classList.add("menu--show"); menuOverlayElement.classList.add("menu__overlay--show"); } //To hide menu function hideMenu() { menuElement.style.transform = "translateX(-110%)"; menuElement.classList.remove("menu--show"); menuOverlayElement.classList.remove("menu__overlay--show"); menuElement.addEventListener("transitionend", onTransitionEnd, false); } var touchStartPoint, touchMovePoint; /*Swipe from edge to open menu*/ //`TouchStart` event to find where user start the touch document.body.addEventListener("touchstart", function(event) { touchStartPoint = event.changedTouches[0].pageX; touchMovePoint = touchStartPoint; }, false); //`TouchMove` event to determine user touch movement document.body.addEventListener("touchmove", function(event) { touchMovePoint = event.touches[0].pageX; if (touchStartPoint < 10 && touchMovePoint > 30) { menuElement.style.transform = "translateX(0)"; } }, false); function onTransitionEnd() { if (touchStartPoint < 10) { menuElement.style.transform = "translateX(0)"; menuOverlayElement.classList.add("menu__overlay--show"); menuElement.removeEventListener("transitionend", onTransitionEnd, false); } } })();
上面的代碼作用于菜單省略號按鈕的動畫。
js/toast.js
(function (exports) { "use strict"; var toastContainer = document.querySelector(".toast__container"); //To show notification function toast(msg, options) { if (!msg) return; options = options || 3000; var toastMsg = document.createElement("div"); toastMsg.className = "toast__msg"; toastMsg.textContent = msg; toastContainer.appendChild(toastMsg); //Show toast for 3secs and hide it setTimeout(function () { toastMsg.classList.add("toast__msg--hide"); }, options); //Remove the element after hiding toastMsg.addEventListener("transitionend", function (event) { event.target.parentNode.removeChild(event.target); }); } exports.toast = toast; //Make this method available in global })(typeof window === "undefined" ? module.exports : window);
上面的代碼是是一個 tost 的提示信息框
latest.js 和 app.js 現在還是空的。
現在,使用本地服務器啟動你的應用程序,例如 http-server模塊可以幫組你啟動本地服務,您的Web應用程序應該如下所示:
Side menu
Index Page
Latest Page
Application Shell
您的應用殼也在上面突出顯示。現在尚未實現加載動態內容,下一步,我們需要從 Github"s API獲取最新的提交。
獲取動態內容打開js/latest.js增加下面的代碼
(function() { "use strict"; var app = { spinner: document.querySelector(".loader") }; var container = document.querySelector(".container"); // Get Commit Data from Github API function fetchCommits() { var url = "https://api.github.com/repos/unicodeveloper/resources-i-like/commits"; fetch(url) .then(function(fetchResponse){ return fetchResponse.json(); }) .then(function(response) { var commitData = { "first": { message: response[0].commit.message, author: response[0].commit.author.name, time: response[0].commit.author.date, link: response[0].html_url }, "second": { message: response[1].commit.message, author: response[1].commit.author.name, time: response[1].commit.author.date, link: response[1].html_url }, "third": { message: response[2].commit.message, author: response[2].commit.author.name, time: response[2].commit.author.date, link: response[2].html_url }, "fourth": { message: response[3].commit.message, author: response[3].commit.author.name, time: response[3].commit.author.date, link: response[3].html_url }, "fifth": { message: response[4].commit.message, author: response[4].commit.author.name, time: response[4].commit.author.date, link: response[4].html_url } }; container.querySelector(".first").innerHTML = "Message: " + response[0].commit.message + "
" + "Author: " + response[0].commit.author.name + "
" + "Time committed: " + (new Date(response[0].commit.author.date)).toUTCString() + "
" + "" + "Click me to see more!" + "
"; container.querySelector(".second").innerHTML = "Message: " + response[1].commit.message + "
" + "Author: " + response[1].commit.author.name + "
" + "Time committed: " + (new Date(response[1].commit.author.date)).toUTCString() + "
" + "" + "Click me to see more!" + "
"; container.querySelector(".third").innerHTML = "Message: " + response[2].commit.message + "
" + "Author: " + response[2].commit.author.name + "
" + "Time committed: " + (new Date(response[2].commit.author.date)).toUTCString() + "
" + "" + "Click me to see more!" + "
"; container.querySelector(".fourth").innerHTML = "Message: " + response[3].commit.message + "
" + "Author: " + response[3].commit.author.name + "
" + "Time committed: " + (new Date(response[3].commit.author.date)).toUTCString() + "
" + "" + "Click me to see more!" + "
"; container.querySelector(".fifth").innerHTML = "Message: " + response[4].commit.message + "
" + "Author: " + response[4].commit.author.name + "
" + "Time committed: " + (new Date(response[4].commit.author.date)).toUTCString() + "
" + "" + "Click me to see more!" + "
"; app.spinner.setAttribute("hidden", true); //hide spinner }) .catch(function (error) { console.error(error); }); }; fetchCommits(); })();
此外在你的latest.html引入latest.js
增加 loading 在你的latest.html
....
在latest.js你可以觀察到,我們從GitHub的API獲取到數據并將其附加到DOM中來,現在獲取后的頁面長這樣子了。
Latest.html 頁面
為了確保我們的app快速加載和離線工作,我們需要緩存app shell 通過 service worker
首先,在根目錄中創建一個 service worker文件。它的名字sw.js
第二,打開app.js文件和添加這段代碼來實現service worker注冊使用
app.js
if ("serviceWorker" in navigator) { navigator.serviceWorker .register("./sw.js") .then(function() { console.log("Service Worker Registered"); }); }
打開sw.js文件并添加這段代碼
sw.js
var cacheName = "pwa-commits-v3"; var filesToCache = [ "./", "./css/style.css", "./images/books.png", "./images/Home.svg", "./images/ic_refresh_white_24px.svg", "./images/profile.png", "./images/push-off.png", "./images/push-on.png", "./js/app.js", "./js/menu.js", "./js/offline.js", "./js/toast.js" ]; // Install Service Worker self.addEventListener("install", function(event) { console.log("Service Worker: Installing...."); event.waitUntil( // Open the Cache caches.open(cacheName).then(function(cache) { console.log("Service Worker: Caching App Shell at the moment......"); // Add Files to the Cache return cache.addAll(filesToCache); }) ); }); // Fired when the Service Worker starts up self.addEventListener("activate", function(event) { console.log("Service Worker: Activating...."); event.waitUntil( caches.keys().then(function(cacheNames) { return Promise.all(cacheNames.map(function(key) { if( key !== cacheName) { console.log("Service Worker: Removing Old Cache", key); return caches.delete(key); } })); }) ); return self.clients.claim(); }); self.addEventListener("fetch", function(event) { console.log("Service Worker: Fetch", event.request.url); console.log("Url", event.request.url); event.respondWith( caches.match(event.request).then(function(response) { return response || fetch(event.request); }) ); });
就像我在這篇文章的前面部分所解釋的,我們所有的靜態資源都放到filesToCache數組里面,當service worker被安裝時,它在瀏覽器中打開緩存并且數組里面所有文件都被緩存到pwa-commits-v3這個緩存里面。一旦 service worker已經安裝,install事件就會觸發。此階段確保您的service worker在任何應用殼文件更改時更新其緩存。fetch事件階段應用殼從緩存中獲取數據。
注意:為了更容易和更好的方式預先高速緩存你的資源。檢查谷歌瀏覽器的 sw-toolbox 和 sw-precachelibraries
現在重載你的 web app 并且打開 DevTools,到Application 選項去查看Service Worker面板,確保Update on reload這個選項是勾選的。如下圖
現在,重新加載Web頁面并檢查它。有離線瀏覽么?
Index Page Offline
Yaaay!!! 首頁終于離線也是可以瀏覽了,那么latest頁面是不是顯示最新的提交呢?
Latest Page Offline
Yaaay!!!latest已是離線服務。但是等一下!數據在哪里?提交在哪里?哎呀!我們的 app試圖請求Github API當用戶與Internet斷開連接時,它失敗了。
Data Fetch Failure, Chrome DevTools
我們該怎么辦?處理這個場景有不同的方法。其中一個選項是告訴service worker提供離線頁面。另一種選擇是在第一次加載時緩存提交數據,在后續請求中加載本地保存的數據,然后在用戶連接時獲取最新的數據。提交的數據可以存儲在IndexedDB或local Storage。
好了,我們現在就此結束!
附上:
原文地址: https://auth0.com/blog/introduction-to-progressive-apps-part-one/
項目代碼地址:https://github.com/unicodeveloper/pwa-commits
博客文章:https://blog.naice.me/articles/5a31d20a78c3ad318b837f59
如果有那個地方翻譯出錯或者失誤,請各位大神不吝賜教,小弟感激不盡
期待下一篇: 介紹一下漸進式 Web App(即時加載) - Part 2
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/112881.html
摘要:在上一篇,介紹一下漸進式離線的文章中,我們討論了典型的應該是什么樣子的并且同時也介紹了。暴露了一個異步,以避免阻塞的加載。但一些研究表明,在某些情況下,它是阻塞的。打開并且添加如下代碼清除緩存并重新加載。 在上一篇,介紹一下漸進式 Web App(離線) - Part 1的文章中,我們討論了典型的pwa應該是什么樣子的并且同時也介紹了 server worker。到目前為止,我們已經緩...
摘要:在上一篇,介紹一下漸進式離線的文章中,我們討論了典型的應該是什么樣子的并且同時也介紹了。暴露了一個異步,以避免阻塞的加載。但一些研究表明,在某些情況下,它是阻塞的。打開并且添加如下代碼清除緩存并重新加載。 在上一篇,介紹一下漸進式 Web App(離線) - Part 1的文章中,我們討論了典型的pwa應該是什么樣子的并且同時也介紹了 server worker。到目前為止,我們已經緩...
摘要:在上一篇,介紹一下漸進式離線的文章中,我們討論了典型的應該是什么樣子的并且同時也介紹了。暴露了一個異步,以避免阻塞的加載。但一些研究表明,在某些情況下,它是阻塞的。打開并且添加如下代碼清除緩存并重新加載。 在上一篇,介紹一下漸進式 Web App(離線) - Part 1的文章中,我們討論了典型的pwa應該是什么樣子的并且同時也介紹了 server worker。到目前為止,我們已經緩...
摘要:基本上是使用現代技術構建的網站但是體驗上卻像一個移動,在年,谷歌工程師和創造了。此后谷歌就一直致力于讓能給用戶像原生一般的體驗。檢查谷歌瀏覽器的和現在重載你的并且打開,到選項去查看面板,確保這個選項是勾選的。 Web開發多年來有了顯著的發展。它允許開發人員部署網站或Web應用程序并在數分鐘內為全球數百萬人服務。只需一個瀏覽器,用戶可以輸入URL就可以訪問Web應用程序了。隨著 Prog...
摘要:基本上是使用現代技術構建的網站但是體驗上卻像一個移動,在年,谷歌工程師和創造了。此后谷歌就一直致力于讓能給用戶像原生一般的體驗。檢查谷歌瀏覽器的和現在重載你的并且打開,到選項去查看面板,確保這個選項是勾選的。 Web開發多年來有了顯著的發展。它允許開發人員部署網站或Web應用程序并在數分鐘內為全球數百萬人服務。只需一個瀏覽器,用戶可以輸入URL就可以訪問Web應用程序了。隨著 Prog...
閱讀 3249·2023-04-25 22:47
閱讀 3764·2021-10-11 10:59
閱讀 2299·2021-09-07 10:12
閱讀 4242·2021-08-11 11:15
閱讀 3431·2019-08-30 13:15
閱讀 1749·2019-08-30 13:00
閱讀 968·2019-08-29 14:02
閱讀 1679·2019-08-26 13:57