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.
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.
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.
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
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.
{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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Jika Anda tertarik dengan aplikasi Fugu Greetings, temukan dan fork-nya 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.