PWA

Posted by huangqing on August 31, 2020

什么是PWA

PWA是Progressive Web App的英文缩写, 翻译过来就是渐进式增强WEB应用, 是Google 在2016年提出的概念,2017年落地的web技术。目的就是在移动端利用提供的标准化框架,在网页应用中实现和原生应用相近的用户体验的渐进式网页应用。

  1. 可靠——即时加载,即使在不确定的网络条件下也不会受到影响。 当用户从主屏幕启动时,service work可以立即加载渐进式Web应用程序,完全不受网络环境的影响。service work就像一个客户端代理,它控制缓存以及如何响应资源请求逻辑,通过预缓存关键资源,可以消除对网络的依赖,确保为用户提供即时可靠的体验。
  2. 快速 据统计,如果站点加载时间超过 3s,53% 的用户会放弃等待。页面展现之后,用户期望有平滑的体验,过渡动画和快速响应
  3. 沉浸式体验—— 感觉就像设备上的原生应用程序,具有沉浸式的用户体验。 渐进式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架构模型

  1. 快速加载
  2. 尽可能使用较少的数据
  3. 使用本机缓存中的静态资产
  4. 将内容与导航分离开来
  5. 检索和显示特定页面的内容(HTML、JSON 等)
  6. 缓存动态内容 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是一个基于 客户端 ,服务端 和 推送服务器 三者组成的一种流程规范,可以分为三个步骤:

  1. 客户端完成请求订阅一个用户的逻辑。
  2. 服务端调用遵从 web push 协议的接口,传送消息推送(push message)到推送服务器(该服务器由浏览器决定,开发者所能做的只有控制发送的数据)。
  3. 推送服务器将该消息推送至对应的浏览器,用户收到该推送。

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"
}
  1. name:指定了 Web App 的名称,也就是保存到桌面图标的名称。
  2. short_name:当 name 名称过长时,将会使用 short_name 来代替name显示,也就是 Web App 的简称。
  3. start_url:指定了用户打开该 Web App 时加载的URL。相对URL会相对于 manifest.json 。这里我们指定了 index.html 作为 Web App 的启动页。
  4. display:指定了应用的显示模式,它有四个值可以选择:
    1. fullscreen:全屏显示,会尽可能将所有的显示区域都占满。
    2. standalone:浏览器相关UI(如导航栏、工具栏等)将会被隐藏,因此看起来更像一个Native App。
    3. minimal-ui:显示形式与standalone类似,浏览器相关UI会最小化为一个按钮,不同浏览器在实现上略有不同。
    4. browser:一般来说,会和正常使用浏览器打开样式一致。
    5. 这里需要说明一下的是当一些系统的浏览器不支持fullscreen时将会显示成 standalone 的效果,当不支持 standalone 属性时,将会显示成 minimal-ui 的效果,以此类推。
  5. icons:指定了应用的桌面图标和启动页图像,用数组表示:
    1. sizes:图标的大小。通过指定大小,系统会选取最合适的图标展示在相应位置上。
    2. src:图标的文件路径。相对路径是相对于 manifest.json 文件,也可以使用绝对路径例如http://xxx.png。
    3. type:图标的图片类型。 浏览器会从 icons 中选择最接近 128dp(px = dp * (dpi / 160)) 的图片作为启动画面图像。
  6. background_color:指定了启动画面的背景颜色,采用相同的颜色可以实现从启动画面到首页的平稳过渡,也可以用来改善页面资源正在加载时的用户体验,结合icons属性,可以定义背景颜色+图片icon的启动页效果,类似与Native App的splash screen效果。
  7. theme_color:指定了 Web App 的主题颜色。可以通过该属性来控制浏览器 UI 的颜色。比如状态栏、内容页中状态栏、地址栏的颜色。
  8. orientation:设置某些值会具有类似锁屏的效果(禁止旋转)。具体的值包括:any, natural, landscape, landscape-primary, landscape-secondary, portrait, portrait-primary, portrait-secondary

Resource Hint

使用Resource Hint(以及Preload)来提升页面加载性能与体验,简单来说:

  1. DNS Prefetch 可以帮助我们进行DNS预查询;
  2. Preconnect 可以帮助我们进行预连接,例如在一些重定向技术中,可以让浏览器和最终目标源更早建立连接;
  3. Prefetch 可以帮助我们预先获取所需资源(并且不用担心该资源会被执行),例如我们可以根据用户行为猜测其下一步操作,然后动态预获取所需资源;
  4. Prerender 则会更进一步,不仅获取资源,还会预加载(执行)部分资源,因此如果我们Prerender下一个页面,打开该页面时会让用户感觉非常流畅;
  5. Preload 则像是 Prefetch的升级版,会强制立即高优获取资源,非常适合Preload(尽早获取)一些关键渲染路径中的资源。

PWA未来

PWA 可以利用的技术:

  1. Ajax、响应式设计
  2. JavaScript 框架
  3. ECMAScript Next
  4. CSS Next
  5. Houdini
  6. Indexed DB
  7. Device APIs
  8. Web Bluetooth
  9. Web Socket
  10. Web Payment
  11. Background Sync API
  12. Streams
  13. WebVR……

##

PWA 学习手册