专栏首页云前端PWA - 令人惊奇的web用户体验新方法

PWA - 令人惊奇的web用户体验新方法

PWA(Progressive Web Apps)-令人惊奇的web用户体验新方法。

PWA全称Progressive Web App,即渐进式WEB应用,由谷歌2015年提出.明确的一点就是:PWA是一个网页, 可以通过web技术开发出一个网页应用. 随后添加上 App Manifest 和 Service Worker 来实现 PWA 的安装和离线等功能.pwa 可以添加在用户的主屏幕上,不用从应用商店进行下载,他们通过网络应用程序 Manifest file 提供类似于 APP 的使用体验( 在 Android 上可以设置全屏显示,由于 Safari 支持度的问题,所以在 IOS 上并不可以 ),并且还能进行 ”推送通知” 。

  • 渐进式:能确保每个用户都能打开网页,可以运行在不支持 PWA 技术的浏览器里。用户不能离线访问,不过其他功能都像原来一样没有影响。
  • 离线应用:支持用户在没网的条件下也能打开网页,这里就需要 Service Worker 的帮助,可以离线运行
  • APP 化:能够像 APP 一样和用户进行交互。被打开时,PWA 会展示一个有吸引力的闪屏。chrome 提供了可选选项,可以使 PWA 得到全屏体验。
  • 安全:PWA使用https进行通信加密,防止了被第三方获取数据以及数据被篡改
  • 推送:做到在不打开网页的前提下,推送新的消息
  • 可安装:能够将 Web像 APP 一样添加到桌面,可以在主屏幕上创建图标

为什么是渐进式

  • 降低站点改造的代价,逐步支持各项新技术,不要一蹴而就
  • 新技术标准的支持度还不完全,新技术的标准还未完全确定

Service Worker主要是干什么?

  • 主要用来做持久的离线缓存。
  • 一个独立的 worker 线程,独立于当前网页进程,有自己独立的 worker context。
  • 一旦被 install,就永远存在,除非被 uninstall
  • 可编程拦截代理请求和返回,缓存文件,缓存的文件可以被网页进程取到(包括网络离线状态)
  • 能向客户端推送消息
  • 出于安全的考虑,必须在 HTTPS 环境下才能工作
  • 异步实现,内部大都是通过 Promise 实
  • Service Worker 的缓存机制是依赖 Cache API 实现的
  • 依赖 HTML5 fetch API
  • 运行于浏览器后台,可以控制打开的作用域范围下所有的页面请求
  • 不能操作页面 DOM。但可以通过事件机制来处理

service worker 生命周期

register(注册) - installing -> installed(安装) -> actvating -> Activated(激活) -> Redundant(废弃)

  • 安装( installing ):这个状态发生在 Service Worker 注册之后,表示开始安装,触发 install 事件回调指定一些静态资源进行离线缓存。

install 事件回调中有两个方法: * event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止。 * self.skipWaiting():self 是当前 context 的 global 变量,执行该方法表示强制当前处在 waiting 状态的 Service Worker 进入 activate 状态。 * 安装后( installed ):Service Worker 已经完成了安装,并且等待其他的 Service Worker 线程被关闭。 * 激活( activating ):在这个状态下没有被其他的 Service Worker 控制的客户端,允许当前的 worker 完成安装,并且清除了其他的 worker 以及关联缓存的旧缓存资源,等待新的 Service Worker 线程被激活。

activate 回调中有两个方法: * event.waitUntil():传入一个 Promise 为参数,等到该 Promise 为 resolve 状态为止 * self.clients.claim():在 activate 事件回调中执行该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面,之后会被停止。 * 激活后( activated ):在这个状态会处理 activate 事件回调 (提供了更新缓存策略的机会)。并可以处理功能性的事件 fetch (请求)、sync (后台同步)、push (推送)。 * 废弃状态 ( redundant ):这个状态表示一个 Service Worker 的生命周期结束。 * 安装 (install) 失败,激活 (activating) 失败 都进入废弃 (redundant) 状态

Service Worker 有几个重要的功能性的的事件

  • fetch (请求):当浏览器在当前指定的 scope 下发起请求时,会触发 fetch 事件,并得到传有 response 参数的回调函数,回调中就可以做各种代理缓存的事情了。
  • push (推送):push 事件是为推送准备的。不过首先需要了解一下 Notification API 和 PUSH API。通过 PUSH API,当订阅了推送服务后,可以使用推送方式唤醒 Service Worker 以响应来自系统消息传递服务的消息,即使用户已经关闭了页面。
  • sync (后台同步):sync 事件由 background sync (后台同步)发出。background sync 配合 Service Worker 推出的 API,用于为 Service Worker 提供一个可以实现注册和监听同步处理的方法。但它还不在 W3C Web API 标准中, 也只是一个实验性功能。

cache api

  • Cache.match(request, options) 返回一个 Promise对象,resolve的结果是跟 Cache 对象匹配的第一个已经缓存的请求。
  • Cache.matchAll(request, options) 返回一个Promise 对象,resolve的结果是跟Cache对象匹配的所有请求组成的数组。
  • Cache.add(request) 抓取这个URL, 检索并把返回的response对象添加到给定的Cache对象.这在功能上等同于调用 fetch(), 然后使用 Cache.put() 将response添加到cache中.
  • Cache.addAll(requests) 抓取一个URL数组,检索并把返回的response对象添加到给定的Cache对象。
  • Cache.put(request, response) 同时抓取一个请求及其响应,并将其添加到给定的cache。
  • Cache.delete(request, options) 搜索key值为request的Cache 条目。如果找到,则删除该Cache 条目,并且返回一个resolve为true的Promise对象;如果未找到,则返回一个resolve为false的Promise对象。
  • Cache.keys(request, options) 返回一个Promise对象,resolve的结果是Cache对象key值组成的数组。

跑一个小例子

下面是个简单的 PWA 页面, 准备一个 HTML 文件, 以及相应的 CSS 等。sw.js 文件需要在 HTML 当中引入:

一个简单的结构:
看一下index.html 要干点啥:
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta http-equiv="X-UA-Compatible" content="ie=edge">
 <title>pwa beauty</title>
 <link rel="stylesheet" href="index.css">
 <link rel="manifest" href="manifest.json">
</head>
<body>
 <p>hello world beauty</p>
</body>
<script>index.html
 //判断serviceWorker是否可用
 if ('serviceWorker' in navigator) {
   navigator.serviceWorker
   .register('sw.js', {scope: '/'}) //启动安装,scope参数可选,指定控制内容的子目录,接收指定目录的所有fetch事件
   .then(function(registration) {
     //success
     console.log('scope: ', registration.scope);
   })
   .catch(err => console.log('serviceWorker 失败'), err);
 }
</script>
</html>

Service Worker 在网页已经关闭的情况下还可以运行, 用来实现页面的缓存和离线, 后台通知等等功能。

接下来看一下sw.js 主要做的这几件事情:

首先安装时会触发 install 事件,监听该事件可执行安装时要做的事情。示例中是缓存用于离线时使用的静态资源,这也是最常见的行为.

处理静态缓存:首先定义需要缓存的路径, 以及需要缓存的静态文件的列表:

var cacheStorageKey = 'manimal-pwa-1';var cacheList = [
 '/',
 'index.html',
 'index.css',
 '8.png'
]
//监听sw 的install 的事件
self.addEventListener('install', e => {
 //安装成功,调用e.waitUtill这个回调
 e.waitUtill(
   //操作缓存,先通过打开这个应用的缓存空间
   caches.open(cacheStorageKey)
   .then(caches => caches.addAll(cacheList)) //cacheList就是你想缓存的数组
   .then(() => self.skipWaiting) //skipWaiting install 之后强制进入激活状态,跳过waiting状态
 )
})

service worker 自定义请求响应

处理动态缓存: 每次任何被 Service Worker 控制的资源被请求到时,都会触发 fetch 事件,Service Worker 添加一个 fetch 的事件监听器,接着调用 event 上的 respondWith() 方法来劫持我们的 HTTP 响应,然后你可以用来更新他们

self.addEventListener('fetch', function(e) {
 e.respondWith(
   caches.match(e.request)
   .then(function (response) {
     //如果有自己的返回,直接返回,减少请求
     if (response != null) {
       return response
     }
     const request = e.request.clone();
     return fetch(request).then(function (httpRes) {
       //失败返回失败结果
       if (!httpRes || httpRes.status !== 200) {
         return httpRes;
       }
       //成功,把请求缓存
       const responseClone = httpRes.clone();
       caches.open(cacheStorageKey).then(function (caches) {
         caches.put(e.request, responseClone)
       })
       return httpRes;
     })
   })
 )
})

版本更新

更新静态资源:缓存的资源随着版本的更新会过期, 所以会根据缓存的字符串名称(这里变量为 cacheStorageKey, 值用了 "minimal-pwa-1")清除旧缓存, 可以遍历所有的缓存名称逐一判断决决定是否清除。如果 /sw.js 内容有更新,当访问网站页面时浏览器获取了新的文件,逐字节比对 /sw.js 文件发现不同时它会认为有更新启动 更新算法,于是会安装新的文件并触发 install 事件。但是此时已经处于激活状态的旧的 Service Worker 还在运行,新的 Service Worker 完成安装后会进入 waiting 状态。直到所有已打开的页面都关闭,旧的 Service Worker 自动停止,新的 Service Worker 才会在接下来重新打开的页面里生效

自动更新所有页面

self.addEventListener('activate', function (e) {
 e.waitUtill(
   caches.keys().then(cacheNames => {
     return Promise.all(
       //处理旧版本
       cacheNames.map(name => {
         if (name !== cacheStorageKey) {
           return caches.delete(name)
         }
       })  
     )
   }).then(() => {
     //更新客户端该方法表示取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存
     return self.clients.claim();
   })
 )
})

在新安装的 Service Worker 中通过调用 self.clients.claim() 取得页面的控制权, 这样之后打开页面都会使用版本更新的缓存。旧的 Service Worker 脚本不再控制着页面之后会被停止。

手动更新

var version = 'manimal-pwa-2';navigator.serviceWorker.register('/sw.js').then(function (reg) {
   if (localStorage.getItem('sw_version') !== version) {
       reg.update().then(function () {
           localStorage.setItem('sw_version', version)
       });
   }
});

查看demo

执行命令:

http-server -p 8088 -c-1 # 注意设置关闭缓存, 这里用参数 -c-1
# 用另一个终端
ngrok http 8088

注意: Demo 当中如果直接启动 http-server 而不使用 -c-1 关闭缓存, sw.js 可能被缓存住, 导致更新方案失败。这种情况下存在 Caches API 和 HTML caching 两层缓存, 需要进行清理才能完成更新。由于 Service Worker 限制了使用 HTTPS 地址, 在 Android Chrome 打开需要借助 ngrok 生成的 HTTPS 地址, 这样才能把 demo 添加到首屏。添加到首屏之后, 即便在离线状态下, 页面也可以打开。

桌面浏览器可以直接通过 http://localhost:8088 访问, 从 DevTools 的 Application 标签可以看到 Service Worker。

Service Worker 浏览器支持情况

  • 2018年,全球顶级的浏览器厂商,Google、Microsoft、Apple已经全数宣布支持PWA技术
  • (apple: Safari 11.1 beta版已经支持Web App Manifest 和 Service Worker)
  • Microsoft:Edge和Windows 10全面支持PWA
  • 微软,宣布全面支持PWA

sw调试 借助 Chrome 浏览器 debug

使用 Chrome 浏览器,可以通过进入控制台 Application -> Service Workers 面板查看和调试

APP Manifest (应用清单)与添加到主屏幕

Web App Manifest 是一个 JSON 格式的文件用来描述应用相关的信息,目的是提供将应用添加至桌面的功能:

  • 能够将你浏览的网页添加到你的手机屏幕上
  • 在 Android 上能够全屏启动,不显示地址栏 ( 由于 Iphone 手机的浏览器是 Safari ,所以不支持哦)
  • 控制屏幕 横屏 / 竖屏 展示
  • 定义启动画面
  • 可以设置你的应用启动是从主屏幕启动还是从 URL 启动
  • 可以设置你添加屏幕上的应用程序图标、名字、图标大小
<link rel="manifest" href="manifest.json">
{
 "scope": "/", //定义应用模式下的路径范围,超出范围会已浏览器方式显示
 "name": "这是一个app 的名字", //完整的名字
 "short_name": "app", //短名称
 "start_url": "/index.html", //启动地址
 "display": "standalone",
 "description": "每天知道多一点.",
 "dir": "rtl",
 "lang": "cn",
 "orientation": "portrait", //纵向, 竖屏
 "theme_color": "#3f51b5",
 "background_color": "#fff",
 "icons": [{
   "src": "images/avatar/lzwme-36x36.png",
   "sizes": "36x36",
   "type": "image/png"
 }, {
   "src": "images/avatar/lzwme-48x48.png",
   "sizes": "48x48",
   "type": "image/png"
 }, {
   "src": "images/avatar/lzwme-72x72.png",
   "sizes": "72x72",
   "type": "image/png"
 }, {
   "src": "images/avatar/lzwme-96x96.png",
   "sizes": "96x96",
   "type": "image/png"
 }, {
   "src": "images/avatar/lzwme-144x144.png",
   "sizes": "144x144",
   "type": "image/png"
 }, {
   "src": "images/avatar/lzwme-192x192.png",
   "sizes": "192x192",
   "type": "image/png"
 }, {
   "src": "images/avatar/lzwme-256x256.png",
   "sizes": "256x256",
   "type": "image/png"
 }]
}
  • short_name: 应用展示的名字
  • icons: 定义不同尺寸的应用图标
  • start_url: 定义桌面启动的 URL
  • description: 应用描述,可以参考 meta 中的 description
  • display: 定义应用的显示方式,有 4 种显示方式,分别为:
    • fullscreen: (全屏)
    • standalone: 应用 , 浏览器相关UI(如导航栏、工具栏等)将会被隐藏
    • minimal-ui: 类似于应用模式,但比应用模式多一些系统导航控制元素,但又不同于浏览器模式
    • browser: 浏览器模式,默认值
  • name: 应用名称
  • orientation: 定义默认应用显示方向,竖屏、横屏
  • background_color: 应用加载之前的背景色,用于应用启动时的过渡
  • theme_color: 定义应用默认的主题色
  • dir: 文字方向,3 个值可选 ltr(left-to-right), rtl(right-to-left) 和 auto(浏览器判断),默认为 auto
  • lang: 语言
  • scope: 定义应用模式下的路径范围,超出范围会已浏览器方式显示

PWA 应用具备了轻量化、离线使用、本地通知等优势特点,应用本身只需占用很小的存储空间,依然保留了原生 Apps 大部分功能,甚至还优化了硬件性能消耗、应用频繁出现广告内容的问题。如果你希望在安装原生应用之前,提前体验功能和内容,轻量化的 PWA 应用会是一个非常不错的选择。

参考资料

  • 浏览器兼容
  • manifest.json参数详解
  • Service Worker API

本文分享自微信公众号 - 云前端(fewelife),作者:王文秀

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2018-05-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [译] HTTP 缓存头部 - 完全指南

    原文: http://cncc.bingj.com/cache.aspx?q=max-age+expires+Last-Modified&d=499745815...

    江米小枣
  • [译]深入Promise错误处理

    原文: https://codeburst.io/promise-error-handling-in-depth-90b0965149c0

    江米小枣
  • 顺藤摸瓜:用单元测试读懂 vue3 watch 函数

    在 Vue 3.x 的 Composition API 中,我们可以用近似 React Hooks 的方式组织代码的复用;ref/reactive/comput...

    江米小枣
  • 优化不易,且写且珍惜!

    本文要感谢我职级评定过程中的一位评委,他建议把之前所做的各种性能优化的案例和方案加以提炼、总结,以文档的形式沉淀下来,并在内部进行分享。力求达到如下效果:

    技术zhai
  • 二叉树的递归遍历

    百度百科中最近公共祖先的定义为:“对于有根树 T 的两个结点 p、q,最近公共祖先表示为一个结点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节...

    程序员小王
  • 优化不易,且写且珍惜!

    本文要感谢我职级评定过程中的一位评委,他建议把之前所做的各种性能优化的案例和方案加以提炼、总结,以文档的形式沉淀下来,并在内部进行分享。力求达到如下效果:

    技术zhai
  • 常见性能优化策略的总结

    本文要感谢我职级评定过程中的一位评委,他建议把之前所做的各种性能优化的案例和方案加以提炼、总结,以文档的形式沉淀下来,并在内部进行分享。力求达到如下效果: 1....

    美团技术团队
  • 学界 | 看深度学习十九般武艺:它还能帮助保护濒危动物

    AI 科技评论按:濒危动物研究中一大难题是准确估计它们的数量,想要追踪以及详细了解其中的个体更是难上加难。不过来自杜克尼古拉斯学院的两位老师想到了办法,居然可以...

    AI科技评论
  • electron打包爬坑

    一番前面用electron+nodejs+vue+python开发了一个pdf合并工具,现在的情况是:

    efonfighting
  • 下载助手已更新!!!

    A1:请检查浏览器是否已安装tampermonkey或暴力猴 , 并启用脚本,Tampermonkey请安装4.7以上版本,如下图所示

    DataScience

扫码关注云+社区

领取腾讯云代金券