本文将会大体介绍PWA系列技术,但是并不会深入全面到各个细节, 更为深入的内容可以参看 https://developer.mozilla.org/zh-CN/docs/Web/Progressive_web_apps
PWA是什么
PWA的优势
PWA核心技术包括:(一系列web技术)
web app mainfest
{
// 应用的名称, 用户安装横幅提示的名字,和启动画面中的文字
"name": "HackerWeb",
// 应用的短名字, 用于主屏幕显示
"short_name": "HackerWeb",
// 指定用户从设备启动应用程序时加载的URL, 可以是相对路径或者绝对路径
"start_url": ".",
// fullscreen 全屏显示, 无状态栏
// standalone 看起来像一个独立应用,拥有独立图标和窗口, 可以包含其他UI元素,如状态栏
// minimal-ui 看起来像独立应用程序,但是会有浏览器地址栏
// browser 在传统的浏览器标签或新窗口打开
"display": "standalone",
// 用户指定启动时的过度背景颜色#fff / red
"background_color": "#fff",
// 指定应用程序的主题颜色, 应用顶部那一块的颜色
"theme_color": "#ff0000",
// 定义应用程序上下文的导航范围
// 限制了manifest可以查看的网页范围,如果用户在范围之外浏览应用程序,这返回到正常网页
"scope": "/myapp/",
// 提供有关Web应用程序的一般描述
"description": "A simply readable Hacker News app.",
// 应用程序的图标,规格有: 48x48, 72x72, 96x96, 144x144, 168x168, 192x192
"icons": [{
"src": "imgs/favicon.png",
"sizes": "64x64",
"type": "image/png"
}],
// 定义所有Web应用程序顶级的默认方向, 有如下值:
// any,natural, landscape,landscape-primary,
// landscape-secondary, portrait,portrait-primary, portrait-secondary
"orientation": "portrait-primary",
// 代表可由底层平台安装或可访问的本机应用程序
"related_applications": [{
// Google Play Store 可以找到应用程序的平台。
"platform": "play",
// 可以找到应用程序的URL。
"url": "https://play.google.com/store/apps/details?id=com.example.app1",
// 用于表示指定平台上的应用程序的ID。
"id": "com.example.app1"
}, {
"platform": "itunes", // itunes
"url": "https://itunes.apple.com/app/example-app1/id123456789"
}]
}
service worker
pwa的核心技术,可以让网页在离线或者网速比较慢的情况下依然可以访问
是一个独立的worker线程,独立于当前网页进程,是一种特殊的 web worker
优化网页性能的一个有效方法。除去,cdn , css sprite, 文件合并压缩,缓存,异步加载, 图片懒加载预加载,code spliting等
特点:
如何使用:
service worker 用法和特征:
Service Worker 使用步骤:
service worker 的生命周期
fetch api
dom中能使用XMLHttpRequest 和 fetch api, 而service work中只能使用fetch api
Fetch 提供了对 Request 和 Response (以及其他与网络请求有关的)对象的通用定义。将可以使用在多场景中,如:service workers、Cache API、又或者是其他处理请求和响应的方式。
XMLHttpRequest 只能使用在DOM中
fetch api 基于promise实现的
fetch非常强大,不仅可以获取接口,还可以获取json, 图片等资源
response 是一个二进制数据流,需要使用**res. json()**方法可以转换成json数据, 前提是读取的是一个json数据。
fetch(URL, config) 用于发送http请求,返回一个包含响应结果的promise对象
config 常见参数:
(1) body: 用于设置请求体
(2) headers: 用于设置请求头
(3) method: 设置请求方式
Cache Storage api
用来做缓存,service worker 能够实现离线访问主要就是依赖于缓存策略,缓存资源
Cache api
cache Storage 接口表示Cache对象的存储,可以配合service work 来实现资源的缓存, 可以再控制台Applicant – > Cache --> Cache Storage 中查看缓存的数据
(2) cache api 类似于数据库的操作
caches.open(cacheName).then(function (cache ) { }) ,
用于打开缓存,返回一个匹配cacheName的cache对象(Promise类型), 类似于链接数据库。
cacheName 为缓存的名字
cache类似于已经连上数据库了,可接下来就可以基于cache来做一些操作了.
caches.keys() 返回一个Promise对象,包括所有的缓存的key(数据库名)。 类似于可以获取到所有的数据库
caches.delete(key) 返回一个Promise对象, 根据key删除对应的缓存(数据库)。
相当于根据一个key,删除掉一个已有数据库
Cahce对象常用方法:
常见的缓存策略: 确定哪些资源需要走缓存,那些资源需要走网络
缓存处理时必须要注意避免跨域资源缓存
由于更新机制的问题,如果Service Work 缓存了错误的结果,将会对web应用造成灾难性的后果。我们必须要小心翼翼的检查网络返回是否准确。一种常见的做法是只缓存满足如下条件的结果:
Notification: 用来向用户配置和显示桌面通知的,在网页下面也可以实现消息通知
基本使用:
优势
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="manifest" href="./manifest.json">
<title>Document</title>
</head>
<body>
<div id="app">
<img src="./imgs/favicon.png" alt="">
pwa practice
<ul>
<li v-for="item in list" :key="item">
{{ item }}
</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
new Vue({
el: '#app',
data() {
return {
a: 1,
list: []
}
},
mounted() {
let page = parseInt(Math.random() * 10)
fetch('https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&page_limit=50&page_start=' + page).then((res) => {
res.json().then(respond => {
this.list = respond.subjects || []
})
})
}
})
</script>
<script>
window.onload = function () {
if ('serviceWorker' in navigator) {
// 注册Service Worker
navigator.serviceWorker.register('./sw.js').then(() => {
console.log('register success')
}).catch(() => {
console.log('register fail')
})
}
// 判断是否有权限弹出消息通知
if (Notification.permission !== 'granted') {
// 请求获取消息弹出的权限
Notification.requestPermission()
}
// 检测是否在线
if (!navigator.onLine) {
new Notification("离线", {
body: "您已断网,现在访问的是离线内容"
})
}
// 检测联网,实时监控,一旦联网将会触发
window.addEventListener('online', function () {
new Notification("上线", {
body: "您已联网,请冲浪"
})
})
// 检测离线,实时监控,一旦离线将会触发
window.addEventListener('offline', function () {
new Notification("离线", {
body: "您已离线,请检查网络"
})
})
}
</script>
</body>
</html>
sw.js
const CACHE_NAME = "cache_v5"
const urls = [
"/",
"./index.html",
"./manifest.json",
"./sw.js",
"./imgs/favicon.png",
"/j/search_subjects"
]
self.addEventListener('install', async e => {
// 打开缓存
const cache = await caches.open(CACHE_NAME)
// 添加所有需要缓存的资源到Cache Storage缓存
await cache.addAll(urls)
// 跳过等待,触发activate周期
await self.skipWaiting()
})
self.addEventListener('activate', async e => {
// 获取所有的cache缓存版本
const keys = await caches.keys()
// 删除以前的所有缓存
keys.forEach(async (item, key) => {
if (item !== CACHE_NAME) {
await caches.delete(item)
}
})
// 缓存本次生效
e.waitUntil(self.clients.claim())
})
self.addEventListener('fetch', async e => {
const req = e.request
const url = new URL(req.url)
// 同源策略,只有请求的资源和当前运行环境的域名相同时,才缓存
if (url.origin !== self.orgin) {
return false
}
// 网络优先,事件响应获取到的资源
e.respondWith(await networkFirst(req))
})
// 缓存优先
async function cacheFirst (req) {
const cache = await caches.open(CACHE_NAME)
const cached = cache.match(req)
if (cached) {
return cached
} else {
const fresh = fetch(req)
cache.put(req, fresh.clone())
return fresh
}
}
// 网络优先
async function networkFirst (req) {
const cache = await caches.open(CACHE_NAME)
try {
const fresh = await fetch(req)
cache.put(req, fresh.clone())
return fresh
} catch (e) {
const cached = await cache.match(req)
if (cached) {
return cached
}
}
}