前言
- pwa这门技术不火的原因还是很明显的,但是这个技术好处也是非常明显的,知道的人越多,这个技术越繁荣,就有更多人“挨饿”。由于这技术主要还是谷歌推,国内访问不了谷歌,这就导致国内学习pwa技术有点麻烦。资料也比较少,恰好翻到些文章,总结学习下。
安装
-
首先最应该说的就是pwa的应用安装了。先看下显示条件:这个应用安装可以理解为网页的快捷入口,不过更像是应用而不是网页。原生应用就不用说了。
-
显示应用安装横幅的条件:
浏览器在 PWA 站点满足以下条件时会自动显示横幅:
站点部署 manifest.json,该文件需配置如下属性:
short_name (用于主屏幕显示)
name (用于安装横幅显示)
icons (其中必须包含一个 mime 类型为 image/png 的图标声明)
start_url (应用启动地址)
display (必须为 standalone 或 fullscreen)
站点注册 Service Worker。
站点支持 HTTPS 访问。
站点在同一浏览器中被访问至少两次,两次访问间隔至少为 5 分钟。 -
显示原生应用安装横幅的条件
浏览器在 PWA 站点满足以下条件时会自动显示横幅:
站点部署 manifest.json,该文件需配置如下属性:
short_name (用于主屏幕显示)
name (用于安装横幅显示)
icons (其中必须包含一个 192x192 且 mime 类型为 image/png 的图标声明)
包含原生应用相关信息的 related_applications 对象
站点注册 Service Worker。
站点支持 HTTPS 访问。
站点在同一浏览器中被访问至少两次,两次访问间隔至少为 2 天。
其中 related_applications 的定义如下:
related_applications: Array. 关联应用列表
AppInfo 的属性值包括:
platform: {string} 应用平台
id: {string} 应用id
代码
- 主要还是依靠监听
beforeinstallprompt
事件。事件mdn。 - 在页面出现后,会触发这个事件,这个事件的event对象正常来说会显示横幅,当然也有支持不好的就不会显示横幅,需要主动触发,于是可以使用preventDefault先禁掉再通过prompt方法主动触发。
- 我研究了下,这个event事件的prompt方法只能放到监听用户动作里,不然会报错,虽然报错也能工作。这个beforeinstallprompt事件,在用户拒绝安装后必须设置为空。等待下次触发。
- 一般点击按钮安装应用:
<button hidden id="installBtn">安装应用</button>
<script>
window.addEventListener('load', function () {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./sw.js');
}
let appPromptEvent = null;
const installBtn = document.getElementById('installBtn');
window.addEventListener('beforeinstallprompt', function (event) {
console.log('触发事件:beforeinstallprompt');
event.preventDefault();
appPromptEvent = event;
installBtn.hidden = false;
return false;
});
installBtn.addEventListener('click', function () {
if (appPromptEvent !== null) {
console.log(appPromptEvent)
appPromptEvent.prompt();
appPromptEvent.userChoice.then(function (result) {
if (result.outcome === 'accepted') {
console.log('同意安装应用');
} else {
console.log('不同意安装应用');
}
appPromptEvent = null;
});
}
});
window.addEventListener('appinstalled', function () {
console.log('应用已安装');
installBtn.hidden = true;
});
});
</script>
- 这个后面的userChoice就有点像小程序里写的选择了。
- 另外manifest.json忘说了。html里加入Link
<link rel="manifest" href="./manifest.json">
manifest.json
{
"short_name": "短名称",
"name": "这是一个完整名称",
"icon": [
{
"src": "icon.png",
"type": "image/png",
"sizes": "48x48"
}
],
"start_url": "index.html"
}
- 更多配置说明移步mdn manifest.json配置。
Service Worker
- 提到pwa,不得不学习Service Worker,相比于pwa,service Worker基本支持的很完善。
注册
- 使用Service Worker得注册:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('./sw.js', { scope: './' }).then(function(registration) {
// do domething...
}).catch(function(err) {
// do domething...
});
});
}
- register 方法中的 scope 参数指定了 Service Worker 可接收 fetch 事件的作用域,比如 scope 的值为 /mobile,那么 Service Worker 便只能接收 path 以 /mobile 开头的 fetch 事件,默认值为 sw.js 所在路径。
安装
- 注册完需要进行安装:
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open('sw-cache').then(function(cache) {
return cache.addAll([
'/',
'/index.html',
'/main.css',
'/main.js',
'/image.jpg'
]);
})
);
});
- Service Worker进入安装需要有2个条件:
页面中尚未安装 Service Worker。
Service Worker 已安装,并且从服务器获取的 sw.js 文件与本地版本存在差异。 - 这个caches是api。caches的mdn。下面会有基本使用。
- waituntil是需要个promise作为参数,会等promise resolve继续下一阶段。
等待
- 安装成功后,如果已经存在一个版本的 Service Worker 且有页面正在使用该版本,新版 Service Worker 便会进入等待状态,当 Service Worker 处于该阶段时,由于它必须等正在运行旧版本 Service Worker 的页面全部关闭后才会获得控制权,因此如果我们需要所有页面能够及时得到更新,可在 install 中通过 self.skipWaiting 来强制跳过该阶段。
self.addEventListener('install', function(event) {
self.skipWaiting();
//……
});
激活
- 当满足以下任意条件,即可进入该状态:
1 self.skipWaiting 方法被调用。
2 安装完成后,不存在旧版本的 Service Worker 或无页面使用此版本。
3 等待状态下正在运行旧版本 Service Worker 的页面被全部关闭(页面刷新或切换无法使 Service Worker 从等待进入激活状态,这是由于当页面刷新或切换时,浏览器需要等到新页面渲染完成之后才会销毁旧页面,即新旧两个页面存在共同的交叉时间)。 - 进入该事件,会触发activate,常用来对缓存维护。
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.filter(function(cacheName) {
return cacheName != 'sw-cache';
}).map(function(cacheName) {
return caches.delete(cacheName);
})
);
})
);
});
- 当 Service Worker 被首次注册时,已打开的页面只有在刷新后才会接受 Service Worker 的控制,如果想要 Service Worker 在激活后尽快掌握这些页面的控制权,可在 activate 中调用 self.clients.claim 方法来实现:
self.addEventListener('activate', function(event) {
self.clients.claim()
//……
});
已激活
- 到这个时候,就可以通过监听 fetch、push、sync、message 等事件来为应用提供丰富的离线处理能力。
注销
- 一般用不上,浏览器application里面有个serviceWorker可以点击unregister注销。
- 或者代码:
const serviceWorker = navigator.serviceWorker;
if (typeof serviceWorker.getRegistrations === 'function') {
serviceWorker.getRegistrations().then(function(registrations) {
registrations.forEach(function(registration) {
registration.unregister();
});
});
} else if (typeof serviceWorker.getRegistration === 'function') {
serviceWorker.getRegistration().then(function(registration) {
registration.unregister();
})
}
- 一个有s一个没s,复数循环注销,单数直接注销。
状态监听
- 注册成功后,我们可通过回调中的 registration 参数来获取以下状态的 ServiceWorker 实例。
- 实例可以监听各种状态,比如updatefound等。mdn可监听状态。
navigator.serviceWorker.register('./sw.js').then(function(registration) {
registration.addEventListener('updatefound', function() {
});
});
- 取得控制权的监听:
navigator.serviceWorker.addEventListener('controllerchange', function() {
});
IndexedDB
- 由于serviceWorker只能获取indexDB或者caches内容,所以这2个基本使用也是要学习的。有网址的使用caches,其他资源使用IndexedDB。
- 打开数据库,第二个参数是版本号。IndexedDBmdn。
const openRequest = window.indexedDB.open('TodoList', 1);
- indexedDB操作都是异步调用的,所以创建数据库是通过
onupgradeneeded
获得event
回调来做:
openRequest.onupgradeneeded = function(event) {
const db = event.target.result;
const todosStore = db.createObjectStore('todos', { keyPath: 'id', autoIncrement : true });
todosStore.createIndex('status', 'status');
};
- 这个创建索引有三个参数,创建索引mdn。
- 数据操作:
openRequest.onsuccess = function(event) {
const db = event.target.result;
const transaction = db.transaction(['todos'], 'readonly');
const todosStore = transaction.objectStore('todos');
// ……
};
- 这个是基于事务来操作,三种事务模式:readonly,readwrite,和versionchange。
var transaction = db.transaction(["customers"], "readwrite");
db.transaction("customers").objectStore("customers").get("444-44-4444").onsuccess = function(event) {
alert("Name for SSN 444-44-4444 is " + event.target.result.name);
};
var request = db.transaction(["customers"], "readwrite")
.objectStore("customers")
.delete("444-44-4444");
request.onsuccess = function(event) {
// 删除成功!
};
- 还可以使用游标进行操作,具体看mdnindexedDB mdn
Caches
-
先通过CacheStorage获取对象,然后通过Cache接口进行操作。
-
CacheStorage 接口
主要接口为:
open:获取指定名称的 Cache 对象。
keys:获取 CacheStorage 所有 Cache 对象中的 Response 条目键值列表。
has:判断是否存在指定名称的 Cache 对象。
delete:删除指定名称的 Cache 对象。
match:获取指定请求所对应的 Response 条目(如匹配到多个,则返回第一个)。 -
Cache 接口
match:获取指定请求所对应的 Response 条目(如匹配到多个,则返回第一个)。
matchAll:与 match 的唯一差别是该接口返回所有匹配项。
add:获取指定 URL 的资源,将返回的 Response 添加到 Cache 对象中。
addAll: 与 add 的唯一差别是该接口可以获取多个 URL 的资源,并将其依次添加到 Cache 对象中。
put:将指定 Request 的 Response 添加到 Cache 对象中。
delete: 删除指定 Request 的 Response 条目。
keys:获取指定 Request 的 Response 条目键值列表。