PWA技術及其用戶體驗設計

  • 2020 年 4 月 10 日
  • 筆記

實驗室最近多了一個實驗產品MAX:群控手機的項目。主要包括:後端服務、web前端客戶端、安卓app客戶端。涉及到的編程語言:Java、Nodejs。技術上主要涉及安卓的MediaProjection API、配合websocket來實現。

MediaProjection 提供了錄屏功能;websocket主要是傳輸方便,可以做到實時。

今天主要介紹下web前端客戶端的實現,主要使用了PWA技術。

– 什麼是PWA?

PWA全稱Progressive Web Apps漸進增強 Web 應用程序,它可以離線運行,並且可以在運行的系統中選擇性安裝,它從外觀還是執行效果來看,與一般應用程序無異。

不知大家體驗過微軟的郵件服務沒?Outlook.com已經完成了PWA版本,可以在瀏覽器裏面像本地應用一樣直接打開即用。

比如我使用的mac,添加了一個PWA應用之後,底部菜單欄多了一個應用的icon,效果如下:

就是mixlab那個logo

PWA是一系列技術的集合,裏面最核心的是一個叫「app shell」的概念。

– 什麼是App shell?

我們先了解下,渲染網站主要有兩種方法:在服務器上或在客戶端上。

-服務器端渲染(SSR)

意味着網站每次都是在服務器上渲染,因此它提供了更快的首次加載,但是在頁面之間跳轉需要每次都下載所有內容,因而它的加載速度往往會比較慢。

-客戶端渲染(CSR)

頁面是在客戶端(瀏覽器)渲染的,因而加載速度往往取決於瀏覽器的性能,訪問速度會比較快,但是在開始時需要更多的初始下載(首次訪問時網站速度較慢),以保證整個網站其他頁面實現客戶端渲染所需要的數據。

兩種方式各有利弊,而PWA使用的方法是「app shell」,它混合了SSR和CSR的方式。

App shell,可以理解為程序的外殼。App shell意圖儘快加載最小的用戶界面,然後緩存它,以便在後續訪問時可以離線使用,然後加載應用程序的所有內容。這樣,下次有人從設備訪問應用程序時,UI立即從緩存加載,並從服務器請求新內容(如果它已在緩存中不可用)。

一個App shell的代碼結構如下:

<!DOCTYPE html>  <html lang="zh">  <head>          <meta charset="utf-8">    <title>Max</title>    <meta name="description" content="app的介紹">    <meta name="author" content="作者">    <meta name="theme-color" content="#B12A34">    <meta name="viewport" content="width=device-width, initial-scale=1">    <meta property="og:image" content="icons/icon-512.png">    <link rel="shortcut icon" href="favicon.ico">    <link rel="stylesheet" href="style.css">    <link rel="manifest" href="manifest.json">    <script src="app.js" defer></script>  </head>  <body>  <header>    <p>MAX-demo</p>  </header>  <main>    <h1>HELLO WORLD</h1>    <button id="notifications">Request notifications</button>    <section id="content">      // Content inserted in here    </section>  </main>  <footer>    <p>© max 2012-2018, created and maintained by shadow.</p>  </footer>  </body>  </html>

為了配合app shell,需要一個叫Service Worker API的支持。

– Service Worker

Service Worker API可以完成2種任務,一種是緩存App shell所需的數據,另一種是如果你有比較耗時的計算,你可以把它們從主線程中抽離出來,在Service Worker中進行計算,最後在它們計算完畢的時候從Service Worker中取得計算結果。

Service Worker主要由3項技術構成:

  • 緩存機制是依賴 Cache API 實現的
  • 依賴 HTML5 fetch API 發起網絡請求
  • 依賴 Promise 實現異步

service worker是需要註冊的,我們在app.js中,輸入:

if ('serviceWorker' in navigator) {      window.addEventListener('load', function() {          navigator.serviceWorker.register('/serviceWorker.js', { scope: '/' })              .then(function(registration) {                  // 註冊成功                  console.log('ServiceWorker registration successful with scope: ', registration.scope);              })              .catch(function(err) {                  // 註冊失敗                  console.log('ServiceWorker registration failed: ', err);              });      });    };

即可,使用上我們編寫好的serviceWorker.js文件。

serviceWorker.js主要對有install跟fetch事件進行監聽,對cache進行操作,達到緩存的目的。

let cacheName = 'max-v1';  let contentToCache = [      '/',      '/index.html',      '/style.css',      '/app.js'  ];    // 對app shell和主體內容(content)裏面的數據創建緩存  self.addEventListener('install', function(e) {      console.log('[Service Worker] Install');      e.waitUntil(          // 安裝成功後操作 CacheStorage 緩存,使用之前需要先通過 caches.open() 打開對應緩存空間。          caches.open(cacheName).then(function(cache) {              console.log('[Service Worker] Caching all: app shell and content');              // 通過 cache 緩存對象的 addAll 方法添加 緩存              return cache.addAll(contentToCache);          })      );  });      //如果條件允許,service worker將從緩存中請求content中所需的數據,從而提供離線應用功能  self.addEventListener('fetch', function(e) {      e.respondWith(          caches.match(e.request).then(function(r) {              console.log('[Service Worker] Fetching resource: ' + e.request.url);              return r || fetch(e.request).then(function(response) {                  return caches.open(cacheName).then(function(cache) {                      console.log('[Service Worker] Caching new resource: ' + e.request.url);                      cache.put(e.request, response.clone());                      return response;                  });              });          })      );  });

兩種方式可以比較一下:

– install

優點是第二次訪問即可離線,缺點是需要將需要緩存的 URL 在編譯時插入到腳本中,增加代碼量和降低可維護性;

– fetch

優點是無需更改編譯過程,也不會產生額外的流量,缺點是需要訪問過一次才能離線使用。

因此,在設計技術架構的時候,需要考慮到2種方式的優缺點。

除了配置serviceWorker.js之外,我們還需要配置manifest.json文件。

– 添加至桌面功能

serviceWorker使得網頁在速度跟體驗上接近原生app,除此之外,還需要引導用戶添加pwa應用到桌面,以方便下次使用。

實現 PWA 應用添加至桌面的功能,除了要求站點支持 HTTPS 之外,需要準備 manifest.json 文件去配置應用的圖標、名稱等信息。一個基本的 manifest.json 應包含如下信息:

{      "name": "max-demo-v1",      "short_name": "max",      "description": "demo",      "icons": [{          "src": "icons/mix-logo.png",          "sizes": "72x72 96x96 128x128 256x256",          "type": "image/png"      }],      "start_url": "/index.html",      "display": "fullscreen",      "theme_color": "#4a4a4a",      "background_color": "#eeeeee"  }

運行之後,在瀏覽器地址欄右側,可以看到一個+號,點擊安裝。

另外,調試可以在chrome的Application面板中進行查看

由於PWA的api不是所有瀏覽器都支持,因而,你還需要注意使用caniuse.com 來查看主流瀏覽器的支持情況。

– 如何告知普通用戶什麼是離線模式?或者什麼是PWA?

這是體驗設計上需要注意的地方,我們應該認識到並不是每個用戶都是技術出身,都對PWA的概念了解得很清楚。因而,用戶體驗設計需要為用戶提供指導,以便他們可以了解什麼是PWA(或者離線模式)。

確實,離線模式比PWA(漸進式)更為容易理解,但是離線模式對每個人來說都是一個全新的心智模式。通俗來講,您需要告訴用戶當他們無法連接上網絡時會發生什麼變化。比如:

哪些功能無法使用;

或者是頁面上的數據是什麼時間更新的;

目前的網絡連接情況;

等等。

除此之外,設計上要考慮首次加載的問題,如首次加載時間過長,需要設計動畫提示,可以把加載的文件內容簡要告知用戶,讓用戶知道網頁正在加載,而不是「死機了」。

下一步,擬集成TensorFlowJS,探索下可能性。