逐步強化漸進式網頁應用程式

為新式瀏覽器建構應用程式,並逐步改善,就像 2003 年一樣

2003 年 3 月,Nick FinckSteve Champeon漸進式強化的概念為網頁設計世界打破傳統概念,這項網路設計策略會先載入核心網頁內容,再逐步加入更細緻、技術嚴謹的內容呈現和功能。在 2003 年,漸進式改善技術是指使用當時的現代 CSS 功能、不顯眼的 JavaScript,甚至是可調整向量圖形。2020 年及以後的漸進式強化功能,主要是使用新式瀏覽器功能

透過漸進式改善,打造未來的無障礙網頁設計。標題投影片:Finck 和 Champeon 的原創簡報。
投影片:採用多元包容設計,因應未來趨勢,漸進式強化。 (資料來源)

新版 JavaScript

談到 JavaScript,瀏覽器支援最新核心 ES 2015 JavaScript 功能的情況非常理想。新標準包含承諾、模組、類別、範本文字常值、箭頭函式、letconst、預設參數、產生器、結構重組指派、rest 和 spread、Map/SetWeakMap/WeakSet 等等。所有語言都支援

CanIUse 支援表格:ES6 功能支援表格,顯示所有主要瀏覽器的支援情形。
ECMAScript 2015 (ES6) 瀏覽器支援表格。(來源)

非同步函式是 ES 2017 的功能,也是我個人最喜歡的功能之一,可在所有主要瀏覽器中使用asyncawait 關鍵字可讓以更簡潔的樣式編寫具有保證的非同步行為,不必明確設定承諾鏈。

CanIUse 支援表格:顯示所有主要瀏覽器支援的非同步函式。
非同步函式瀏覽器支援資料表。(來源)

甚至連最近新增的 ES 2020 語言功能,例如可選鏈結空值合併,也都很快就獲得支援。請參考下方的程式碼範例。就 JavaScript 核心功能而言,現在的情況已經相當理想。

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
經典的 Windows XP 綠色草地背景圖片。
談到 JavaScript 核心功能,綠草如茵。 (Microsoft 產品螢幕截圖,使用時須取得授權)。

範例應用程式:Fugu Greetings

在本文中,我會使用名為 Fugu Greetings 的簡易 PWA (GitHub)。這個應用程式的名稱是向 Project Fugu 致敬 🐡,Project Fugu 是為了讓網頁擁有 Android/iOS/電腦應用程式的所有功能。如要進一步瞭解這項專案,請前往到達網頁

Fugu Greetings 是一款繪圖應用程式,可用來建立虛擬賀卡,並將這些卡片傳送給親友。這項範例展現了 PWA 的核心概念。這項服務可靠且完全支援離線使用,因此即使沒有網路,您還是可以使用這項服務。您也可以使用在裝置的主畫面安裝此元件,並與作業系統完美整合,做為獨立應用程式。

Fugu Greetings PWA,其中有類似 PWA 社群標誌的圖片。
Fugu Greetings 範例應用程式。

漸進增強

先告一段落,接著就來談談漸進式強化功能。 MDN 網路文件詞彙解釋了這個概念的定義如下:

漸進式增強是一種設計理念,可為盡可能多的使用者提供基本內容和功能,同時只為可執行所有必要程式碼的最新瀏覽器使用者提供最佳體驗。

功能偵測通常用於判斷瀏覽器是否可處理較新式功能,而polyfill通常用於透過 JavaScript 新增缺少的功能。

[…]

漸進式強化是相當實用的技術,可讓網頁開發人員專心開發成效最佳的網站,同時透過多個未知的使用者代理程式讓網站運作。優雅降級與漸進式增強有相關,但兩者並不相同,且通常被視為漸進式增強的反向做法。實際上,兩種做法都有效,而且通常可以相輔相成。

MDN 內容提供者

從零開始製作每張賀卡可能非常麻煩。 為什麼不提供讓使用者匯入圖片的功能,讓他們從那裡開始呢?在傳統方法中,您會使用 <input type=file> 元素來達成這項目標。首先,您需要建立元素,將其 type 設為 'file',並將 MIME 類型新增至 accept 屬性,然後以程式設計方式「點選」該元素,並監聽變更。選取圖片後,系統會直接將圖片匯入畫布。

const importImage = async () => {
  return new Promise((resolve) => {
    const input = document.createElement('input');
    input.type = 'file';
    input.accept = 'image/*';
    input.addEventListener('change', () => {
      resolve(input.files[0]);
    });
    input.click();
  });
};

當有匯入功能時,可能也應有匯出功能,方便使用者將賀卡儲存在本機。儲存檔案的傳統方法,是以 download 屬性建立錨點連結,並將 blob 網址做為 href。您也可以透過程式輔助方式「點選」該按鈕,觸發下載作業。此外,為了避免記憶體流失,請務必記得撤銷 Blob 物件網址。

const exportImage = async (blob) => {
  const a = document.createElement('a');
  a.download = 'fugu-greeting.png';
  a.href = URL.createObjectURL(blob);
  a.addEventListener('click', (e) => {
    setTimeout(() => URL.revokeObjectURL(a.href), 30 * 1000);
  });
  a.click();
};

但請稍候片刻。在心理層面上,你並未「下載」賀卡,而是「儲存」賀卡。瀏覽器不會顯示用來選擇檔案儲存位置的「儲存」對話方塊,而是直接下載問候卡,且會直接將卡入「下載」資料夾。這不是個好現象。

有沒有更好的做法?如果只要開啟本機檔案、編輯內容,然後將修改內容儲存至新檔案,或復原為最初開啟的原始檔案,該怎麼做?事實上,有。File System Access API 可讓您開啟及建立檔案和目錄,以及修改及儲存檔案。

那麼,如何偵測 API 的功能?File System Access API 會公開新的 window.chooseFileSystemEntries() 方法。因此,我需要根據這個方法是否可用,條件式載入不同的匯入和匯出模組。請參閱下方說明,瞭解如何執行這項操作。

const loadImportAndExport = () => {
  if ('chooseFileSystemEntries' in window) {
    Promise.all([
      import('./import_image.mjs'),
      import('./export_image.mjs'),
    ]);
  } else {
    Promise.all([
      import('./import_image_legacy.mjs'),
      import('./export_image_legacy.mjs'),
    ]);
  }
};

不過,在深入探討 File System Access API 的詳細資料之前,讓我先快速介紹漸進式增強模式。在目前不支援 File System Access API 的瀏覽器上,我會載入舊版指令碼。您可以在下方看到 Firefox 和 Safari 的網路分頁。

Safari 網頁檢查器,顯示正在載入舊版檔案。
Safari Web Inspector 網路分頁。
Firefox 開發人員工具:載入舊版檔案。
Firefox 開發人員工具網路分頁。

不過,在 Chrome 上,只有支援這個 API 的瀏覽器會載入新的指令碼。 這項功能之所以能順利實現,要歸功於所有新式瀏覽器都支援動態 import()。就像我之前說的,現在草地很綠色。

Chrome 開發人員工具:顯示正在載入的現代化檔案。
Chrome 開發人員工具網路分頁。

File System Access API

我已解決此問題,接著來看看根據 File System Access API 實際實作的成果。如要匯入圖片,我會呼叫 window.chooseFileSystemEntries(),並傳遞 accepts 屬性,指出我要的圖片檔案。系統支援副檔名和 MIME 類型。這會產生檔案句柄,我可以透過呼叫 getFile() 取得實際檔案。

const importImage = async () => {
  try {
    const handle = await window.chooseFileSystemEntries({
      accepts: [
        {
          description: 'Image files',
          mimeTypes: ['image/*'],
          extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'],
        },
      ],
    });
    return handle.getFile();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

匯出圖片的做法幾乎相同,但這次我需要將 'save-file' 的類型參數傳遞至 chooseFileSystemEntries() 方法。畫面上會顯示檔案儲存對話方塊。 在檔案開啟時,由於預設為 'open-file',因此不需要這麼做。我設定的 accepts 參數與先前類似,但這次只限於 PNG 圖片。我再次取得檔案控制代碼,但這次不是取得檔案,而是透過呼叫 createWritable() 建立可寫入的串流。接下來,我會將 Blob (即賀卡圖片) 寫入檔案。最後,我關閉可寫入的串流。

所有操作都可能失敗:磁碟可能沒有空間,可能發生寫入或讀取錯誤,也可能是使用者取消檔案對話方塊。因此,我一律會將呼叫納入 try...catch 陳述式中。

const exportImage = async (blob) => {
  try {
    const handle = await window.chooseFileSystemEntries({
      type: 'save-file',
      accepts: [
        {
          description: 'Image file',
          extensions: ['png'],
          mimeTypes: ['image/png'],
        },
      ],
    });
    const writable = await handle.createWritable();
    await writable.write(blob);
    await writable.close();
  } catch (err) {
    console.error(err.name, err.message);
  }
};

透過 File System Access API 使用漸進式增強功能,我可以像以前一樣開啟檔案。匯入的檔案會直接繪製到畫布上。我可以編輯內容,最後透過真正的儲存對話方塊儲存內容,並選擇檔案名稱和儲存位置。現在檔案已準備好進行永久保存。

顯示檔案開啟對話方塊的 Fugu Greetings 應用程式。
檔案開啟對話方塊。
Fugu Greetings 應用程式現在已匯入圖片。
匯入的圖片。
使用經過修改的圖片的 Fugu Greetings 應用程式。
將修改過的圖片儲存到新檔案。

Web Share 和 Web Share Target API

除了永久儲存之外,我可能還想分享賀卡。 這正是 Web Share APIWeb Share Target API 可讓我執行的操作。行動作業系統和最近的電腦作業系統都已獲得內建的分享機制。舉例來說,以下是 macOS 電腦版 Safari 的分享頁面,是由我的網誌上的文章觸發。點選「分享文章」按鈕後,你可以透過 macOS 訊息應用程式等方式,與朋友分享文章連結。

文章的「分享」按鈕觸發了電腦版 Safari 的分享工作表
macOS 電腦版 Safari 上的 Web Share API。

用來執行這類程式碼的程式碼非常簡單。我呼叫 navigator.share(),並在物件中傳遞選用的 titletexturl。但如果我想附加圖片,該怎麼做?Web Share API 的第 1 級目前不支援這項功能。好消息是,Web Share Level 2 新增了檔案分享功能。

try {
  await navigator.share({
    title: 'Check out this article:',
    text: `"${document.title}" by @tomayac:`,
    url: document.querySelector('link[rel=canonical]').href,
  });
} catch (err) {
  console.warn(err.name, err.message);
}

讓我示範如何在 Fugu 賀卡應用程式中使用這項功能。首先,我需要準備 data 物件,其中包含由一個 Blob 組成的 files 陣列,然後再準備 titletext。接下來,我會使用新的 navigator.canShare() 方法,這是最佳做法,這個方法會按照名稱執行:它會告訴我,我要分享的 data 物件是否可由瀏覽器分享。如果 navigator.canShare() 告知可以分享資料,我就可以像先前一樣呼叫 navigator.share()。由於所有內容都可能失敗,我再次使用 try...catch 區塊。

const share = async (title, text, blob) => {
  const data = {
    files: [
      new File([blob], 'fugu-greeting.png', {
        type: blob.type,
      }),
    ],
    title: title,
    text: text,
  };
  try {
    if (!(navigator.canShare(data))) {
      throw new Error("Can't share data.", data);
    }
    await navigator.share(data);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

和先前一樣,我使用漸進式增強功能。如果 'share''canShare' 都存在於 navigator 物件中,我才會繼續操作,並透過動態 import() 載入 share.mjs。在行動版 Safari 等只符合兩個條件之一的瀏覽器上,我不會載入該功能。

const loadShare = () => {
  if ('share' in navigator && 'canShare' in navigator) {
    import('./share.mjs');
  }
};

在 Fugu Greetings 中,如果我在 Android 版 Chrome 等支援的瀏覽器上輕觸「Share」按鈕,內建的分享頁面就會開啟。舉例來說,我可以選擇 Gmail,電子郵件撰寫工具小工具就會彈出,並附上圖片。

OS 層級分享工作表顯示各種可分享圖片的應用程式。
選擇要共用檔案的應用程式。
附有圖片的 Gmail 電子郵件撰寫小工具。
檔案會附加至 Gmail 撰寫工具中的新電子郵件。

Contact Picker API

接下來,我想談談聯絡人,也就是裝置的電話簿或聯絡人管理應用程式。在寫賀卡時,不一定能正確寫出對方的姓名。舉例來說,我有一位朋友 Sergey,他偏好使用西里爾字母拼寫自己的名字。我使用德文 QWERTZ 鍵盤,不知道該如何輸入名稱。這正是 Contact Picker API 可以解決的問題。因為我的好友已經儲存在手機的聯絡人應用程式中, 是透過 Contacts Picker API 儲存,所以我可以從網頁上 使用我的聯絡人

首先,我需要指定要存取的屬性清單。在本例中,我只需要名稱,但在其他用途中,我可能會想取得電話號碼、電子郵件、顯示圖片圖示或實際地址。接下來,我會設定 options 物件並將 multiple 設為 true,以便選取多個項目。最後,我可以呼叫 navigator.contacts.select(),針對使用者選取的聯絡人傳回想要的屬性。

const getContacts = async () => {
  const properties = ['name'];
  const options = { multiple: true };
  try {
    return await navigator.contacts.select(properties, options);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

到目前為止,您可能已經瞭解這個模式:我只會在 API 實際支援時載入檔案。

if ('contacts' in navigator) {
  import('./contacts.mjs');
}

在 Fugu Greeting 中,當我輕觸「Contacts」按鈕,然後選擇兩個最佳好友時然後將他們的名字繪製在賀卡上。

聯絡人挑選器,顯示通訊錄中的兩位聯絡人名稱。
從通訊錄中選取聯絡人選擇器。
在賀卡上繪製的兩位先前選取聯絡人的姓名。
接著,這兩個名稱就會繪製到賀卡上。

非同步 Clipboard API

接下來要介紹複製和貼上。我們軟體開發人員最常用的操作之一就是複製和貼上。身為賀卡作者,我有時也會想這麼做。我們可以將圖片貼到我正在處理的問候卡中,或是複製自己的問候卡,以便從其他地方繼續編輯。Async Clipboard API 支援文字和圖片。讓我逐步說明如何在 Fugu Greetings 應用程式中新增複製和貼上功能。

為了將內容複製到系統的剪貼簿,我需要寫入資料。 navigator.clipboard.write() 方法會將剪貼簿項目陣列做為參數。每個剪貼簿項目基本上都是一個物件,其中 Blob 做為值,Blob 的類型則做為鍵。

const copy = async (blob) => {
  try {
    await navigator.clipboard.write([
      new ClipboardItem({
        [blob.type]: blob,
      }),
    ]);
  } catch (err) {
    console.error(err.name, err.message);
  }
};

如要貼上內容,我需要循環處理透過呼叫 navigator.clipboard.read() 取得的剪貼簿項目。這是因為剪貼簿中可能有多個剪貼簿項目,且這些項目的表示方式也不同。每個剪貼簿項目都有一個 types 欄位,可告知可用資源的 MIME 類型。我呼叫剪貼簿項目的 getType() 方法,傳遞先前取得的 MIME 類型。

const paste = async () => {
  try {
    const clipboardItems = await navigator.clipboard.read();
    for (const clipboardItem of clipboardItems) {
      try {
        for (const type of clipboardItem.types) {
          const blob = await clipboardItem.getType(type);
          return blob;
        }
      } catch (err) {
        console.error(err.name, err.message);
      }
    }
  } catch (err) {
    console.error(err.name, err.message);
  }
};

現在就不用這麼說我只在支援的瀏覽器上執行這項操作。

if ('clipboard' in navigator && 'write' in navigator.clipboard) {
  import('./clipboard.mjs');
}

那麼,這項功能在實際應用上又是如何運作呢?我在 macOS 預覽應用程式中開啟一張圖片,並將其複製到剪貼簿。當我按一下「貼上」時,Fugu Greetings 應用程式會詢問我是否要允許應用程式查看剪貼簿中的文字和圖片。

Fugu Greetings 應用程式顯示剪貼簿權限提示。
剪貼簿權限提示。

最後,在接受權限後,圖片就會貼到應用程式中。反之亦然。讓我將問候卡複製到剪貼簿。接著開啟預覽功能,並依序點選「File」和「New from Clipboard」,即可將問候卡貼到新的未命名圖片中。

macOS 預覽應用程式,其中含有未命名且剛貼上的圖片。
貼到 macOS 預覽應用程式中的圖片。

徽章 API

另一個實用的 API 是 Badging API。作為可安裝的 PWA,Fugu Greetings 當然有應用程式圖示,可讓使用者放置在應用程式匣或主畫面上。在 Fugu Greetings 中將其當做筆劃筆觸再簡單示範 API。我已新增事件監聽器,只要 pointerdown 事件發生,就會遞增筆劃計數器,然後設定更新的圖示徽章。每次清除畫布時,計數器都會重設,徽章也會移除。

let strokes = 0;

canvas.addEventListener('pointerdown', () => {
  navigator.setAppBadge(++strokes);
});

clearButton.addEventListener('click', () => {
  strokes = 0;
  navigator.setAppBadge(strokes);
});

這項功能是漸進式增強功能,因此載入邏輯與平常相同。

if ('setAppBadge' in navigator) {
  import('./badge.mjs');
}

在這個範例中,我使用一筆劃畫出 1 到 7 的數字。圖示上的徽章計數器現在是 7 個。

在賀卡上畫上從一到七的數字,每個數字都只用一筆劃畫出。
使用七個筆劃繪製 1 到 7 的數字。
Fugu Greetings 應用程式中的徽章圖示 7。
畫筆筆劃計數器的格式為應用程式圖示徽章。

Periodic Background Sync API

想每天都能有新鮮事物嗎? Fugu Greetings 應用程式提供的實用功能,是每天早上都能為您帶來靈感,提供新的背景圖片,讓您開始製作賀卡。應用程式會使用 Periodic Background Sync API 達成這項目標。

第一步是在服務工作者註冊程序中註冊定期同步處理事件。它會監聽名為 'image-of-the-day' 的同步標記,且最短間隔為一天,因此使用者每 24 小時就能取得新的背景圖片。

const registerPeriodicBackgroundSync = async () => {
  const registration = await navigator.serviceWorker.ready;
  try {
    registration.periodicSync.register('image-of-the-day-sync', {
      // An interval of one day.
      minInterval: 24 * 60 * 60 * 1000,
    });
  } catch (err) {
    console.error(err.name, err.message);
  }
};

第二個步驟是在 Service Worker 中監聽 periodicsync 事件。如果事件代碼為 'image-of-the-day',也就是先前註冊的代碼,則系統會透過 getImageOfTheDay() 函式擷取當天的圖片,並將結果傳播至所有用戶端,以便更新其畫布和快取。

self.addEventListener('periodicsync', (syncEvent) => {
  if (syncEvent.tag === 'image-of-the-day-sync') {
    syncEvent.waitUntil(
      (async () => {
        const blob = await getImageOfTheDay();
        const clients = await self.clients.matchAll();
        clients.forEach((client) => {
          client.postMessage({
            image: blob,
          });
        });
      })()
    );
  }
});

這同樣是漸進式強化,因此只有在瀏覽器支援 API 時,才會載入程式碼。這適用於用戶端程式碼和服務工作程式碼。在未支援的瀏覽器上,系統不會載入這兩個元素。請注意,我在 Service Worker 中使用的是傳統的 importScripts(),而非動態 import() (目前 Service Worker 的環境不支援動態 import())。

// In the client:
const registration = await navigator.serviceWorker.ready;
if (registration && 'periodicSync' in registration) {
  import('./periodic_background_sync.mjs');
}
// In the service worker:
if ('periodicSync' in self.registration) {
  importScripts('./image_of_the_day.mjs');
}

在 Fugu Greetings 中,按下「Wallpaper」按鈕即可查看當天的賀卡圖片,這些圖片會透過 Periodic Background Sync API 每天更新。

Fugu Greetings 應用程式,當天新推出的賀卡圖片。
按下「桌布」按鈕,即可顯示當日圖片。

Notification Triggers API

有時即使有許多靈感,您仍需要一點動力才能完成開始的問候卡。這是由 Notification Triggers API 啟用的功能。使用者可以輸入要提醒自己完成賀卡的時間。屆時我將收到正在等待的問候卡通知。

在提示目標時間後,應用程式會使用 showTrigger 排定通知。這可以是先前選取的目標日期 TimestampTrigger。提醒通知會在本機觸發,無需網路或伺服器端。

const targetDate = promptTargetDate();
if (targetDate) {
  const registration = await navigator.serviceWorker.ready;
  registration.showNotification('Reminder', {
    tag: 'reminder',
    body: "It's time to finish your greeting card!",
    showTrigger: new TimestampTrigger(targetDate),
  });
}

如同我先前所示,這是漸進式增強功能,因此程式碼只會依條件載入。

if ('Notification' in window && 'showTrigger' in Notification.prototype) {
  import('./notification_triggers.mjs');
}

當我在 Fugu Greetings 中勾選「Reminder」核取方塊時,系統會顯示提示,詢問我要何時收到提醒,以便完成賀卡。

Fugu Greetings 應用程式,其中提示會詢問使用者何時要收到提醒,以便完成賀卡。
安排提醒您完成賀卡的本地通知。

當排定的通知在 Fugu Greetings 中觸發時,系統會顯示通知,就像其他通知一樣。但如同我先前所述,這項功能不需要網路連線。

macOS 通知中心顯示 Fugu Greetings 觸發的通知。
觸發的通知會顯示在 macOS 通知中心。

Wake Lock API

我也想加入 Wake Lock API。有時你只需要盯著螢幕看,直到靈感來臨。最糟的情況是螢幕關閉,Wake Lock API 可避免這種情況發生。

第一步是使用 navigator.wakelock.request method() 取得喚醒鎖定。我會傳遞 'screen' 字串,以取得螢幕 Wake Lock。然後新增事件監聽器,以便在 Wake Lock 釋放時收到通知。舉例來說,當分頁瀏覽權限變更時,就可能發生這種情況。 如果發生這種情況,我可以在分頁再次顯示時重新取得 Wake Lock。

let wakeLock = null;
const requestWakeLock = async () => {
  wakeLock = await navigator.wakeLock.request('screen');
  wakeLock.addEventListener('release', () => {
    console.log('Wake Lock was released');
  });
  console.log('Wake Lock is active');
};

const handleVisibilityChange = () => {
  if (wakeLock !== null && document.visibilityState === 'visible') {
    requestWakeLock();
  }
};

document.addEventListener('visibilitychange', handleVisibilityChange);
document.addEventListener('fullscreenchange', handleVisibilityChange);

是的,這是漸進式增強功能,因此只需在瀏覽器支援 API 時載入即可。

if ('wakeLock' in navigator && 'request' in navigator.wakeLock) {
  import('./wake_lock.mjs');
}

在 Fugu Greetings 中,有一個「Insomnia」核取方塊,勾選後螢幕就會保持喚醒狀態。

如果勾選 insomnia 核取方塊,螢幕就會保持喚醒狀態。
「Insomnia」核取方塊可讓應用程式保持啟用狀態。

Idle Detection API

有時,即使你盯著螢幕看了好幾個小時,也無法解決問題,也不知道該如何處理賀卡。Idle Detection API 可讓應用程式偵測使用者的閒置時間。如果使用者閒置時間過長,應用程式會重設為初始狀態,並清除面板。這個 API 目前需要通知權限才能使用,因為許多實際的閒置偵測用途都與通知相關,例如只將通知傳送至使用者目前正在使用的裝置。

確認已授予通知權限後,我會將閒置偵測器例項化。我會註冊事件監聽器來監聽閒置變更,包括使用者和畫面狀態。使用者可處於有效或閒置狀態,也能解鎖或鎖定螢幕。如果使用者處於閒置狀態,畫布會清空。 我將閒置偵測器的門檻設為 60 秒。

const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
  const userState = idleDetector.userState;
  const screenState = idleDetector.screenState;
  console.log(`Idle change: ${userState}, ${screenState}.`);
  if (userState === 'idle') {
    clearCanvas();
  }
});

await idleDetector.start({
  threshold: 60000,
  signal,
});

如往常,我只會在瀏覽器支援時載入這段程式碼。

if ('IdleDetector' in window) {
  import('./idle_detection.mjs');
}

在 Fugu Greetings 應用程式中,如果勾選「Ephemeral」核取方塊,且使用者閒置太久,畫布就會清除。

使用者閒置過久後,Fugu Greetings 應用程式會清除畫布。
勾選「暫時」核取方塊,且使用者閒置太久,系統就會清除畫布。

Closing

呼,你好!在一個範例應用程式中就包含這麼多 API。請注意,我絕不會讓使用者為瀏覽器不支援的功能支付下載費用。透過漸進增強原則,我可以確保只載入相關程式碼。由於 HTTP/2 的請求成本低廉,因此這類模式應可適用於許多應用程式,但您可能會考慮為大型應用程式使用 bundler。

Chrome 開發人員工具「網路」面板只會顯示目前瀏覽器支援的程式碼檔案要求。
Chrome 開發人員工具「Network」分頁只會顯示目前瀏覽器支援的程式碼檔案要求。

由於並非所有平台都支援所有功能,因此應用程式在各瀏覽器上的外觀可能略有不同,但核心功能始終存在,並會根據特定瀏覽器的功能逐步強化。請注意,即使在同一個瀏覽器中,這些功能也可能會因應用程式是以已安裝的應用程式或在瀏覽器分頁中執行而有所不同。

在 Android Chrome 上執行的 Fugu Greetings,顯示許多可用功能。
在 Android 版 Chrome 上執行的 Fugu Greetings
Fugu Greetings 目前在電腦版 Safari 上執行,顯示較少可用功能。
Fugu Greetings 在電腦版 Safari 上執行。
在 Chrome 電腦版上執行的 Fugu Greetings,顯示許多可用功能。
Fugu Greetings 在電腦版 Chrome 上執行。

如果您對 Fugu Greetings 應用程式有興趣,請前往 GitHub 尋找並分支

GitHub 上的 Fugu Greetings 存放區。
GitHub 上的 Fugu Greetings 應用程式。

Chromium 團隊正努力改善進階 Fugu API 的效能。在應用程式開發過程中,我會套用漸進式強化功能,確保所有使用者都能享有良好且穩固的基本體驗,而使用支援更多網頁平台 API 的瀏覽器的使用者,則能享有更優質的體驗。期待看到您在應用程式中使用漸進式增強功能的成果。

特別銘謝

感謝 Christian LiebelHemanth HM 的成員都對 Fugu Greeting 大有貢獻。 本文由 Joe MedleyKayce Basques 審查。Jake Archibald 協助我找出服務工作者情境中動態 import() 的問題。