Meningkatkan Progressive Web App Anda secara bertahap

Membangun untuk browser modern dan meningkat secara bertahap seperti tahun 2003

Pada bulan Maret 2003 yang lalu, Nick Finck dan Steve Champeon mengejutkan dunia desain web dengan konsep progressive enhancement, sebuah strategi untuk desain web yang menekankan pemuatan konten halaman web inti terlebih dahulu, dan yang kemudian secara progresif menambahkan lapisan presentasi dan fitur yang lebih bernuansa dan secara teknis ketat pada konten. Sementara pada tahun 2003, {i>progressive enhancement<i} adalah tentang penggunaan — pada saat itu — fitur CSS modern, JavaScript yang tidak mengganggu, dan bahkan hanya Scalable Vector Graphics. Progressive enhancement pada tahun 2020 dan seterusnya adalah tentang penggunaan kemampuan browser modern.

Desain web inklusif untuk masa depan dengan {i>progressive enhancement<i}. Slide judul dari presentasi asli Finck dan Champeon.
Slide: Desain Web Inklusif untuk Masa Depan dengan Peningkatan Progresif. (Sumber)

JavaScript Modern

Berbicara tentang JavaScript, situasi dukungan browser untuk fitur JavaScript inti ES 2015 terbaru sangat bagus. Standar baru ini mencakup promise, modul, class, literal template, fungsi panah, let dan const, parameter default, generator, tugas destrukturisasi, rest dan penyebaran, Map/Set, WeakMap/WeakSet, dan banyak lagi. Semuanya didukung.

Tabel dukungan CanIUse untuk fitur ES6 yang menunjukkan dukungan di semua browser utama.
Tabel dukungan browser ECMAScript 2015 (ES6). (Sumber)

Fungsi asinkron, fitur ES 2017, dan salah satu favorit pribadi saya, dapat digunakan di semua browser utama. Kata kunci async dan await memungkinkan perilaku asinkron berbasis promise untuk ditulis dengan gaya yang lebih sederhana, sehingga tidak perlu mengonfigurasi rantai promise secara eksplisit.

Tabel dukungan CanIUse untuk fungsi asinkron yang menunjukkan dukungan di semua browser utama.
Tabel dukungan browser fungsi Asinkron. (Sumber)

Selain itu, penambahan bahasa ES 2020 yang sangat baru seperti rantai opsional dan penggabungan null telah mencapai dukungan dengan sangat cepat. Anda dapat melihat contoh kode di bawah. Jika menyangkut fitur JavaScript inti, rumputnya tidak mungkin lebih hijau daripada saat ini.

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
Gambar latar belakang rumput hijau Windows XP yang ikonis.
Rumputnya berwarna hijau jika menyangkut fitur JavaScript inti. (Screenshot produk Microsoft, digunakan dengan izin.)

Aplikasi contoh: Fugu Greetings

Untuk artikel ini, saya menggunakan PWA sederhana, yang disebut Fugu Greetings (GitHub). Nama aplikasi ini merupakan tip dari Project Fugu 🐡, sebuah upaya untuk memberikan semua kemampuan aplikasi Android/iOS/desktop kepada web. Anda dapat membaca selengkapnya tentang project di halaman landing.

Fugu Greetings adalah aplikasi menggambar yang memungkinkan Anda membuat kartu ucapan virtual dan mengirimkannya kepada orang terkasih. Ini contoh konsep inti PWA. Metode ini andal dan diaktifkan sepenuhnya secara offline. Jadi, meskipun tidak memiliki jaringan, Anda masih dapat menggunakannya. Aplikasi ini juga Dapat diinstal ke layar utama perangkat dan terintegrasi lancar dengan sistem operasi sebagai aplikasi mandiri.

PWA Fugu Greetings dengan gambar yang menyerupai logo komunitas PWA.
Aplikasi contoh Fugu Greetings.

{i>Progressive enhancement <i}

Setelah memahami hal tersebut, kini saatnya membahas tentang peningkatan progresif. Glosarium Dokumen Web MDN menentukan konsep sebagai berikut:

Progressive enhancement adalah filosofi desain yang memberikan dasar fungsi dan konten penting kepada sebanyak mungkin pengguna, sekaligus memberikan pengalaman terbaik hanya kepada pengguna browser paling modern yang dapat menjalankan semua kode yang diperlukan.

Deteksi fitur umumnya digunakan untuk menentukan apakah browser dapat menangani fungsi yang lebih modern, sedangkan polyfill sering digunakan untuk menambahkan fitur yang tidak ada di JavaScript.

[…]

Progressive enhancement adalah teknik bermanfaat yang memungkinkan developer web berfokus pada pengembangan situs terbaik sekaligus membuat situs tersebut berfungsi di beberapa agen pengguna yang tidak dikenal. Degradasi halus berkaitan, tetapi bukan hal yang sama dan sering dilihat berlawanan arah dengan progressive enhancement. Pada kenyataannya, kedua pendekatan tersebut valid dan sering kali dapat saling melengkapi.

Kontributor MN

Memulai setiap kartu ucapan dari awal bisa sangat rumit. Jadi, mengapa tidak memiliki fitur yang memungkinkan pengguna untuk mengimpor gambar, dan memulainya dari sana? Dengan pendekatan tradisional, Anda harus menggunakan elemen <input type=file> untuk mewujudkannya. Pertama, Anda harus membuat elemen, menetapkan type-nya ke 'file', dan menambahkan jenis MIME ke properti accept, lalu "mengklik" elemen tersebut secara terprogram dan memproses perubahan. Saat Anda memilih gambar, gambar tersebut akan diimpor langsung ke kanvas.

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();
  });
};

Jika ada fitur import, kemungkinan harus ada fitur import sehingga pengguna dapat menyimpan kartu ucapan mereka secara lokal. Cara tradisional untuk menyimpan file adalah dengan membuat link anchor dengan atribut download dan dengan URL blob sebagai href-nya. Anda juga telah "mengklik" objek secara terprogram untuk memicu download, dan, untuk mencegah kebocoran memori, semoga tidak lupa mencabut URL objek 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();
};

Tapi tunggu sebentar. Secara mental, Anda belum “mengunduh” kartu ucapan, Anda telah "menyimpannya". Alih-alih menampilkan dialog "simpan" yang memungkinkan Anda memilih lokasi untuk meletakkan file, browser telah mendownload kartu ucapan secara langsung tanpa interaksi pengguna dan telah memasukkannya langsung ke folder Download Anda. Hal ini tidak bagus.

Bagaimana jika ada cara yang lebih baik? Bagaimana jika Anda bisa saja membuka file lokal, mengeditnya, lalu menyimpan modifikasinya, baik ke file baru, atau kembali ke file asli yang pertama kali Anda buka? Ternyata ada. File System Access API memungkinkan Anda membuka dan membuat file serta direktori, serta mengubah dan menyimpannya .

Jadi, bagaimana cara mendeteksi fitur API? File System Access API mengekspos metode baru window.chooseFileSystemEntries(). Akibatnya, saya perlu memuat berbagai modul impor dan ekspor secara kondisional bergantung pada apakah metode ini tersedia atau tidak. Kami telah menunjukkan cara melakukannya di bawah.

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'),
    ]);
  }
};

Tapi sebelum mendalami detail File System Access API, saya akan menyoroti pola {i>progressive enhancement<i} di sini. Saya memuat skrip lama di browser yang saat ini tidak mendukung File System Access API. Anda dapat melihat tab jaringan Firefox dan Safari di bawah.

Safari Web Inspector menampilkan file lama yang sedang dimuat.
Tab jaringan Safari Web Inspector.
Firefox Developer Tools menampilkan file lama yang dimuat.
Tab jaringan Firefox Developer Tools.

Namun, di Chrome, browser yang mendukung API, hanya skrip baru yang dimuat. Hal ini dimungkinkan secara elegan berkat import() dinamis, yang didukung oleh semua browser modern. Seperti yang saya katakan sebelumnya, rumputnya cukup hijau akhir-akhir ini.

Chrome DevTools menampilkan file modern yang dimuat.
Tab jaringan Chrome DevTools.

File System Access API

Setelah saya mengatasi hal ini, kini saatnya melihat implementasi aktual berdasarkan File System Access API. Untuk mengimpor gambar, saya memanggil window.chooseFileSystemEntries() dan meneruskan properti accepts tempat saya menginginkan file gambar. Baik ekstensi file maupun jenis MIME didukung. Tindakan ini menghasilkan handle file, tempat saya bisa mendapatkan file sebenarnya dengan memanggil 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);
  }
};

Proses mengekspor gambar hampir sama, tetapi kali ini saya harus meneruskan parameter jenis 'save-file' ke metode chooseFileSystemEntries(). Dari sini saya mendapatkan dialog penyimpanan file. Saat file terbuka, hal ini tidak diperlukan karena 'open-file' adalah default. Saya menyetel parameter accepts mirip dengan sebelumnya, tetapi kali ini terbatas hanya untuk gambar PNG. Sekali lagi, saya mendapatkan kembali handle file. Namun, kali ini saya membuat stream yang dapat ditulis dengan memanggil createWritable(), bukan mendapatkan filenya. Selanjutnya, saya menulis blob, yang merupakan gambar kartu ucapan saya, ke file tersebut. Terakhir, saya menutup feed yang dapat ditulis tersebut.

Semuanya bisa selalu gagal: Disk bisa kehabisan ruang, mungkin terjadi error tulis atau baca, atau mungkin pengguna membatalkan dialog file. Itulah sebabnya saya selalu menggabungkan panggilan dalam pernyataan 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);
  }
};

Menggunakan {i>progressive enhancement<i} dengan File System Access API, saya dapat membuka file seperti sebelumnya. File yang diimpor akan digambar langsung ke kanvas. Saya dapat melakukan pengeditan dan akhirnya menyimpannya dengan kotak dialog penyimpanan sungguhan untuk memilih nama dan lokasi penyimpanan file. Sekarang file tersebut siap untuk dipertahankan untuk selamanya.

Aplikasi Fugu Greetings dengan dialog membuka file.
Dialog pembukaan file.
Aplikasi Fugu Greetings kini dengan gambar yang diimpor.
Gambar yang diimpor.
Aplikasi Fugu Greetings dengan gambar yang dimodifikasi.
Menyimpan gambar yang telah diubah ke file baru.

Web Share API dan Web Share Target API

Selain menyimpan untuk selamanya, mungkin aku sebenarnya ingin berbagi kartu ucapan. Ini adalah fitur yang dapat saya lakukan oleh Web Share API dan Web Share Target API. Sistem operasi seluler, dan baru-baru ini, telah memperoleh mekanisme berbagi bawaan. Misalnya, di bawah ini adalah sheet berbagi Safari desktop di macOS yang dipicu dari artikel di blog saya. Saat mengklik tombol Bagikan Artikel, Anda dapat membagikan link artikel kepada teman, misalnya, melalui aplikasi Message macOS.

Sheet berbagi Desktop Safari di macOS dipicu dari tombol Bagikan di artikel
Web Share API di Safari desktop di macOS.

Kode untuk melakukannya cukup mudah. Saya memanggil navigator.share() dan meneruskan title, text, dan url opsional dalam objek. Tapi bagaimana jika saya ingin melampirkan gambar? Level 1 dari Web Share API belum mendukung fitur ini. Kabar baiknya adalah Web Share Level 2 memiliki tambahan kemampuan berbagi file.

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);
}

Mari saya tunjukkan cara kerjanya dengan aplikasi kartu Ucapan Fugu. Pertama, saya harus menyiapkan objek data dengan array files yang terdiri dari satu blob, lalu title dan text. Selanjutnya, sebagai praktik terbaik, saya menggunakan metode navigator.canShare() baru yang sesuai dengan namanya: Metode ini memberi tahu saya apakah objek data yang saya coba bagikan secara teknis dapat dibagikan oleh browser. Jika navigator.canShare() memberitahukan bahwa data dapat dibagikan, saya siap memanggil navigator.share() seperti sebelumnya. Karena semuanya bisa gagal, saya menggunakan blok try...catch lagi.

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);
  }
};

Seperti sebelumnya, saya menggunakan {i>progressive enhancement<i}. Jika 'share' dan 'canShare' ada di objek navigator, hanya saya yang akan melanjutkan dan memuat share.mjs melalui import() dinamis. Pada browser seperti Safari seluler yang hanya memenuhi salah satu dari dua kondisi tersebut, saya tidak memuat fungsi tersebut.

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

Di Fugu Greetings, jika saya mengetuk tombol Share pada browser pendukung seperti Chrome di Android, sheet berbagi bawaan akan terbuka. Misalnya, saya dapat memilih Gmail, lalu widget email composer akan muncul dengan gambar terlampir.

Sheet berbagi tingkat OS yang menampilkan berbagai aplikasi untuk membagikan gambar.
Memilih aplikasi yang akan dijadikan tujuan berbagi file.
Widget tulis email Gmail dengan gambar terlampir.
File akan dilampirkan ke email baru di komposer Gmail.

Contact Picker API

Selanjutnya, saya ingin membahas kontak, yaitu buku alamat perangkat atau aplikasi pengelola kontak. Saat menulis kartu ucapan, mungkin tidak selalu mudah menulis nama seseorang dengan benar. Misalnya, saya punya teman Sergey yang lebih suka namanya dieja dalam huruf Sirilik. Saya menggunakan keyboard QWERTZ bahasa Jerman dan tidak tahu cara mengetik namanya. Ini adalah masalah yang dapat diselesaikan oleh Contact Picker API. Karena teman saya disimpan di aplikasi kontak ponsel, melalui Contacts Picker API, saya dapat mengakses kontak dari web.

Pertama, saya perlu menentukan daftar properti yang ingin saya akses. Dalam hal ini, saya hanya ingin namanya, tetapi untuk kasus penggunaan lainnya, saya mungkin tertarik dengan nomor telepon, email, ikon avatar, atau alamat fisik. Selanjutnya, saya mengonfigurasi objek options dan menetapkan multiple ke true, sehingga saya dapat memilih lebih dari satu entri. Terakhir, saya dapat memanggil navigator.contacts.select(), yang menampilkan properti yang diinginkan untuk kontak yang dipilih pengguna.

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);
  }
};

Dan sekarang Anda mungkin telah mempelajari polanya: Saya hanya memuat file ketika API benar-benar didukung.

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

Di Fugu Greeting, saat saya mengetuk tombol Contacts dan memilih dua sahabat terbaik saya, 奇 佋勋n l l o l o l o r dan 劳伦斯·爱德华·"拉里"·佩奇, Anda dapat melihat bagaimana nama kontak mereka terbatas, bukan hanya nama kontak mereka. Nama mereka kemudian digambar di kartu ucapan saya.

Pemilih kontak menampilkan nama dua kontak di buku alamat.
Memilih dua nama dengan pemilih kontak dari buku alamat.
Nama dua kontak yang sebelumnya dipilih yang digambar di kartu ucapan.
Kedua nama tersebut kemudian akan digambar pada kartu ucapan.

Asynchronous Clipboard API

Selanjutnya adalah menyalin dan menempel. Salah satu operasi favorit kita sebagai pengembang perangkat lunak adalah {i>copy<i} dan {i>paste<i}. Sebagai penulis kartu ucapan, terkadang saya mungkin ingin melakukan hal yang sama. Sebaiknya saya menempel gambar ke kartu ucapan yang sedang dikerjakan, atau menyalin kartu ucapan agar dapat terus mengeditnya dari tempat lain. Async Clipboard API mendukung teks dan gambar. Saya akan memandu Anda tentang cara menambahkan dukungan salin dan tempel ke aplikasi Fugu Greetings.

Untuk menyalin sesuatu ke {i>clipboard<i} sistem, saya perlu menulis ke sana. Metode navigator.clipboard.write() menggunakan array item papan klip sebagai parameter. Setiap item papan klip pada dasarnya adalah objek dengan blob sebagai nilai, dan jenis blob sebagai kunci.

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

Untuk menempelkan, saya perlu melakukan loop pada item papan klip yang saya peroleh dengan memanggil navigator.clipboard.read(). Alasannya adalah beberapa item papan klip mungkin berada di papan klip dengan representasi yang berbeda. Setiap item papan klip memiliki kolom types yang memberi tahu saya jenis MIME dari resource yang tersedia. Saya memanggil metode getType() item papan klip, dengan meneruskan jenis MIME yang saya peroleh sebelumnya.

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);
  }
};

Dan hampir tidak perlu dikatakan sekarang. Saya hanya melakukan ini pada {i>browser<i} pendukung.

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

Jadi, bagaimana cara kerjanya? Saya membuka gambar di aplikasi Pratinjau macOS dan menyalinnya ke {i>clipboard<i}. Saat mengklik Paste, aplikasi Fugu Greetings akan menanyakan apakah saya ingin mengizinkan aplikasi melihat teks dan gambar di papan klip.

Aplikasi Fugu Greetings menampilkan perintah izin papan klip.
Dialog izin papan klip.

Terakhir, setelah menerima izin tersebut, gambar tersebut kemudian ditempelkan ke dalam aplikasi. Hal yang sebaliknya juga berlaku. Saya akan menyalin kartu ucapan ke {i>clipboard<i}. Ketika saya membuka Pratinjau dan mengklik File kemudian New from Clipboard, kartu ucapan akan ditempelkan ke gambar baru tanpa judul.

Aplikasi Pratinjau macOS dengan gambar yang baru saja ditempelkan tanpa judul.
Gambar yang ditempelkan ke aplikasi Pratinjau macOS.

Badging API

API lain yang berguna adalah Badging API. Sebagai PWA yang dapat diinstal, Fugu Greetings tentu saja memiliki ikon aplikasi yang dapat ditempatkan pengguna di dok aplikasi atau layar utama. Cara yang menyenangkan dan mudah untuk mendemonstrasikan API adalah dengan (ab) menggunakannya di Fugu Greetings sebagai penghitung goresan pena. Saya telah menambahkan pemroses peristiwa yang menambahkan penghitung goresan pena setiap kali peristiwa pointerdown terjadi, lalu menetapkan badge ikon yang diperbarui. Setiap kali kanvas dihapus, penghitung akan direset, dan badge dihapus.

let strokes = 0;

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

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

Fitur ini merupakan peningkatan progresif, sehingga logika pemuatan berjalan seperti biasa.

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

Dalam contoh ini, saya menggambar angka dari satu sampai tujuh, menggunakan satu goresan pena per angka. Penghitung lencana di ikon sekarang ada di tujuh.

Angka dari satu hingga tujuh digambar pada kartu ucapan, masing-masing hanya dengan satu goresan pena.
Menggambar angka dari 1 sampai 7, menggunakan tujuh goresan pena.
Ikon badge di aplikasi Fugu Greetings yang menampilkan angka 7.
Penghitung goresan pena dalam bentuk badge ikon aplikasi.

Periodic Background Sync API

Ingin memulai hari dengan hal baru? Salah satu fitur menarik dari aplikasi Fugu Greetings adalah aplikasi ini dapat menginspirasi Anda setiap pagi dengan gambar latar baru untuk memulai kartu ucapan Anda. Aplikasi menggunakan Periodic Background Sync API untuk mencapai hal ini.

Langkah pertama adalah register peristiwa sinkronisasi berkala dalam pendaftaran pekerja layanan. Library ini memproses tag sinkronisasi yang disebut 'image-of-the-day' dan memiliki interval minimum satu hari, sehingga pengguna bisa mendapatkan gambar latar baru setiap 24 jam.

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);
  }
};

Langkah kedua adalah memproses peristiwa periodicsync di pekerja layanan. Jika tag peristiwa adalah 'image-of-the-day', yaitu tag yang didaftarkan sebelumnya, gambar hari ini diambil melalui fungsi getImageOfTheDay(), dan hasilnya disebarkan ke semua klien sehingga klien dapat memperbarui kanvas dan cache-nya.

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,
          });
        });
      })()
    );
  }
});

Sekali lagi, ini benar-benar merupakan progressive enhancement, sehingga kode hanya dimuat saat API didukung oleh browser. Ini berlaku untuk kode klien dan kode pekerja layanan. Di browser yang tidak didukung, keduanya tidak dimuat. Perhatikan bagaimana di pekerja layanan, bukan di import() dinamis (yang belum didukung dalam konteks pekerja layanan), saya menggunakan importScripts() klasik.

// 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');
}

Di Fugu Greetings, menekan tombol Wallpaper akan menampilkan gambar kartu ucapan hari tersebut yang diperbarui setiap hari melalui Periodic Background Sync API.

Aplikasi Salam Fugu dengan gambar kartu ucapan baru hari ini.
Menekan tombol Wallpaper akan menampilkan gambar hari ini.

Notification Triggers API

Terkadang, bahkan dengan banyak inspirasi, Anda membutuhkan dorongan untuk menyelesaikan kartu sapaan awal. Ini adalah fitur yang diaktifkan oleh Notification Triggers API. Sebagai pengguna, saya dapat memasukkan waktu kapan saya ingin didorong untuk menyelesaikan kartu ucapan. Ketika waktunya tiba, saya akan mendapatkan notifikasi bahwa kartu ucapan saya sedang menunggu.

Setelah meminta waktu target, aplikasi akan menjadwalkan notifikasi dengan showTrigger. Kolom ini dapat berupa TimestampTrigger dengan tanggal target yang dipilih sebelumnya. Notifikasi pengingat akan dipicu secara lokal, tidak memerlukan jaringan atau sisi server.

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),
  });
}

Seperti semua hal lain yang telah saya tunjukkan sejauh ini, ini adalah progressive enhancement, sehingga kode hanya dimuat secara bersyarat.

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

Saat saya mencentang kotak Pengingat di Salam Fugu, sebuah perintah akan meminta saya kapan saya ingin diingatkan untuk menyelesaikan kartu ucapan saya.

Aplikasi Fugu Greetings dengan perintah yang menanyakan kepada pengguna kapan mereka ingin diingatkan untuk menyelesaikan kartu ucapan.
Menjadwalkan notifikasi lokal agar diingatkan untuk menyelesaikan kartu ucapan.

Saat notifikasi terjadwal dipicu di Fugu Greetings, notifikasi ini akan ditampilkan seperti notifikasi lainnya, tetapi seperti yang saya tulis sebelumnya, notifikasi tidak memerlukan koneksi jaringan.

Pusat Notifikasi macOS menampilkan notifikasi yang dipicu dari Fugu Greetings.
Notifikasi yang dipicu akan muncul di Pusat Notifikasi macOS.

Wake Lock API

Saya juga ingin menyertakan Wake Lock API. Terkadang Anda hanya perlu menatap layar cukup lama sampai inspirasi mencium Anda. Hal terburuk yang dapat terjadi adalah mematikan layar. Wake Lock API dapat mencegah hal ini terjadi.

Langkah pertama adalah mendapatkan penguncian layar saat aktif dengan navigator.wakelock.request method(). Saya meneruskan string 'screen' untuk mendapatkan penguncian layar saat aktif. Kemudian saya menambahkan pemroses peristiwa untuk diberi tahu saat penguncian layar saat aktif dilepaskan. Hal ini dapat terjadi, misalnya, saat visibilitas tab berubah. Jika ini terjadi, saya bisa mendapatkan kembali penguncian layar saat aktif saat tab kembali terlihat.

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);

Ya, ini adalah progressive enhancement, jadi saya hanya perlu memuatnya jika browser mendukung API.

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

Di Fugu Greetings, ada kotak centang Insomnia yang, jika dicentang, akan membuat layar tetap aktif.

Kotak centang insomnia, jika dicentang, membuat layar tetap aktif.
Kotak centang Insomnia membuat aplikasi tetap aktif.

Idle Detection API

Kadang-kadang, bahkan jika Anda menatap layar selama berjam-jam, itu tidak berguna dan Anda tidak bisa mendapatkan ide apa pun yang harus dilakukan dengan kartu ucapan Anda. Idle Detection API memungkinkan aplikasi mendeteksi waktu tidak ada aktivitas pengguna. Jika pengguna tidak ada aktivitas terlalu lama, aplikasi akan direset ke status awal dan membersihkan kanvas. API ini saat ini dilindungi di balik izin notifikasi, karena banyak kasus penggunaan produksi deteksi tidak ada aktivitas yang terkait dengan notifikasi, misalnya, untuk hanya mengirim notifikasi ke perangkat yang saat ini aktif digunakan pengguna.

Setelah memastikan izin notifikasi diberikan, saya kemudian membuat instance detektor tidak ada aktivitas. Saya mendaftarkan pemroses peristiwa yang memproses perubahan tidak ada aktivitas, yang mencakup pengguna dan status layar. Pengguna bisa aktif atau tidak ada aktivitas, dan layar dapat dibuka atau dikunci. Jika pengguna tidak ada aktivitas, kanvas akan hilang. Saya memberi batas waktu 60 detik untuk detektor tidak ada aktivitas.

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,
});

Dan seperti biasa, saya hanya memuat kode ini saat browser mendukungnya.

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

Di aplikasi Fugu Greetings, kanvas akan dihapus saat kotak centang Ephemeral dicentang dan pengguna tidak ada aktivitas untuk waktu yang terlalu lama.

Aplikasi Fugu Greetings dengan kanvas yang telah dibersihkan setelah pengguna terlalu lama tidak ada aktivitas.
Jika kotak Ephemeral dicentang dan pengguna tidak ada aktivitas terlalu lama, kanvas akan dihapus.

Penutup

Wah, luar biasa. Begitu banyak API hanya dalam satu aplikasi contoh. Dan, ingat, saya tidak pernah membuat pengguna membayar biaya download untuk fitur yang tidak didukung browser. Dengan menggunakan progressive enhancement, saya memastikan hanya kode yang relevan yang dimuat. Dan karena HTTP/2 memiliki permintaan yang murah, pola ini seharusnya berfungsi dengan baik untuk banyak aplikasi, meskipun Anda mungkin perlu mempertimbangkan pemaket untuk aplikasi yang sangat besar.

Panel Jaringan Chrome DevTools hanya menampilkan permintaan untuk file dengan kode yang didukung browser saat ini.
Tab Jaringan Chrome DevTools hanya menampilkan permintaan file dengan kode yang didukung browser saat ini.

Aplikasi ini mungkin terlihat sedikit berbeda di setiap browser karena tidak semua platform mendukung semua fitur, tetapi fungsi intinya selalu ada—ditingkatkan secara progresif sesuai dengan kemampuan browser tertentu. Perhatikan bahwa kemampuan ini dapat berubah bahkan di satu browser yang sama, bergantung pada apakah aplikasi berjalan sebagai aplikasi terinstal atau di tab browser.

Salam Fugu yang berjalan di Chrome Android, menampilkan banyak fitur yang tersedia.
Salam Fugu yang berjalan di Android Chrome.
Salam Fugu yang berjalan di Safari desktop, menampilkan lebih sedikit fitur yang tersedia.
Salam Fugu yang berjalan di Safari desktop.
Salam Fugu yang berjalan di Chrome desktop, menampilkan banyak fitur yang tersedia.
Salam Fugu yang berjalan di Chrome desktop.

Jika Anda tertarik dengan aplikasi Fugu Greetings, temukan dan fork-nya di GitHub.

repo Fugu Greetings di GitHub.
Aplikasi Fugu Greetings di GitHub.

Tim Chromium berupaya keras untuk membuat rumput lebih hijau saat menggunakan Fugu API lanjutan. Dengan menerapkan progressive enhancement dalam pengembangan aplikasi, saya memastikan bahwa semua orang mendapatkan pengalaman dasar yang baik dan solid, tetapi orang yang menggunakan browser yang mendukung lebih banyak API platform Web mendapatkan pengalaman yang lebih baik. Saya menantikan apa yang Anda lakukan dengan progressive enhancement pada aplikasi.

Ucapan terima kasih

Saya berterima kasih kepada Christian Liebel dan Hemanth HM yang keduanya telah berkontribusi untuk Fugu Greetings. Artikel ini ditinjau oleh Joe Medley dan Kayce Basques. Jake Archibald membantu saya mencari tahu situasi dengan import() dinamis dalam konteks pekerja layanan.