摘要:基本上是使用現(xiàn)代技術(shù)構(gòu)建的網(wǎng)站但是體驗(yàn)上卻像一個(gè)移動(dòng),在年,谷歌工程師和創(chuàng)造了。此后谷歌就一直致力于讓能給用戶(hù)像原生一般的體驗(yàn)。檢查谷歌瀏覽器的和現(xiàn)在重載你的并且打開(kāi),到選項(xiàng)去查看面板,確保這個(gè)選項(xiàng)是勾選的。
Web開(kāi)發(fā)多年來(lái)有了顯著的發(fā)展。它允許開(kāi)發(fā)人員部署網(wǎng)站或Web應(yīng)用程序并在數(shù)分鐘內(nèi)為全球數(shù)百萬(wàn)人服務(wù)。只需一個(gè)瀏覽器,用戶(hù)可以輸入U(xiǎn)RL就可以訪問(wèn)Web應(yīng)用程序了。隨著 Progressive Web Apps的到來(lái),開(kāi)發(fā)人員可以使用現(xiàn)代Web技術(shù)向用戶(hù)提供很好體驗(yàn)的應(yīng)用程序。在這篇文章中,你會(huì)學(xué)習(xí)到如何構(gòu)建一個(gè)離線的漸進(jìn)式 web 應(yīng)用程序(Progressive Web Apps),下面就叫 PWA 啦。
首先介紹一下什么是 PWA雖然很多文章已經(jīng)說(shuō)過(guò)了,已經(jīng)理解的童鞋請(qǐng)?zhí)^(guò)這個(gè)步驟。PWA基本上是使用現(xiàn)代Web技術(shù)構(gòu)建的網(wǎng)站,但是體驗(yàn)上卻像一個(gè)移動(dòng) app,在2015年,谷歌工程師Alex Russell和Frances Berriman創(chuàng)造了“ Progressive Web Apps”。此后谷歌就一直致力于讓 PWA能給用戶(hù)像原生 app一般的體驗(yàn)。一個(gè)典型的PWA的應(yīng)該是這樣的:
1、開(kāi)始在Web瀏覽器的地址欄中訪問(wèn)
2、有顯示添加到設(shè)備的主屏幕選項(xiàng)
3、逐步開(kāi)始展示諸如離線使用、推送通知和后臺(tái)同步等應(yīng)用程序?qū)傩浴?/p>
到目前為止,移動(dòng)APP可以做很多Web App不能真正做的事情。PWA,一個(gè)web app嘗試去做移動(dòng)app已經(jīng)很長(zhǎng)時(shí)間了。它結(jié)合最好的 web技術(shù)的和最好的app技術(shù),可以在慢速網(wǎng)絡(luò)連接上快速加載,離線瀏覽,推送消息,并在Web屏幕上加載Web應(yīng)用程序入口。
到現(xiàn)在,安卓上最心版本的Chrome瀏覽器,支持在桌面上快速打開(kāi)你的 web app 了,這一切都感謝 PWA,如下圖
WPA 的特性這類(lèi)新的Web應(yīng)用程序具有定義它們存在的特性。沒(méi)有很難的知識(shí)點(diǎn),下面這些都是 PWA具有的一些特性:
Responsive(響應(yīng)式):Ui可以適配多個(gè)終端,桌面,手機(jī),平板等等
App-like(像app):當(dāng)與一個(gè)PWA交互時(shí),它應(yīng)該感覺(jué)像一個(gè)原生的應(yīng)用程序。
Connectivity Independent(連接獨(dú)立): 它能離線瀏覽(通過(guò) Service Workers) 或者在低網(wǎng)速上也能瀏覽
Re-engageable(重新連接):通過(guò)推送通知等功能,用戶(hù)應(yīng)該能夠持續(xù)地參與和重用應(yīng)用程序。
Installable(安裝):用戶(hù)可以添加在主屏幕并且從那里啟動(dòng)它他們就可以重新應(yīng)用程序了。
Discoverable(可發(fā)現(xiàn)的):用戶(hù)通過(guò)搜索應(yīng)被識(shí)別發(fā)現(xiàn)的
Fresh(最新數(shù)據(jù)):當(dāng)用戶(hù)連接到網(wǎng)絡(luò)時(shí),應(yīng)該能夠在應(yīng)用程序中提供新的內(nèi)容。
Safe(安全):該通過(guò)HTTPS提供服務(wù),防止內(nèi)容篡改和中間人攻擊。
Progressive(漸進(jìn)式):不管瀏覽器的選擇如何,它應(yīng)該對(duì)每個(gè)用戶(hù)都有效。
Linkable(可鏈接):通過(guò)URL分享給別人。
PWA的一些生產(chǎn)用例Flipkart Lite: FlipKart 是印度最大的電商之一。如下圖
AliExpress:AliExpress 是個(gè)非常受歡迎的全球在線零售市場(chǎng),通過(guò)實(shí)踐 PWA之后,訪問(wèn)量和瀏覽數(shù)都成倍增加這里不做詳細(xì)講解。如下圖
Service Workers是可編程代理的一個(gè)script腳本運(yùn)行在你瀏覽器的后臺(tái),它具有攔截、處理HTTP請(qǐng)求的能力,也能以各種方式對(duì)他們作出響應(yīng)。它有響應(yīng)網(wǎng)絡(luò)請(qǐng)求、推送通知、連接更改等等的功能。Service Workers不能訪問(wèn)DOM,但它可以利用獲取和緩存API。您可以Service Workers緩存所有靜態(tài)資源,這將自動(dòng)減少網(wǎng)絡(luò)請(qǐng)求并提高性能。 Service worker 可以顯示一個(gè) app應(yīng)用殼,通知用戶(hù),他們與互聯(lián)網(wǎng)斷開(kāi)了并且提供一個(gè)頁(yè)面供用戶(hù)在離線時(shí)進(jìn)行交互、瀏覽。
一個(gè)Service worker文件,例如sw.js需要像這樣放置在根目錄中:
在你的PWA中開(kāi)始service workers,如果你的應(yīng)用程序的JS文件是app.js,你需要去注冊(cè)service workers在你的app.js文件,下面的代碼就是注冊(cè)你的service workers。
if ("serviceWorker" in navigator) { navigator.serviceWorker .register("./sw.js") .then(function() { console.log("Service Worker Registered"); }); }
上面的代碼檢查瀏覽器是否支持service workers。如果支持,就開(kāi)始注冊(cè)service workers,一旦service workers注冊(cè)了,我們就開(kāi)始體驗(yàn)用戶(hù)第一次訪問(wèn)頁(yè)面時(shí)service workers的生命周期。
service workers的生命周期Install:在用戶(hù)第一次訪問(wèn)頁(yè)面時(shí)觸發(fā)安裝事件。在這個(gè)階段中,service workers被安裝在瀏覽器中。在安裝過(guò)程中,您可以將Web app的所有靜態(tài)資產(chǎn)緩存下來(lái)。如下面代碼所示:
// 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變量代表的所有文件要緩存數(shù)組cachename指給緩存存儲(chǔ)的名稱(chēng)
Activate:當(dāng)service worker啟動(dòng)時(shí),此事件將被觸發(fā)。
// 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每當(dāng)應(yīng)用殼(app shell)文件更改時(shí)都更新其緩存。
Fetch:此事件作用與于從服務(wù)器端的數(shù)據(jù)緩存到 app殼中。caches.match()解析了觸發(fā)Web請(qǐng)求的事件,并檢查它是否在緩存中獲得數(shù)據(jù)。然后,它要么響應(yīng)于緩存版本的數(shù)據(jù),要么用fetch從網(wǎng)絡(luò)中獲取數(shù)據(jù)。用 e.respondWith()方法來(lái)響應(yīng)返回到Web頁(yè)面。
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); }) ); });
在寫(xiě)代碼的時(shí)候。我們需要注意一下Chrome, Opera、Firefox是支持service workers 的,但是Safari 和 Edge 還沒(méi)有兼容到service workers
Service Worker Specification 和 primer 都是關(guān)于Service Workers的一下非常有用的學(xué)習(xí)資料。
Application Shell(應(yīng)用殼)在文章的前面,我曾多次提到過(guò)應(yīng)用殼app shell。應(yīng)用程序殼是用最小的HTML,CSS和JavaScript驅(qū)動(dòng)應(yīng)用程序的用戶(hù)界面。一個(gè)PWA確保應(yīng)用殼被緩存,以對(duì)應(yīng)app多次快速訪問(wèn)和快速加載。
下面我們將逐步寫(xiě)一個(gè) PWA例子我們將構(gòu)建一個(gè)簡(jiǎn)單的PWA。這個(gè)app只跟蹤來(lái)自特定開(kāi)源項(xiàng)目的最新提交。作為一個(gè) PWA,他應(yīng)該具具有:
離線應(yīng)用,用戶(hù)應(yīng)該能夠在沒(méi)有Internet連接的情況下查看最新提交。
應(yīng)用程序應(yīng)立即加載重復(fù)訪問(wèn)
打開(kāi)按鈕通知按鈕后,用戶(hù)將獲得對(duì)最新提交到開(kāi)放源代碼項(xiàng)目的通知。
可安裝(添加到主屏幕)
有一個(gè)Web應(yīng)用程序清單
光說(shuō)不做瞎扯淡,開(kāi)始吧!創(chuàng)建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!
創(chuàng)建一個(gè) css 文件夾,并且在這個(gè)文件下載創(chuàng)建一個(gè)style.css文件(可以點(diǎn)擊這里查看),創(chuàng)建一個(gè)js文件夾,并在這個(gè)文件下創(chuàng)建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"; } } })();
上面的代碼幫助用戶(hù)在 ui視覺(jué)上區(qū)分離線和在線狀態(tài)。
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); } } })();
上面的代碼作用于菜單省略號(hào)按鈕的動(dòng)畫(huà)。
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);
上面的代碼是是一個(gè) tost 的提示信息框
latest.js 和 app.js 現(xiàn)在還是空的。
現(xiàn)在,使用本地服務(wù)器啟動(dòng)你的應(yīng)用程序,例如 http-server模塊可以幫組你啟動(dòng)本地服務(wù),您的Web應(yīng)用程序應(yīng)該如下所示:
Side menu
Index Page
Latest Page
Application Shell
您的應(yīng)用殼也在上面突出顯示。現(xiàn)在尚未實(shí)現(xiàn)加載動(dòng)態(tài)內(nèi)容,下一步,我們需要從 Github"s API獲取最新的提交。
獲取動(dòng)態(tài)內(nèi)容打開(kāi)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獲取到數(shù)據(jù)并將其附加到DOM中來(lái),現(xiàn)在獲取后的頁(yè)面長(zhǎng)這樣子了。
Latest.html 頁(yè)面
為了確保我們的app快速加載和離線工作,我們需要緩存app shell 通過(guò) service worker
首先,在根目錄中創(chuàng)建一個(gè) service worker文件。它的名字sw.js
第二,打開(kāi)app.js文件和添加這段代碼來(lái)實(shí)現(xiàn)service worker注冊(cè)使用
app.js
if ("serviceWorker" in navigator) { navigator.serviceWorker .register("./sw.js") .then(function() { console.log("Service Worker Registered"); }); }
打開(kāi)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); }) ); });
就像我在這篇文章的前面部分所解釋的,我們所有的靜態(tài)資源都放到filesToCache數(shù)組里面,當(dāng)service worker被安裝時(shí),它在瀏覽器中打開(kāi)緩存并且數(shù)組里面所有文件都被緩存到pwa-commits-v3這個(gè)緩存里面。一旦 service worker已經(jīng)安裝,install事件就會(huì)觸發(fā)。此階段確保您的service worker在任何應(yīng)用殼文件更改時(shí)更新其緩存。fetch事件階段應(yīng)用殼從緩存中獲取數(shù)據(jù)。
注意:為了更容易和更好的方式預(yù)先高速緩存你的資源。檢查谷歌瀏覽器的 sw-toolbox 和 sw-precachelibraries
現(xiàn)在重載你的 web app 并且打開(kāi) DevTools,到Application 選項(xiàng)去查看Service Worker面板,確保Update on reload這個(gè)選項(xiàng)是勾選的。如下圖
現(xiàn)在,重新加載Web頁(yè)面并檢查它。有離線瀏覽么?
Index Page Offline
Yaaay!!! 首頁(yè)終于離線也是可以瀏覽了,那么latest頁(yè)面是不是顯示最新的提交呢?
Latest Page Offline
Yaaay!!!latest已是離線服務(wù)。但是等一下!數(shù)據(jù)在哪里?提交在哪里?哎呀!我們的 app試圖請(qǐng)求Github API當(dāng)用戶(hù)與Internet斷開(kāi)連接時(shí),它失敗了。
Data Fetch Failure, Chrome DevTools
我們?cè)撛趺崔k?處理這個(gè)場(chǎng)景有不同的方法。其中一個(gè)選項(xiàng)是告訴service worker提供離線頁(yè)面。另一種選擇是在第一次加載時(shí)緩存提交數(shù)據(jù),在后續(xù)請(qǐng)求中加載本地保存的數(shù)據(jù),然后在用戶(hù)連接時(shí)獲取最新的數(shù)據(jù)。提交的數(shù)據(jù)可以存儲(chǔ)在IndexedDB或local Storage。
好了,我們現(xiàn)在就此結(jié)束!
附上:
原文地址: https://auth0.com/blog/introduction-to-progressive-apps-part-one/
項(xiàng)目代碼地址:https://github.com/unicodeveloper/pwa-commits
博客文章:https://blog.naice.me/articles/5a31d20a78c3ad318b837f59
如果有那個(gè)地方翻譯出錯(cuò)或者失誤,請(qǐng)各位大神不吝賜教,小弟感激不盡
期待下一篇: 介紹一下漸進(jìn)式 Web App(即時(shí)加載) - Part 2
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/54498.html
摘要:在上一篇,介紹一下漸進(jìn)式離線的文章中,我們討論了典型的應(yīng)該是什么樣子的并且同時(shí)也介紹了。暴露了一個(gè)異步,以避免阻塞的加載。但一些研究表明,在某些情況下,它是阻塞的。打開(kāi)并且添加如下代碼清除緩存并重新加載。 在上一篇,介紹一下漸進(jìn)式 Web App(離線) - Part 1的文章中,我們討論了典型的pwa應(yīng)該是什么樣子的并且同時(shí)也介紹了 server worker。到目前為止,我們已經(jīng)緩...
摘要:在上一篇,介紹一下漸進(jìn)式離線的文章中,我們討論了典型的應(yīng)該是什么樣子的并且同時(shí)也介紹了。暴露了一個(gè)異步,以避免阻塞的加載。但一些研究表明,在某些情況下,它是阻塞的。打開(kāi)并且添加如下代碼清除緩存并重新加載。 在上一篇,介紹一下漸進(jìn)式 Web App(離線) - Part 1的文章中,我們討論了典型的pwa應(yīng)該是什么樣子的并且同時(shí)也介紹了 server worker。到目前為止,我們已經(jīng)緩...
摘要:在上一篇,介紹一下漸進(jìn)式離線的文章中,我們討論了典型的應(yīng)該是什么樣子的并且同時(shí)也介紹了。暴露了一個(gè)異步,以避免阻塞的加載。但一些研究表明,在某些情況下,它是阻塞的。打開(kāi)并且添加如下代碼清除緩存并重新加載。 在上一篇,介紹一下漸進(jìn)式 Web App(離線) - Part 1的文章中,我們討論了典型的pwa應(yīng)該是什么樣子的并且同時(shí)也介紹了 server worker。到目前為止,我們已經(jīng)緩...
摘要:基本上是使用現(xiàn)代技術(shù)構(gòu)建的網(wǎng)站但是體驗(yàn)上卻像一個(gè)移動(dòng),在年,谷歌工程師和創(chuàng)造了。此后谷歌就一直致力于讓能給用戶(hù)像原生一般的體驗(yàn)。檢查谷歌瀏覽器的和現(xiàn)在重載你的并且打開(kāi),到選項(xiàng)去查看面板,確保這個(gè)選項(xiàng)是勾選的。 Web開(kāi)發(fā)多年來(lái)有了顯著的發(fā)展。它允許開(kāi)發(fā)人員部署網(wǎng)站或Web應(yīng)用程序并在數(shù)分鐘內(nèi)為全球數(shù)百萬(wàn)人服務(wù)。只需一個(gè)瀏覽器,用戶(hù)可以輸入U(xiǎn)RL就可以訪問(wèn)Web應(yīng)用程序了。隨著 Prog...
摘要:基本上是使用現(xiàn)代技術(shù)構(gòu)建的網(wǎng)站但是體驗(yàn)上卻像一個(gè)移動(dòng),在年,谷歌工程師和創(chuàng)造了。此后谷歌就一直致力于讓能給用戶(hù)像原生一般的體驗(yàn)。檢查谷歌瀏覽器的和現(xiàn)在重載你的并且打開(kāi),到選項(xiàng)去查看面板,確保這個(gè)選項(xiàng)是勾選的。 Web開(kāi)發(fā)多年來(lái)有了顯著的發(fā)展。它允許開(kāi)發(fā)人員部署網(wǎng)站或Web應(yīng)用程序并在數(shù)分鐘內(nèi)為全球數(shù)百萬(wàn)人服務(wù)。只需一個(gè)瀏覽器,用戶(hù)可以輸入U(xiǎn)RL就可以訪問(wèn)Web應(yīng)用程序了。隨著 Prog...
閱讀 1600·2021-09-30 09:47
閱讀 3590·2021-09-22 15:05
閱讀 2835·2021-08-30 09:44
閱讀 3620·2019-08-30 15:55
閱讀 1370·2019-08-30 13:08
閱讀 1325·2019-08-29 16:40
閱讀 549·2019-08-29 12:45
閱讀 1385·2019-08-29 11:25