[Google I/O2021]优化PWA体验的四个建议

本文内容来自视频直播,若是想要了解更多内容能够点击每一段落的标题打开对应的博文进行了解
想要了解PWA以及相应的优化技巧,推荐阅读一下 PWA应用实战 ,虽然里面有部份内容已通过气+过时了,不过里面的思想仍是能够活用于如今的javascript

为了让网页能够提供原生应用般的体验,PWA自提出以来愈来愈受欢迎。单就本身的使用体验而言,愈来愈多的谷歌网页都开始适配PWA支持,使得之前要在标签页里输入打开的网页成为一个能够直接在开始菜单启动的应用。除了一般的适配策略以外,在这里分享五个用于提供更好用户体验的PWA优化技巧。html

提供离线时的fallback页面

谷歌相册会在网络很差的时候跳转到断网页面 serviceWorker能够对于网络请求进行缓存,这样不但能够提升加载的速度,还能够在网络请求失败的时候(例如用户网络环境很差)进行兜底,用缓存数据替代从网络获取失败的页面(并不推荐使用跳转的方式,由于这样意味着用户可能会丢失想要访问的页面URL)。所以,对于不少须要大量网络交互的应用能够在用户进入页面的时候经过一个友好的页面告知用户应用在网路状态很差的状况下不能使用。对于一些纯前端的小应用就不必增长离线告知界面了,只须要缓存应用代码让用户离线使用便可。前端

思路首次加载的时候注册SW,缓存OFFLINE.html->下次访问的页面若是既没有匹配到SW的缓存又没有成功经过网络fetch到,就返回以前混存了的OFFLINE.html告知用户此次打开的时候网络有问题->在离线界面不断地循环fetch操做,若是可以成功从网络拉取到任何信息则说明网络恢复,自动跳转到应用界面java

代码片断

首先,准备一个告知用户当前离线的页面。为了便于缓存以及节省用户的存储空间,样式和脚本最好内嵌在同一个文件中。
其中,脚本方面,须要对于两部分进行监听:首先,固然是浏览器的网络访问情况,所以,监听Navigator.online事件,网络恢复就从新加载git

window.addEventListener('online', () => {
        window.location.reload();
      });  
复制代码

除此以外,还有可能服务器出现问题致使不能访问,所以对于这种用户在线服务器当机的状况,设定定时循环操做按期检验服务器状态,直到服务器有响应再从新加载github

async function checkNetworkAndReload() {
        try {
          const response = await fetch('.');
          // 检验是否能从服务器得到应答
          if (response.status >= 200 && response.status < 500) {
            window.location.reload();
            return;
          }
        } catch {
          //用户在线,服务器不能正常工做,那也不用从新加载页面了
        }
        //做为按期任务
        window.setTimeout(checkNetworkAndReload, 2500);
      }

      checkNetworkAndReload();
复制代码

页面准备好了就要配置SW让其能够在断网状况下加载出来,请参考注释内容web

/* Copyright 2015, 2019, 2020, 2021 Google LLC. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */

// Incrementing OFFLINE_VERSION will kick off the install event and force
// previously cached resources to be updated from the network.
// This variable is intentionally declared and unused.
// Add a comment for your linter if you want:
// eslint-disable-next-line no-unused-vars
const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline";
// Customize this with a different URL if needed.
const OFFLINE_URL = "offline.html";

self.addEventListener("install", (event) => {
  event.waitUntil(
    (async () => {
      const cache = await caches.open(CACHE_NAME);//若是SW成功安装,就在对应的cache版本中保存离线用页面
      // Setting {cache: 'reload'} in the new request will ensure that the
      // response isn't fulfilled from the HTTP cache; i.e., it will be from
      // the network.
      await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
    })()
  );
  // Force the waiting service worker to become the active service worker.
  self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    (async () => {
      // Enable navigation preload if it's supported.
      // See https://developers.google.com/web/updates/2017/02/navigation-preload
      if ("navigationPreload" in self.registration) {
        await self.registration.navigationPreload.enable();
      }
    })()
  );

  // Tell the active service worker to take control of the page immediately.
  self.clients.claim();
});

self.addEventListener("fetch", (event) => {
  // We only want to call event.respondWith() if this is a navigation request
  // for an HTML page.
  if (event.request.mode === "navigate") {//只有跳转请求才使用一整个离线页面接管
    event.respondWith(
      (async () => {
        try {
          // 首先看看浏览器的preloadResponse能不能返回
          const preloadResponse = await event.preloadResponse;
          if (preloadResponse) {
            return preloadResponse;
          }

          // 而后使用网络请求
          const networkResponse = await fetch(event.request);
          return networkResponse;
        } catch (error) {
        //若是出现错误就用以前缓存的离线页面进行回应
          // catch is only triggered if an exception is thrown, which is likely
          // due to a network error.
          // If fetch() returns a valid HTTP response with a response code in
          // the 4xx or 5xx range, the catch() will NOT be called.
          console.log("Fetch failed; returning offline page instead.", error);

          const cache = await caches.open(CACHE_NAME);
          const cachedResponse = await cache.match(OFFLINE_URL);
          return cachedResponse;
        }
      })()
    );
  }

  // If our if() condition is false, then this fetch handler won't intercept the
  // request. If there are any other fetch handlers registered, they will get a
  // chance to call event.respondWith(). If no fetch handlers call
  // event.respondWith(), the request will be handled by the browser as if there
  // were no service worker involvement.
});
复制代码

按照谷歌相册的使用流程,当网络与服务器的链接不稳定的时候,会显示“相册只能在网络下使用”的提示,中间有一个“重试”的按钮让用户能够主动从新加载,即使浏览器处于online状态,可是不能访问谷歌的时候也不会进行跳转,当网络能够访问的时候,便自动刷新页面。
能够在开发者工具-Application-SW设置里开启offline模式,查看谷歌家的PWA应用是如何处理网络问题的。
不过有的应用则是选择优先加载应用骨架,而后经过组件的提示来告知断网状况,各有利弊吧express

image.png

给应用增长快捷指令

image.png
许多应用都会有些经常使用功能,例如推特常常须要查看我的首页,或者发布消息,对于笔记应用,对于收藏夹增长个快捷方式也是很不错的。所以,咱们能够在PWA的声明文件中声明一些经常使用连接/功能的快捷方式,这样,对于移动端用户,能够经过在桌面长按安装了的PWA的图标选择快捷方式,桌面端用户也能够右键单击图标的方式进行使用。apache

代码示例

要让一个PWA能够被安装,同时让用户和浏览器更加理解应用提供的功能,manifest必不可少,所以,若是须要添加快捷方式,也只须要在shortcuts项中声明便可。json

{
  "name": "Player FM",
  "start_url": "https://player.fm?utm_source=homescreen",
  …
  "shortcuts": [
    {
      "name": "Open Play Later",
      "short_name": "Play Later",
      "description": "View the list of podcasts you saved for later",
      "url": "/play-later?utm_source=homescreen",
      "icons": [{ "src": "/icons/play-later.png", "sizes": "192x192" }]
    },
    {
      "name": "View Subscriptions",
      "short_name": "Subscriptions",
      "description": "View the list of podcasts you listen to",
      "url": "/subscriptions?utm_source=homescreen",
      "icons": [{ "src": "/icons/subscriptions.png", "sizes": "192x192" }]
    }
  ]
}
复制代码

其中,只有 name,url是必要的。全部被声明了的快捷方式能够在开发者工具中查看,使用的时候须要注意:把最经常使用的放在shortcuts数组的最前面,名字须要简洁直接 image.png

作好应用声明的国际化

虽然感受这个不属于前端的范畴了,不过仍是值得讲一下。以前提到了PWA经过声明文件告知浏览器和用户本身的各类信息和功能,所以,对于不一样语言的用户,提供不一样语言的声明文件十分有必要,这样一样是添加一个应用在桌面,中文用户显示的是推特,英文用户显示的就是twitter了。其实现有两种方式。 许多人会把应用托管在GHPages上,所以须要把更换manifest的逻辑放在前端,所以能够参照这篇文章进行操做,具体思路就是判断navigator.language而后根据得到的用户语言动态生成对应语言的manifest节点。

<script>
    var userLan = navigator.language || 'en-US'
link = document.createElement('link');
    link.href = './manifest/manifest.' + userLan + '.json';
    link.rel = 'manifest';
document.getElementsByTagName('head')[0].appendChild(link);
</script>
<!-- <link rel="manifest" href="/manifest/manifest.zh-CN.json" > --> 复制代码

不过这个有一个缺点,由于应用安装事后不能更改manifest的路径/文件名,不然就不能对其进行更新。所以若是用户中间更改了须要的语言,就须要卸载从新安装了。
所以,能够对服务器进行操做,推荐使用请求头来判断用户语言,而后给用户的同一请求返回对应语言的文件的方式,这样manifest的路径不变,PWA就能够以更新的方式切换应用声明的语言了。能够经过获取请求头的accept-language来决定语言,若是应用提供设置界面给用户切换应用语言的话(推特,又是你),还能够先读取cookie携带的信息,经过用户设置的语言->浏览器语言的方式决定返回什么语言的声明。

监测用户使用PWA功能的效果

一样是网页应用,其又能够直接在浏览器直接浏览,也能够经过添加到桌面的方式像一个独立的应用进行使用。那确定是但愿用户可以安装一下啦(,因此就搞个推荐窗口引导用户安装,而且进行统计吧。

引导用户安装

若是浏览器判断用户在一个页面中使用的活跃度比较高的话,就会自动弹出以下的窗口建议用户安装PWA应用到桌面以更好地使用。不过不少用户不明白这是什么,因此就让咱们本身来接管吧(使命感) image.png

let defferedPromt;//这个存储被接管了的浏览器PWA安装推广事件
window.addEventListener('beforeinstallpromt',e=>{
e.preventDefault();//接管
defferedPromt =e;//用于用户点击了咱们提供的按钮以后的调用
showCustomPWAPromtInWeb();
ga(xxxx);
})
复制代码

showCustomPWAPromtInWeb函数中,咱们须要在本身的页面中弹出推广窗口,推荐用户将应用进行安装,威逼利诱下啥的,例如“我这里有好康的,让我安装!”或者"咱们发现您在咱们页面中使用了不少次,是否有兴趣将其安装到桌面,这样就能够以独立程序的形式进行使用啦"这样的,用户点击了yes以后,就调用 defferedPromt.promt() 这时候浏览器才会弹出以前的那个安装确认气泡,这样用户的安装几率就大大提高了。

作好用户使用的统计

小应用什么的随便啦,不过相信对于适配了PWA的开发者而言,仍是对于本身的PWA效果挺好奇的吧,用户喜不喜欢,适配PWA能不能提高用户留存啥的。所以能够对于如下的流程进行埋点统计:
首先,当弹出了PWA安装提示的时候,以及用户对于浏览器安装PWA提示的选择 await defferedPromt.userChoice 能够获得用户对于PWA的接受程度,同时还能够监听appInstalled事件,判断用户是自行点击浏览器地址栏的应用安装的仍是5经过咱们弹出的小广告来安装的。同时,还能够匹配当前应用是在standalone(独立应用窗口)仍是tab的形式进行使用,来生成对应的用户画像。

闲话

我很喜欢PWA!由于可让一个简单写出来的网页有应用般的效果! 如今浏览器愈来愈厉害了,让web应用有原生应用般的体验,不管是网络优化,仍是系统支持,都在愈来愈完善,我也发现不少我平时使用的平台开始对于PWA进行支持了。不过国内的网络走向了另外一个方向,首先是阉割web功能,强制下载app制造围城,齐次国内的浏览器也阉割了对于web标准的支持,不过别人怎么样是别人的事,若是本身在写web小工具的时候能够进行优化,相信别人在使用的时候也能会心一笑吧xd