1.PWA是什么
PWA 是 Google 于 2016 年提出的概念,于 2017 年正式落地,于 2018 年迎来重大突破,全球顶级的浏览器厂商,Google、Microsoft、Apple 已经全数宣布支持 PWA 技术。
PWA 全称为 Progressive Web App,中文译为渐进式 Web APP,其目的是通过各种 Web 技术实现与原生 App 相近的用户体验。
纵观现有 Web 应用与原生应用的对比差距,如离线缓存、沉浸式体验等等,可以通过已经实现的 Web 技术去弥补这些差距,最终达到与原生应用相近的用户体验效果。
2.为什么要使用PWA
安全可靠 使用 Service Work 技术实现即时下载,当用户打开应用后,页面资源的加载不再完全依赖于网络,而是使用 Service Work 缓存离线包存在本地,确保为用户提供即时可靠的体验。
访问更快 首屏可以部署在服务端,节省网页请求时间,加载速度更快,拥有更平滑的动态效果和快速的页面响应。
响应式界面 支持各种类型的终端和屏幕。
沉浸式体验 在支持 PWA 的浏览器和手机应用上可以直接将 Web 应用添加到用户的主屏幕上,无需从应用商店下载安装。从主屏幕上打开应用之后,提供沉浸式的全屏幕体验。
3.PWA具体实现
3.1.手机应用配置(Web App Manifest)
可以通过 manifest.json 文件配置,使得可以直接添加到手机的桌面上。
具体实现:
在index.html文件中只需要配置 <link rel="manifest" href="manifest.json">,并且对 manifest.json文件进行配置就可以实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manifest / webWork</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
</body>
</html>
{
"name": "HackerWeb_HackerWeb_HackerWeb_HackerWeb",
"short_name": "HackerWeb",
"start_url": ".",
"icons": [{
"src": "images/wechatAva.jpg",
"sizes": "144x144",
"type": "image/jpg"
}],
"display": "standalone",
"background_color": "#FF6700",
"theme_color": "skyblue"
}
3.2.离线加载与缓存(Service Worker+Cache API )
可以通过 Service Worker + HTTPS +Cache Api + indexedDB 等一系列 Web 技术实现离线加载和缓存。
3.2.1 了解Web Workers
MDN链接:developer.mozilla.org/en-US/docs/…
我们都知道JS的运行的单线程的,而Web Workers就是为了对JS单线程模式进行优化而孕育而生的,Web Workers可以其运行可以看成为异步执行一样,都是不会阻塞JS的主线程执行的。
例子:
比如:要知道1-1亿的数字加起来的总和是多少,我们在JS主线程中执行这个循环,肯定会阻塞主进程,导致后面的代码无法执行,
并且还可能失败,这时候我们可以使用 Web Workers 的运行机制进行大数据的计算,既不会阻塞主进程,也可以把结果计算出来
具体实现:
我们可以直接通过 Worker 类创建 webWorker 的实例,new Worker('work.js')里面参数传入的是一个文件的地址,该文件就是实现webWorker所要实现的功能
通过监听self.postMessage事件把计算好的结果返回到主线程中,
主线程通过监听message事件把结果拿到
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manifest / webWork</title>
<link rel="manifest" href="manifest.json">
</head>
<body>
<h1></h1>
<script>
console.log("start")
let webWorker = new Worker('work.js')
webWorker.addEventListener('message', (e)=>{
console.log(e.data)
})
webWorker.postMessage("主线程给webworked发信息")
console.log("end")
</script>
</body>
</html>
let total = 0;
for (let i = 0; i <= 100; i++) {
total += i;
}
self.addEventListener("message", (e)=>{
console.log(e.data)
})
self.postMessage({
total
})
3.2.2 了解service Worker
MDN链接:developer.mozilla.org/en-US/docs/…
服务工作者本质上充当位于web应用程序、浏览器和网络(如果可用)之间的代理服务器。除其他外,它们旨在创建有效的离线体验、拦截网络请求并根据网络是否可用采取适当行动,以及更新服务器上的资产。它们还将允许访问推送通知和后台同步API
具体使用:
service Worker要保证在页面完全load完之后,才可以使用,并且在里面只能使用fetch进行网络请求,可以通过navigator.serviceWorker.register来注册service Worker,我们注册完了之后,对应传入参数的文件中写入数据,并且可以在 application 中的 Service Workers 看到我们注册的service Worker
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>serviceWorker</title>
</head>
<body>
<h1>Server Worker</h1>
<script>
window.addEventListener('load',async ()=>{
// 能力检查,老版本可能没有
if( 'serviceWorker' in navigator ){
// 和webWorker一样, 返回值是promise对象
try {
const serviceWorker = await navigator.serviceWorker.register('./serviceWorker.js')
console.log("注册成功", serviceWorker)
} catch (e) {
console.log("注册失败", e)
}
}
})
</script>
</body>
</html>
/**
* serviceWorker生命周期
* @param { function } install 注册成功时触发,主要用于缓存资源
* @param { function } activate 会在激活 serviceWorker 的时候触发,主要是用于删除旧资源
* @param { function } fetch 在发送请求的时候触发, 主要用于操作缓存 或 读取网络资源
*
* caches Api
* @param { function(cachesName:string) : promise{} } caches.open 返回值是promise,用于打开缓存
* @param { function(): promise{} } caches.keys 返回值是promise,包括所有缓存的key
* @param { function(key:promise<string>): promise{} } caches.delete 根据key删除对应的缓存
*
* caches methods
* @param { function(req:Request,res:Response) : promise{} } caches.put 把请求当成key,并且把 对应的响应 存储起来
* @param { function(url:sting) : promise{} } caches.add 根据url发起请求,并且把 响应结果 存储起来
* @param { function(url:sting) : promise{} } caches.addAll 根据url发起请求,并且把 所有 存储起来
* @param { function(req:Request) : promise{} } caches.match 获取req对应的res
*/
/**
* 首次会触发
* 改动当前文件会触发
* 主要用于缓存资源
*/
const CACHES_NAME = "CHCHES_v1";
self.addEventListener("install", async (e) => {
// 开启caches
const cache = await caches.open(CACHES_NAME);
// 利用 caches.addAll 进行资源存储 后面给路径就行了
await cache.addAll([
"./serviceWorker.html",
"./manifest.json",
"./data.json",
"./images/wechatAva.jpg",
]);
// skipWaiting 的作用就是 让 install 直接进入 activate 的状态
// 因为 skipWaiting 返回的一个promise 可能会导致进程走到了 activate 生命周期中,而 skipWaiting 的异步操作还没处理完
// 为了解决这个异步问题,可以使用 waitUntil 函数进行操作,让他异步执行完了才到 activate 生命周期中
// e.waitUntil(self.skipWaiting());
// 利用 await 特性就不需要使用 waitUntil 方法了
await self.skipWaiting();
});
/**
* 首次会触发
* 改动当前文件不会触发,因为已经存在了 serviceWorker 了,那相当于处在等待状态,如果 serviceWorker 停了,才会再次触发 activate 事件
* 主要是用于删除旧资源
*/
self.addEventListener("activate", async (e) => {
// 通过 caches.keys() 把缓存中的 key 拿到
// key 就是 我们定义的名字相当于 localStorage 中的 localStorage.set(key,value) 的key
const keys = await caches.keys();
keys.forEach((key) => {
// 对 key 进行判断, key 不相等,说明新资源来了
if (key !== CACHES_NAME) {
// 删除资源
caches.delete(key);
}
});
// 立即获取控制权
// waitUntil 为了抓到 fetch 中的网络请求
await self.clients.claim();
});
/**
* 主要用于操作缓存 或 读取网络资源
*/
self.addEventListener("fetch", (e) => {
// 抓取网络请求
const req = e.request;
// 给浏览器响应
e.respondWith( netWorkFirst(req) )
});
// 网络优先
async function netWorkFirst(req) {
// 从网络读取资源
try {
// 有网络的情况下得到
const fresh = await fetch(req);
return fresh
} catch (error) {
// 无网络
// 从缓存中读取
const cache = await caches.open(CACHES_NAME)
const cached = await cache.match(req)
return cached
}
}
// 缓存优先
function cachesFirst() {}
3.2.2 了解Cache API
主要是对缓存进行操作的
serviceWorker 配合 caches storage
caches Api
caches.open(cachesName) 返回值是promise,用于打开缓存
caches.keys() 返回值是promise,包括所有缓存的key
caches.delete(key) 根据key删除对应的缓存
caches methods
caches.put(req,res) 把请求当成key,并且把 对应的响应 存储起来
caches.add(url) 根据url发起请求,并且把 响应结果 存储起来
caches.addAll(url) 根据url发起请求,并且把 所有 存储起来
caches.match(req) 获取req对应的res
3.消息推动与通知(Push&Notification )
实现实时的消息推送与通知,主要是对通知进行操作的
Notification Api
Notification.permission 是否进行授权
三种状态
Notification.permission 默认是询问
'default'
Notification.permission 允许
'granted'
Notification.permission 拒绝
'denied'
Notification.requestPermission() 请求用户授权
new Notification("title",{body:'content'}) 消息通知弹窗