认识PWA
虽然PWA在国内被小程序打压的没什么存在感,但是,本着技多不压身的原则了解学习一下PWA。
- 认识:PWA渐进式Web应用【Progressive Web Apps】, Google推广
- 特点:无需安装、更轻量、不占用大量空间,只需要一款支持 PWA 应用的浏览器,具备跨平台使用的特性
- 优势:离线缓存功能,消息推送功能,URL分享功能,快捷添加功能,搜索引擎发现,不限屏幕大小
准备
npm install --global http-server // 安装http-server
// 准备一个文件夹 【 PWA 】
const file = {
html: 'index.html' // 创建一个 Html 文件
css: 'main.css' // 创建一个 css 文件
json: 'manifest.json' // 创建一个 json 文件
js: 'sw.js' // 创建一个 js 文件
image: 'img.png/jpg' // 创建图片 大小120*120 { 也可以创建图片文件夹 img }
}
html文件
<!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>Hello PWA</title>
<link rel="stylesheet" href="main.css">
<!-- 引入 manifest.json -->
<link rel="manifest" href="manifest.json">
</head>
<body>
<h3>Hello PWA</h3>
<button id="notifications">点击提示通知</button>
</body>
<script>
// 添加注册 Service Worker 注册完成后,sw.js 文件会自动下载,然后安装,最后激活。
if (navigator.serviceWorker != null) {
navigator.serviceWorker.register('sw.js').then(function (registartion) {
console.log('支持sw:', registartion.scope)
})
}
// 通知
// 授权的结果有三种:default:用户没有做出选择的时候,授权结果会返回defalut granted:用户已授权 denied:用户拒绝授权
var button = document.getElementById("notifications");
button.addEventListener('click', function (e) {
Notification.requestPermission().then(function (result) {
console.log('[Service Worker] 用户授权', result)
if (result === 'granted') {
randomNotification();
}
});
});
let settime = null
function randomNotification() {
var randomItem = Math.floor(Math.random() * 2);
var title = '搜索';
var content = `创建通知测试.`;
var image = `img/image${randomItem + 1}.png`;
var options = {
body: content,
icon: image
}
var notification = new Notification(title, options);
}
</script>
</html>
Service Workers 实现了如何正确缓存网站资源并使其在用户设备离线时可用,还提供处理通知,在单独的线程上执行繁重的计算等。它运行在页面的 JavaScript 主线程独立的线程上,并且对 DOM 结构没有任何的访问权限。API 是非阻塞的,并且可以在不同的上下文之间发送和接收信息。
Service workers 可以控制网络请求,修改网络请求,返回缓存的自定义响应,或合成响应。
<!--特点:->
在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和拦截作用域范围内所有页面的 HTTP 请求。
网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)。
控制打开的作用域范围下所有的页面请求。
单独的作用域范围,单独的运行环境和执行线程。
不能操作页面 DOM,可以通过事件机制来处理。
事件驱动型服务线程
manifest.json 文件
{
"name": "一个PWA示例", // 网站应用的全名
"short_name": "PWA示例", // 用户主屏幕上的应用名字
"start_url": "/index.html", // 启动应用的网址
"display": "standalone", // 应用的显示方式;可以是全屏,独立,最小UI或者浏览器
"background_color": "#fff", // 背景色,用于安装程序时和显示启动画面时
"theme_color": "#3eaf7c", // 浏览器的地址栏等 UI 元素的颜色
"description": "Progressive Web App Demo", // 应用的描述
"icons": [ // 主屏幕的图标
{ // 至少包含一个正方形
"src": "/img1.jpg",
"sizes": "120x120",
"type": "image/png"
},
{ // 提供不小于144像素的正方形
"src": "/img2.jpg",
"sizes": "521x521",
"type": "image/png"
}
]
}
sw.js文件
let cacheName = 'pwa-sample3';
let cacheList = [
'/',
'index.html',
'main.css',
'img/image1.png',
'img/image2.png',
'img/image3.png'
];
// 借助 SW 注册完成安装 SW 时,抓取资源写入缓存中。使用方法 self.skipWaiting( ) ,为了在页面更新的过程当中,新的 SW 脚本能够立刻激活和生效。
self.addEventListener('install', function(e) {
console.log('[Service Worker] Install');
e.waitUntil(
// 开启一个cache,得到一个cache对象 cache对象就可以存储资源
caches.open(cacheName).then(function(cache) {
console.log('[Service Worker] Caching all: app shell and content');
return cache.addAll(cacheList);
}).then(() => self.skipWaiting())
);
});
// 更新静态资源,缓存的资源会跟随着版本的更新会过期的,所以会根据缓存的字符串名称清除旧缓存。在新安装的 SW 中通过调用 self.clients.claim( ) 取得页面的控制权,这样之后打开页面都会使用版本更新的缓存。而旧的 Service Worker 仍然会正确的运行,直到没有任何页面使用到它为止,这时候新的 Service Worker 将会被激活,然后接管所有的页面。
self.addEventListener('activate', async e => {
const keys = await caches.keys()
keys.forEach(key => {
console.log('[Service Worker] activate',key);
if (key !== cacheName) {
caches.delete(key)
}
})
await self.clients.claim()
})
// 处理动态资源
// fetch事件会在请求发送时触发
// 处理动态缓存,我们监听 fetch 事件,在 caches 中去 match 事件的 request ,如果 response 不为空的话就返回 response ,最后返回 fetch 请求,在 fetch 事件中我们可以手动生成 response 返回给页面。
self.addEventListener('fetch', e => {
e.respondWith(networkFirst(e.request))
})
// 网络优先
async function networkFirst(req) {
try {
// 先从网络读取最新的资源
const fresh = await fetch(req)
console.log('[Service Worker] 网络读取');
return fresh
} catch (error) {
// 去缓存中读取
const cache = await caches.open(cacheName)
const cached = await cache.match(req)
console.log('[Service Worker] 离线读取');
return cached
}
}
// 缓存优先
async function cacheFirst (req) {
// 先从缓存拿,拿不到再从网络中拿
const cache = await caches.open(cacheName)
const cached = await cache.match(req)
if (cached) {
return cached
} else {
const fresh = await fetch(req)
// 网络中拿到的数组保存到缓存中
cache.put(req, fresh.clone())
return fresh
}
}
使用 http-server -c-1启动项目