HTML5-PWA
什么是PWA
PWA是Progressive Web App的英文缩写, 翻译过来就是渐进式增强WEB应用, 是Google 在2016年提出的概念,2017年落地的web技术。目的就是在移动端利用提供的标准化框架,在网页应用中实现和原生应用相近的用户体验的渐进式网页应用。
- 可靠——即时加载,即使在不确定的网络条件下也不会受到影响。 当用户从主屏幕启动时,
service work
可以立即加载渐进式Web应用程序,完全不受网络环境的影响。service work
就像一个客户端代理,它控制缓存以及如何响应资源请求逻辑,通过预缓存关键资源,可以消除对网络的依赖,确保为用户提供即时可靠的体验。 - 快速 据统计,如果站点加载时间超过 3s,53% 的用户会放弃等待。页面展现之后,用户期望有平滑的体验,过渡动画和快速响应
- 沉浸式体验—— 感觉就像设备上的原生应用程序,具有沉浸式的用户体验。 渐进式Web应用程序可以安装并在用户的主屏幕上,无需从应用程序商店下载安装。他们提供了一个沉浸式的全屏幕体验,甚至可以重新与用户接触的Web推送通知。
Web应用程序中,可以通过manifest.json
控制应用程序的显示方式和启动方式,指定主屏幕图标、启动应用程序时要加载的页面、屏幕方向,甚至可以指定是否显示浏览器Chrome。
核心功能
PWA并不是单指某一项技术,你更可以把它理解成是一种思想和概念,目的就是对标原生app,将Web网站通过一系列的Web技术去优化它,提升其安全性,性能,流畅性,用户体验等各方面指标,最后达到用户就像在用app一样的感觉。
PWA中包含的核心功能及特性如下:
- Web App Manifest
- Service Worker
- Cache API 缓存
- Push&Notification 推送与通知
- Background Sync 后台同步
- 响应式设计
PWA如何弥补和原生App的差距
性能:PWA使用app Shell架构模型
- 快速加载
- 尽可能使用较少的数据
- 使用本机缓存中的静态资产
- 将内容与导航分离开来
- 检索和显示特定页面的内容(HTML、JSON 等)
- 缓存动态内容 App Shell 可保证 UI 的本地化以及从 API 动态加载内容,但同时不影响网络的可链接性和可检测性。 用户下次访问您的应用时,应用会自动显示最新版本。无需在使用前下载新版本。为了保证首屏的加载,在内容请求完成之前,可以优先保证 App Shell 的渲染,做到和 Native App 一样的体验,App Shell 是 PWA 界面展现所需的最小资源。
离线使用
Service Worker
+ HTTPS
+Cache Api
+ indexedDB
等一系列web技术实现离线加载和缓存
数据更新
Background Sync
后台同步技术
实现推送
Push
&Notification
实现推送与通知
Notification
消息推送就是你在手机上收到的某个 APP 的消息推送,相较于移动端 Native 应用,web 应用是缺少这一项常用的功能。而借助 PWA 的Push 特性,就是用户在打开浏览器时,不需要进入特定的网站,就能收到该网站推送而来的消息,而借助于 Android 的 Chrome,我们可以实现在用户不打开任何浏览器或者应用的情况下,收到我们项目的推送,就像一个真实的手机推送。
Web Push是一个基于 客户端 ,服务端 和 推送服务器 三者组成的一种流程规范,可以分为三个步骤:
- 客户端完成请求订阅一个用户的逻辑。
- 服务端调用遵从 web push 协议的接口,传送消息推送(push message)到推送服务器(该服务器由浏览器决定,开发者所能做的只有控制发送的数据)。
- 推送服务器将该消息推送至对应的浏览器,用户收到该推送。
Notifications API
允许网页或应用程序在系统级别发送在页面外部显示的通知;这样即使应用程序空闲或在后台,Web应用程序也会向用户发送信息。本文将介绍在您自己的应用程序中使用此API的基础知识。
Push
1.获取提醒权限:
使用Notification
对象上的静态方法Notification.requestPermission()
来获取授权
// index.js
function askPermission() {
return new Promise(function (resolve, reject) {
var permissionResult = Notification.requestPermission(function (result) {
resolve(result);
});
if (permissionResult) {
permissionResult.then(resolve, reject);
}
}).then(function (permissionResult) {
if (permissionResult !== 'granted') {
throw new Error('We weren\'t granted permission.');
}
});
}
registerServiceWorker('./sw.js').then(function (registration) {
return Promise.all([
registration,
askPermission()
])
})
- denied:用户拒绝了通知的显示
- granted:用户允许了通知的显示
- default:因为不知道用户的选择,所以浏览器的行为与denied时相同
chrome中,可以在chrome://settings/content/notifications里进行通知的设置与管理。
2.设置你的提醒内容
通过registration.showNotification()
方法进行消息提醒
// index.js
registerServiceWorker('./sw.js').then(function (registration) {
return Promise.all([
registration,
askPermission()
])
}).then(function (result) {
var registration = result[0];
/* ===== 添加提醒功能 ====== */
document.querySelector('#js-notification-btn').addEventListener('click', function () {
var title = 'PWA即学即用';
var options = {
body: '邀请你一起学习',
icon: '/img/icons/book-128.png',
actions: [{
action: 'show-book',
title: '去看看'
}, {
action: 'contact-me',
title: '联系我'
}],
tag: 'pwa-starter',
renotify: true
};
registration.showNotification(title, options);
});
/* ======================= */
})
- body:提醒的内容
- icon:提醒的图标
- actions:提醒可以包含一些自定义操作
- tag:相当于是ID,通过该ID标识可以操作特定的notification
- renotify:是否允许重复提醒,默认为false。当不允许重复提醒时,同一个tag的notification只会显示一次
3.捕获用户的点击
// sw.js
self.addEventListener('notificationclick', function (e) {
var action = e.action;
console.log(`action tag: ${e.notification.tag}`, `action: ${action}`);
switch (action) {
case 'show-book':
console.log('show-book');
break;
case 'contact-me':
console.log('contact-me');
break;
default:
console.log(`未处理的action: ${e.action}`);
action = 'default';
break;
}
e.notification.close();
});
- e.action
- e.notification
4.client通信
用Worker的postMessage()
方法来通知client
// sw.js
self.addEventListener('notificationclick', function (e) {
…… // 略去上一节内容
e.waitUntil(
// 获取所有clients
self.clients.matchAll().then(function (clients) {
if (!clients || clients.length === 0) {
return;
}
clients.forEach(function (client) {
// 使用postMessage进行通信
client.postMessage(action);
});
})
);
});
在client中监听message事件,判断data,进行不同的操作
// index.js
navigator.serviceWorker.addEventListener('message', function (e) {
var action = e.data;
console.log(`receive post-message from sw, action is '${e.data}'`);
switch (action) {
case 'show-book':
location.href = 'https://book.douban.com/subject/20515024/';
break;
case 'contact-me':
location.href = 'mailto:someone@sample.com';
break;
default:
document.querySelector('.panel').classList.add('show');
break;
}
});
添加到桌面
AppShell是PWA中的一个重要概念,使用manifest可以让你的WebApp拥有一个类似Native的“外壳”
通过manifest.json
文件配置,使得可以直接添加到手机的桌面上。
<!-- 在index.html中添加以下meta标签 -->
<link rel="manifest" href="/manifest.json">
{
"name": "WECIRCLE",
"short_name": "WECIRCLE",
"icons": [
{
"src": "./img/icons/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "./img/icons/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"background_color": "#000000",
"orientation": "portrait-primary",
"theme_color": "#181818"
}
name
:指定了 Web App 的名称,也就是保存到桌面图标的名称。short_name
:当 name 名称过长时,将会使用 short_name 来代替name显示,也就是 Web App 的简称。start_url
:指定了用户打开该 Web App 时加载的URL。相对URL会相对于 manifest.json 。这里我们指定了 index.html 作为 Web App 的启动页。display
:指定了应用的显示模式,它有四个值可以选择:fullscreen
:全屏显示,会尽可能将所有的显示区域都占满。standalone
:浏览器相关UI(如导航栏、工具栏等)将会被隐藏,因此看起来更像一个Native App。minimal-ui
:显示形式与standalone类似,浏览器相关UI会最小化为一个按钮,不同浏览器在实现上略有不同。browser
:一般来说,会和正常使用浏览器打开样式一致。- 这里需要说明一下的是当一些系统的浏览器不支持fullscreen时将会显示成 standalone 的效果,当不支持 standalone 属性时,将会显示成 minimal-ui 的效果,以此类推。
icons
:指定了应用的桌面图标和启动页图像,用数组表示:sizes
:图标的大小。通过指定大小,系统会选取最合适的图标展示在相应位置上。src
:图标的文件路径。相对路径是相对于 manifest.json 文件,也可以使用绝对路径例如http://xxx.png。type
:图标的图片类型。 浏览器会从 icons 中选择最接近 128dp(px = dp * (dpi / 160)) 的图片作为启动画面图像。
background_color
:指定了启动画面的背景颜色,采用相同的颜色可以实现从启动画面到首页的平稳过渡,也可以用来改善页面资源正在加载时的用户体验,结合icons属性,可以定义背景颜色+图片icon的启动页效果,类似与Native App的splash screen效果。theme_color
:指定了 Web App 的主题颜色。可以通过该属性来控制浏览器 UI 的颜色。比如状态栏、内容页中状态栏、地址栏的颜色。orientation
:设置某些值会具有类似锁屏的效果(禁止旋转)。具体的值包括:any, natural, landscape, landscape-primary, landscape-secondary, portrait, portrait-primary, portrait-secondary
Resource Hint
使用Resource Hint(以及Preload)来提升页面加载性能与体验,简单来说:
DNS Prefetch
可以帮助我们进行DNS预查询;Preconnect
可以帮助我们进行预连接,例如在一些重定向技术中,可以让浏览器和最终目标源更早建立连接;Prefetch
可以帮助我们预先获取所需资源(并且不用担心该资源会被执行),例如我们可以根据用户行为猜测其下一步操作,然后动态预获取所需资源;Prerender
则会更进一步,不仅获取资源,还会预加载(执行)部分资源,因此如果我们Prerender下一个页面,打开该页面时会让用户感觉非常流畅;Preload
则像是 Prefetch的升级版,会强制立即高优获取资源,非常适合Preload(尽早获取)一些关键渲染路径中的资源。
PWA未来
PWA 可以利用的技术:
- Ajax、响应式设计
- JavaScript 框架
- ECMAScript Next
- CSS Next
- Houdini
- Indexed DB
- Device APIs
- Web Bluetooth
- Web Socket
- Web Payment
- Background Sync API
- Streams
- WebVR……