Description
Progressive Web Apps
- 渐进增强 - 能够让每位用户使用,无论用户使用什么浏览器
- 不依赖网络连接 - 通过ServiceWorker可以在离线或网速极差的环境下工作
- 类原生应用 - 有像原生应用般的交互和导航给用户原生应用般的体验,因为它是建立在 app shell 模型上的。
- 持续更新 - 受益于ServiceWorker的更新进程,应用能够始终保持更新
- 安全 - 通过HTTPS来提供服务
- 可发现 - 得益于 manifest 元数据和 ServiceWorker 的登记,让搜索引擎可以找到web应用
- 再次访问 - 通过消息推送等特性让用户再次访问变得容易
- 可安装 - 允许用户保留对他们有用的应用在主屏幕上,不需要通过应用商店
- 可连接性 - 通过URL可以轻松分享应用,不用复杂的安装即可运行
浏览器支持
ServiceWorker 是什么
service worker 是独立于当前页面的一段运行在浏览器后台进程里的脚本。
service worker不需要用户打开 web 页面,也不需要其他交互,异步地运行在一个完全独立的上下文环境,不会对主线程造成阻塞。
service worker提供一种渐进增强的特性,使用特性检测来渐渐增强,不会在老旧的不支持 service workers 的浏览器中产生影响。可以通过service workers解决让应用程序能够离线工作,让存储数据在离线时使用的问题。
ServiceWorker 特点
- 网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)
- ServiceWorker是一个可编程的网络代理,允许开发者控制页面上处理的网络请求
- 单独的作用域范围,单独的运行环境和执行线程。运行在它们自己的完全独立异步的�全局上下文中
- 不能操作页面DOM,但可以通过 postMessage 方法与Web页面通信,让页面操作DOM
- 浏览器可能随时回收ServiceWorker,在不被使用的时候他会自己终止,而当他再次被用到的时候,会被重新激活
- ServiceWorker的生命周期是由事件驱动的而不是通过 Client
ServiceWorker 生命周期
install -> installed -> activating -> activate -> activated -> redundant
install
// 用于标注创建的缓存,也可以根据它来建立版本规范
const CACHE_NAME = "cache_v1.0.0";
// 列举要默认缓存的静态资源,一般用于离线使用
const urlsToCache = [
'/offline.html',
'/offline.png'
];
// self 为当前 scope 内的上下文
self.addEventListener('install', event => {
// event.waitUtil 用于在安装成功之前执行一些预装逻辑
// 但是建议只做一些轻量级和非常重要资源的缓存,减少安装失败的概率
// 安装成功后 ServiceWorker 状态会从 installing 变为 installed
event.waitUntil(
// 使用 cache API 打开指定的 cache 文件
caches.open(CACHE_NAME).then(cache => {
console.log(cache);
// 添加要缓存的资源列表
return cache.addAll(urlsToCache);
})
);
});
只有
urlsToCache
中的文件全部安装成功,ServiceWorker才会认为安装成功。否则会认为安装失败,失败则进入redundant�(�废弃)状态。所以应当尽量少地缓存资源(一般认为离线时需要,联网时不会访问到的内容),以提升成功率。
ServiceWorker 更新
有更新的ServiceWorker完成安装后会进入waiting
状态。直到所有已打开的页面都关闭,旧的 Service Worker 自动停止,新的 Service Worker 才会在接下来打开的页面里生效。
如果希望在有系版本时所有的页面都得到更新,可以在install
事件中执行skipWaiting
方法跳过waiting状态,然后回会直接进入activate
阶段。接着在activate
事件发生时,通过执行self.clients.claim()
方法,更新所有客户端上的ServiceWorker
// 安装阶段跳过等待,直接进入 active
self.addEventListener('install', function(event) {
event.waitUntil(self.skipWaiting());
});
self.addEventListener('activate', evnet => event.waitUntil(
Promise.all([
// 更新客户端
clients.claim(),
// 清理旧版本
caches.keys().then(cacheList => Promise.all(
cacheList.map(cacheName => {
if (cacheName !== CACHE_NAME) {
caches.delete(cacheName);
}
})
))
])
));
添加到主屏幕(显示应用安装横幅)
显示应用安装横幅的条件:
- 站点部署manifest.json,该文件需配置如下属性
short_name
用于主屏幕显示name
用于安装横幅显示icons
其中必须包含一个 mime 类型为 image/png 的图标声明start_url
应用启动地址display
必须为standalone�或fullscreen
- 站点注册ServiceWorker
- 站点支持HTTPS访问
- 站点在同一浏览器中被访问至少两次,两次间隔至少5分钟
manifest.json的配置
{
"short_name": "短名称",
"name": "这是一个完整名称",
"icon": [
{
"src": "../../images/logo-144x144.png",
"type": "image/png",
"sizes": "144x144"
}
],
"start_url": "./index.html"
}
应用横幅安装事件
beforeinstallprompt
返回一个名为userChoice
的Promise对象,并在用户对横幅�进行操作时进行解析,Promise会返回属性outcome
,该属性的值为dismissed
或accepted
,如果用户将网页添加到主屏幕则返回accepted
。
window.addEventListener('beforeinstallprompt', function(e){
e.userChoice.then(function(choiceResult){
if(choiceResult.outcome==='dismissed'){
console.log('用户取消�了安装')
}else{
console.log('用户安装了应用')
}
})
})
取消或延迟安装横幅的触发事件
网站虽然不能主动触发安装横幅的显示事件,但是当该事件被浏览器触发之后,可以对其进行取消或者延迟。
通过阻止 beforeinstallprompt
事件的默认行为,即可取消横幅弹出:
window.addEventListener('beforeinstallprompt', function (e) {
e.preventDefault();
return false;
});
beforeinstallprompt
事件返回一个名为prompt
的方法,通过执行该方法可以触发安装横幅的显示。
var deferPrompt = null;
window.addEventListener('beforeinstallprompt',function(e){
deferPrompt = e;
e.preventDefault();
return false;
})
button.addEventListener('click', function(){
if(deferPrompt!=null){
deferPrompt.prompt();
deferPrompt.userChoice.then(function(choiceResult){
console.log(choiceResult.outcome);
})
deferPrompt = null;
}
})