1、原生app优缺点
a、体验好、下载到手机上入口方便
b、开发成本高(ios和安卓)
c、软件上线需要审核
d、版本更新需要将新版本上传到不同的应用商店
e、使用前需下载
2、web网页优缺点
a、开发成本低、网站更新时上传最新的资源到服务器即可、手机自带浏览器打开即可
b、体验比原生app差
c、入口不便捷
d、无网无相应,不具备离线能力
e、无app的消息推送
3、PWA是什么?
PWA是一个新的前端技术,全称:Progressive Web App,这是一个渐进式的网页应用程序。结合了一系列现代web技术,在网页应用中实现和原生应用相近的用户体验。
PWA的三个关键词:
Reliable(可靠的):当用户从手机屏幕启动时,无需考虑网络状态,可以立刻加载出PWA
Fast(快速的):加载速度快
Engaging(可参与的):PWA可以添加在用户的主屏幕上,无需从应用商店里下载,他们通过网络应用程序Manifest file提供类似于APP的使用体验(android上可设置全屏显示,由于Safari支持度的问题ios不可以),可以进行“推送通知”
小小总结:
a、解决的问题:
1>可添加至主屏幕
2>实现离线缓存功能
3>实现消息推送
b、优势:几乎瞬间加载,但安全且富有弹性
c、核心:manifest文件清单、Service Workers
4、Manifest
作用:
a、能够将你浏览的网页添加到你的手机屏幕上
b、在 Android 上能够全屏启动,不显示地址栏 ( 由于 Iphone 手机的浏览器是 Safari ,所以不支持)
c、控制屏幕 横屏 / 竖屏 展示
d、定义启动画面
e、可以设置你的应用启动是从主屏幕启动还是从 URL 启动
f、可以设置你添加屏幕上的应用程序图标、名字、图标大小
示例:
index.html
manifest.json
{ 'name': 'Minimal PWA', // 必填 显示的插件名称 'short_name': 'PWA Demo', // 可选 在APP launcher和新的tab页显示,如果没有设置,则使用name 'description': 'The app that helps you understand PWA', //用于描述应用 'display': 'standalone', // 定义开发人员对Web应用程序的首选显示模式。standalone模式会有单独的 'start_url': '/', // 应用启动时的url 'theme_color': '#313131', // 桌面图标的背景色 'background_color': '#313131', // 为web应用程序预定义的背景颜色。在启动web应用程序和加载应用程序的内容之间创建了一个平滑的过渡。 'icons': [ // 桌面图标,是一个数组 { 'src': 'icon/lowres.webp', 'sizes': '48x48', // 以空格分隔的图片尺寸 'type': 'image/webp' // 帮助userAgent快速排除不支持的类型 }, { 'src': 'icon/lowres', 'sizes': '48x48' }, { 'src': 'icon/hd_hi.ico', 'sizes': '72x72 96x96 128x128 256x256' }, { 'src': 'icon/hd_hi.svg', 'sizes': '72x72' } ] }
另附:
5、Service Worker
SW 是什么呢?这个是离线缓存文件。我们 PWA 技术使用的就是它!SW 是浏览器在后台独立于网页运行的脚本,它打开了通向不需要网页或用户交互的功能的大门,因为使用了它,才会有的那个 Reliable 特性吧,SW 作用于 浏览器于服务器之间,相当于一个代理服务器。
功能(还是比较逆天的)
生命周期:
当前已无激活状态的 worker 、 SW脚本中的 self.skipWaiting()方法被调用 ( ps: self 是 SW 中作用于全局的对象,这个方法根据英文翻译过来也能明白什么意思啦,跳过等待状态 )、用户已关闭 SW 作用域下的所有页面,从而释放了当前处于激活状态的 worker、超出指定时间,从而释放当前处于激活状态的 worker
如果上个图不好理解,可以看这个,把它的生命周期看成红绿灯:
当用户首次导航至 URL 时,服务器会返回响应的网页
6、实现离线缓存
index.html
<!DOCTYPE html> <html> <head> <meta charset='UTF-8'> <title>Hello Caching World!</title> </head> <body> <!-- Image --> <img src='/images/hello.png' /> <!-- JavaScript --> <script async src='/js/script.js'></script> <script> // 注册 service worker if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(function (registration) { // 注册成功 console.log('ServiceWorker registration successful with scope: ', registration.scope); }).catch(function (err) { // 注册失败 :( console.log('ServiceWorker registration failed: ', err); }); } </script> </body> </html>
注意:
service-worker.js
var cacheName = 'helloWorld'; // 缓存的名称
// install 事件,它发生在浏览器安装并注册 Service Worker 时
self.addEventListener('install', event => {
/* event.waitUtil 用于在安装成功之前执行一些预装逻辑
但是建议只做一些轻量级和非常重要资源的缓存,减少安装失败的概率
安装成功后 ServiceWorker 状态会从 installing 变为 installed */
event.waitUntil(
caches.open(cacheName)
.then(cache => cache.addAll([ // 如果所有的文件都成功缓存了,便会安装完成。如果任何文件下载失败了,那么安装过程也会随之失败。
'/js/script.js',
'/images/hello.png'
]))
);
});
/**
为 fetch 事件添加一个事件监听器。接下来,使用 caches.match() 函数来检查传入的请求 URL 是否匹配当前缓存中存在的任何内容。如果存在的话,返回缓存的资源。
如果资源并不存在于缓存当中,通过网络来获取资源,并将获取到的资源添加到缓存中。
*/
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
if (response) {
return response;
}
var requestToCache = event.request.clone(); //
return fetch(requestToCache).then(
function (response) {
if (!response || response.status !== 200) {
return response;
}
var responseToCache = response.clone();
caches.open(cacheName)
.then(function (cache) {
cache.put(requestToCache, responseToCache);
});
return response;
})
);
});
注意:
7、消息推送
前两步:
index.html
<!DOCTYPE html> <html> <head> <meta charset='UTF-8'> <title>Progressive Times</title> <link rel='manifest' href='/manifest.json'> </head> <body> <script> var endpoint; var key; var authSecret; var vapidPublicKey = 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY'; // 方法很复杂,但是可以不用具体看,知识用来转化vapidPublicKey用 function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding) .replace(/\-/g, '+') .replace(/_/g, '/'); const rawData = window.atob(base64); const outputArray = new Uint8Array(rawData.length); for (let i = 0; i < rawData.length; ++i) { outputArray[i] = rawData.charCodeAt(i); } return outputArray; } if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js').then(function (registration) { return registration.pushManager.getSubscription() .then(function (subscription) { if (subscription) { return; } return registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array(vapidPublicKey) }) .then(function (subscription) { var rawKey = subscription.getKey ? subscription.getKey('p256dh') : ''; key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : ''; var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : ''; authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : ''; endpoint = subscription.endpoint; return fetch('./register', { method: 'post', headers: new Headers({ 'content-type': 'application/json' }), body: JSON.stringify({ endpoint: subscription.endpoint, key: key, authSecret: authSecret, }), }); }); }); }).catch(function (err) { // 注册失败 :( console.log('ServiceWorker registration failed: ', err); }); } </script> </body> </html>
步骤三:
app.js
service worker监听push事件,将通知详情推送给用户
service-worker.js
self.addEventListener('push', function (event) { // 检查服务端是否发来了任何有效载荷数据 var payload = event.data ? JSON.parse(event.data.text()) : 'no payload'; var title = 'Progressive Times'; event.waitUntil( // 使用提供的信息来显示 Web 推送通知 self.registration.showNotification(title, { body: payload.msg, url: payload.url, icon: payload.icon }) ); });
8、PWA小demo
准备:
创建一个关于PWA项目的文件夹
文件夹内准备一张图
一个index.html文件
一个main.css文件
一个manifest.json文件
一个sw.js文件
以后更新把,写崩了,我再寻思寻思
分割线,可爱的我来更新小栗子了
css文件夹里有一个style.css
images里有一个logo.jpg
具体看代码:
index.html
我的style.css是空的哈哈哈
sw.js
var cacheName = 'hello-pwa'; self.addEventListener('install', event => { event.waitUntil( caches.open(cacheName) .then(cache => cache.addAll( [ '/', // 这个一定要包含整个目录,不然无法离线浏览 './images/logo.jpg', './index.html', './css/style.css' ] )).then(() => self.skipWaiting()) ); }); self.addEventListener('fetch', function (event) { event.respondWith( caches.match(event.request) .then(function (response) { if (response) { return response; } return fetch(event.request); }) ); });
接下来通过 http-server 和 ngrok(https)进行调试查看
在当前文件下
安装 http-server
安装 ngrok,下载解压即可
在项目目录下执行如下命令:
http-server -c-1 // -c-1 会关闭缓存
再开启另外一个终端在 ngrok 文件的目录下执行如下命令:
运行:(我端口8080被占了,所以这里是8081)
查看application
查看缓存部分
第一次加载进来缓存没有东西,需要刷新一下页面。
https://github.com/yangTwo100/PWA_search_demo
以上。
联系客服