Progresif Web Uygulamanızı kademeli olarak iyileştirin

Modern tarayıcılar için geliştirme yapıp 2003'teymiş gibi aşamalı olarak geliştirme

Mart 2003'te Nick Finck ve Steve Champeon progresif geliştirme kavramı ile web tasarımı dünyasını şaşırttı. Bu kavram, web tasarımı için önce temel web sayfası içeriğinin yüklenmesini vurgulayan, ardından ardından içeriğe giderek daha fazla incelikli ve teknik olarak titizlikli sunum katmanları ekleyen bir web tasarımı stratejisiydi. 2003'te aşamalı geliştirme, o zamanlar modern CSS özelliklerini, göze çarpmayan JavaScript'i ve hatta ölçeklenebilir vektör grafiklerini kullanmakla ilgiliydi. 2020 ve sonrasındaki aşamalarda, modern tarayıcı özelliklerini kullanarak aşamalı iyileştirmeler yapılacak.

Kademeli iyileştirmeyle gelecek için kapsayıcı web tasarımı. Finck ve Champeon'un orijinal sunumundaki başlık slaytı.
Slayt: Kademeli İyileştirme ile Gelecek İçin Kapsayıcı Web Tasarımı. (Kaynak)

Modern JavaScript

JavaScript'ten bahsetmişken, ES 2015'in en son temel JavaScript özelliklerine ilişkin tarayıcı desteğinin durumu çok iyi. Yeni standart; umutlar, modüller, sınıflar, şablon literalleri, ok işlevleri, let ve const, varsayılan parametreler, jeneratörler, yapı bozma atama, geri kalan ve dağıtma, Map/Set, WeakMap/WeakSet ve daha fazlasını içerir. Tüm tarayıcılar desteklenir.

Önde gelen tüm tarayıcılarda desteği gösteren, ES6 özellikleri için CanIUse destek tablosu.
ECMAScript 2015 (ES6) tarayıcı destek tablosu. (Kaynak)

ES 2017 özelliği olan ve en sevdiğim işlevlerden biri olan arayüz dışı işlevler, tüm popüler tarayıcılarda kullanılabilir. async ve await anahtar kelimeleri, söz zincirlerinin açıkça yapılandırılması gerekmeden, söze dayalı ve eşzamansız davranışın daha net bir şekilde yazılmasını sağlar.

Tüm büyük tarayıcılarda desteği gösteren, CanIUse destek tablosu.
Eşzamansız işlevler tarayıcı desteği tablosu. (Kaynak)

İsteğe bağlı zincirleme ve boş birleştirme gibi ES 2020'de eklenen yeni dil özellikleri bile çok hızlı bir şekilde destek kapsamına alındı. Aşağıda kod örneğini görebilirsiniz. Temel JavaScript özellikleri söz konusu olduğunda, durum bugün olduğundan çok daha iyi.

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
Windows XP'nin ikonik yeşil çimen arka plan resmi.
Temel JavaScript özellikleri, yeşil renktedir. (Microsoft ürün ekran görüntüsü, izin ile kullanılmıştır.)

Örnek uygulama: Fugu Greetings

Bu makalede, Fugu Greetings (GitHub) adlı basit bir PWA kullanıyorum. Bu uygulamanın adı, web'e Android/iOS/masaüstü uygulamalarının tüm özelliklerini kazandırma çabası olan Project Fugu'ya 🐡 bir göndermedir. Proje hakkında daha fazla bilgiyi açılış sayfasında bulabilirsiniz.

Fugu Greetings, sanal tebrik kartları oluşturup sevdiklerinize göndermenize olanak tanıyan bir çizim uygulamasıdır. Bu örnek, PWA'nın temel kavramlarını göstermektedir. Güvenilir ve tamamen çevrimdışı olarak kullanılabildiği için ağ bağlantınız olmasa bile bu çözümü kullanabilirsiniz. Ayrıca cihazın ana ekranına yüklenebilir ve bağımsız bir uygulama olarak işletim sistemiyle sorunsuz bir şekilde entegre olur.

PWA topluluğu logosuna benzeyen bir çizim içeren Fugu Greetings PWA.
Fugu Greetings örnek uygulaması.

Progresif geliştirme

Bu bilgilerden sonra kademeli iyileştirme hakkında konuşmanın zamanı geldi. MDN Web Dokümanlar Sözlüğü, kavramı şu şekilde tanımlar:

Kademeli iyileştirme, mümkün olduğunca çok sayıda kullanıcıya temel içerik ve işlevsellik sunarken yalnızca gerekli tüm kodu çalıştırabilen en modern tarayıcıların kullanıcılarına mümkün olan en iyi deneyimi sunan bir tasarım felsefesidir.

Özellik algılama genellikle tarayıcıların daha modern işlevleri işleyip işleyemeyeceğini belirlemek için kullanılır. Çoklu doldurma ise genellikle JavaScript ile eksik özellikleri eklemek için kullanılır.

[…]

Progresif geliştirme, web geliştiricilerin mümkün olan en iyi web sitelerini geliştirirken bu web sitelerini birden fazla bilinmeyen kullanıcı aracısında çalıştırmasına olanak tanıyan faydalı bir tekniktir. Sorunsuz düşüş, kademeli iyileştirmeyle ilgili ancak aynı değildir ve genellikle kademeli iyileştirmenin tam tersi yönde bir hareket olarak görülür. Gerçekte her iki yaklaşım da geçerlidir ve genellikle birbirini tamamlayabilir.

MDN katkıda bulunanlar

Her tebrik kartını sıfırdan başlatmak gerçekten külfetli olabilir. Öyleyse neden kullanıcıların resimleri içe aktarıp oradan başlamalarını sağlayan bir özelliğe sahip değil misiniz? Geleneksel bir yaklaşımla bunun için <input type=file> öğesi kullanırsınız. Öncelikle öğeyi oluşturur, type değerini 'file' olarak ayarlar ve accept mülküne MIME türleri eklersiniz. Ardından öğeyi programatik olarak "tıklar" ve değişiklikleri beklersiniz. Seçtiğiniz resimler doğrudan kanvas üzerine aktarılır.

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

Bir içe aktarma özelliği olduğunda, kullanıcıların tebrik kartlarını yerel olarak kaydedebilmeleri için muhtemelen bir dışa aktarma özelliği bulunmalıdır. Dosyaları kaydetmenin geleneksel yolu, download özelliğine sahip bir ana sayfa bağlantısı ve href olarak bir blob URL'si oluşturmaktır. Ayrıca, indirme işlemini tetiklemek için bu düğmeyi programatik olarak "tıklamanız" gerekir. Ayrıca, bellek sızıntısını önlemek için blob nesnesi URL'sini iptal etmeyi unutmayın.

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

Lütfen bir dakika bekleyin. Zihinsel olarak bir tebrik kartı "indirmemiş", "kaydetmişsiniz" demektir. Tarayıcı, dosyayı nereye koyacağınızı seçmenize olanak tanıyan bir "kaydet" iletişim kutusu göstermek yerine, kullanıcı etkileşimi olmadan doğrudan tebrik kartını indirip İndirilenler klasörünüze yerleştirdi. Bu hiç iyi değil.

Daha iyi bir yol olsaydı ne olurdu? Yerel bir dosyayı açıp düzenledikten sonra değişiklikleri yeni bir dosyaya veya ilk açtığınız orijinal dosyaya kaydedebilseydiniz ne olurdu? File System Access API, dosya ve dizinleri açıp oluşturmanın yanı sıra bunları değiştirmenize ve kaydetmenize olanak tanır.

Peki bir API'de nasıl özellik algılayabilirim? File System Access API yeni bir window.chooseFileSystemEntries() yöntemini sunuyor. Bu nedenle, bu yöntemin kullanılabilir olup olmadığına bağlı olarak koşullu olarak farklı içe ve dışa aktarma modülleri yüklemem gerekiyor. Bunun nasıl yapılacağını aşağıda görebilirsiniz.

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

Ancak File System Access API ayrıntılarına geçmeden önce, aşamalı iyileştirme modelini kısaca vurgulamak isterim. Şu anda File System Access API'yi desteklemeyen tarayıcılarda eski komut dosyalarını yüklüyorum. Firefox ve Safari'nin ağ sekmelerini aşağıda görebilirsiniz.

Eski dosyaların yüklendiğini gösteren Safari Web İnceleyici.
Safari Web İnspector ağ sekmesi.
Eski dosyaların yüklendiğini gösteren Firefox Geliştirici Araçları.
Firefox Developer Tools ağ sekmesi.

Ancak API'yi destekleyen bir tarayıcı olan Chrome'da yalnızca yeni komut dosyaları yüklenir. Bu, tüm modern tarayıcıların desteklediği dinamik import() sayesinde zarif bir şekilde mümkün kılınmaktadır. Daha önce de söylediğim gibi, bu günlerde işler oldukça yolunda.

Modern dosyaların yüklendiğini gösteren Chrome Geliştirici Araçları.
Chrome Geliştirici Araçları ağ sekmesi.

File System Access API

Bu konuyu ele aldıktan sonra, File System Access API'ye dayalı gerçek uygulamaya bakmanın zamanı geldi. Resim içe aktarmak için window.chooseFileSystemEntries() işlevini çağırıyorum ve resim dosyaları istediğimi belirten bir accepts mülkü iletiyorum. Hem MIME türleri hem de dosya uzantıları desteklenir. Bu işlem sonucunda, getFile() komutunu çağırarak gerçek dosyayı alabileceğim bir dosya işleyici oluşturulur.

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

Resim dışa aktarma işlemi neredeyse aynıdır ancak bu sefer chooseFileSystemEntries() yöntemine 'save-file' türündeki bir parametre göndermem gerekiyor. Bu işlemden sonra bir dosya kaydetme iletişim kutusu görüyorum. Dosya açıkken 'open-file' varsayılan olduğu için bu gerekli değildi. accepts parametresini öncekine benzer şekilde ayarladım ancak bu sefer yalnızca PNG resimleriyle sınırladım. Yine bir dosya tutamaç döndürülür ancak bu sefer dosyayı almak yerine createWritable() işlevini çağırarak yazılabilir bir akış oluştururum. Ardından, tebrik kartı resmim olan blob'u dosyaya yazarım. Son olarak, yazılabilir akışı kapatıyorum.

Her şey her zaman başarısız olabilir: Diskte yer kalmamış olabilir, yazma veya okuma hatası olabilir ya da kullanıcı dosya iletişim kutusunu iptal etmiş olabilir. Bu nedenle, çağrıları her zaman bir try...catch ifadesi içine alırım.

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 ile aşamalı geliştirmeyi kullanarak dosyaları eskisi gibi açabilirim. İçe aktarılan dosya doğrudan tuvale çizilir. Düzenlemeler yapıp dosyanın adını ve depolama konumunu seçebileceğim gerçek bir kaydetme iletişim kutusuyla dosyaları kaydedebilirim. Dosya artık sonsuza kadar saklanacak şekilde hazırdır.

Dosya açma iletişim kutusu içeren Fugu Greetings uygulaması.
Dosya açma iletişim kutusu.
Fugu Greetings uygulaması artık içe aktarılan bir resim içeriyor.
İçe aktarılan resim.
Değiştirilmiş resmi içeren Fugu Greetings uygulaması.
Değiştirilen resmi yeni bir dosyaya kaydetme.

Web Share ve Web Share Target API'leri

Sonsuza kadar depolamanın dışında belki de tebrik kartımı paylaşmak isterim. Web Share API ve Web Share Target API bunu yapmama olanak tanıyor. Mobil ve yakın zamanda masaüstü işletim sistemleri, yerleşik paylaşım mekanizmalarına sahip. Örneğin, aşağıdaki resimde macOS'te masaüstü Safari'nin, blogumdaki bir makaleden tetiklenen paylaşım sayfası gösterilmektedir. Makaleyi Paylaş düğmesini tıkladığınızda makalenin bağlantısını bir arkadaşınızla (ör. macOS Mesajlar uygulaması üzerinden) paylaşabilirsiniz.

Bir makalenin Paylaş düğmesinden tetiklenen macOS&#39;teki masaüstü Safari&#39;nin paylaşım sayfası
macOS'teki masaüstü Safari'sinde Web Paylaşımı API'si.

Bunu gerçekleştirmek için kullanacağınız kod oldukça basittir. navigator.share() işlevini çağırıyorum ve bir nesnede isteğe bağlı title, text ve url parametrelerini ilerliyorum. Peki bir resim eklemek istersem ne olur? Web Share API'nin 1. düzeyi henüz bunu desteklemiyor. Neyse ki Web Paylaşımı Düzeyi 2'ye dosya paylaşım özellikleri eklenmiştir.

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

Bunu Fugu tebrik kartı uygulamasıyla nasıl yapacağınızı göstereyim. Öncelikle, bir blobdan oluşan bir files dizisi içeren bir data nesnesi, ardından bir title ve bir text hazırlamam gerekiyor. Ardından, en iyi uygulama olarak, adının belirttiği şekilde yeni navigator.canShare() yöntemini kullanıyorum: Paylaşmaya çalıştığım data nesnesinin tarayıcı tarafından teknik olarak paylaşılıp paylaşılamayacağını söylüyor. navigator.canShare(), verilerin paylaşılabileceğini söylerse daha önce olduğu gibi navigator.share() numarasını aramaya hazırım. Her şey başarısız olabileceği için yine bir try...catch bloku kullanıyorum.

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

Daha önce olduğu gibi progresif geliştirmeyi kullanıyorum. navigator nesnesinde hem 'share' hem de 'canShare' varsa yalnızca bu durumda ileri gidip dinamik import() aracılığıyla share.mjs öğesini yüklerim. Mobil Safari gibi yalnızca iki koşuldan birini karşılayan tarayıcılarda işlev yüklemiyorum.

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

Fugu Greetings'da, Android'de Chrome gibi desteklenen bir tarayıcıda Paylaş düğmesine dokunursam yerleşik paylaşım sayfası açılır. Örneğin, Gmail'i seçtiğimde, e-posta oluşturucu widget'ı, resmi ekleyerek açılır.

Resmi paylaşabileceğiniz çeşitli uygulamaları gösteren işletim sistemi düzeyinde paylaşım sayfası.
Dosyanın paylaşılacağı uygulamayı seçme.
Gmail&#39;in, resim ekli e-posta oluşturma widget&#39;ı.
Dosya, Gmail'in oluşturucusunda yeni bir e-postaya eklenir.

Contact Picker API

Ardından, cihazın adres defteri veya kişi yöneticisi uygulaması olan kişilerden bahsetmek istiyorum. Bir tebrik kartı yazarken kişinin adını doğru yazmak her zaman kolay olmayabilir. Örneğin, Sergey adının Kiril harfleriyle yazılmasını tercih eden bir arkadaşım var. Almanca QWERTZ klavye kullanıyorum ve adlarını nasıl yazacağımı bilmiyorum. Bu, Contact Picker API'nin çözebileceği bir sorundur. Arkadaşım telefonumun kişiler uygulamasında kayıtlı olduğundan Kişiler Seçici API'sını kullanarak web'den kişilerime ulaşabiliyorum.

Öncelikle, erişmek istediğim mülklerin listesini belirtmem gerekiyor. Bu durumda yalnızca adları istiyorum ancak diğer kullanım alanları için telefon numaraları, e-postalar, avatar simgeleriyle fiziksel adresler de ilgimi çekebilir. Daha sonra, birden fazla giriş seçmek için bir options nesnesi yapılandırıp multiple değerini true olarak ayarlıyorum. Son olarak, kullanıcı tarafından seçilen kişiler için istenen özellikleri döndüren navigator.contacts.select() işlevini çağırabilirim.

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

Şimdiye kadar muhtemelen şu kalıbı öğrendiniz: Yalnızca API gerçekten desteklendiğinde dosyayı yüklerim.

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

Fugu Greeting'de Kişiler düğmesine dokunup en iyi iki arkadaşım Сергей Михайлович Брин ve 劳伦斯·爱德华·"拉里"·佩奇'yi seçtiğimde, kişiler seçicinin yalnızca adlarını göstermekle sınırlı olduğunu, e-posta adreslerini veya telefon numaraları gibi diğer bilgileri göstermediğini görebilirsiniz. Ardından, isimlerini tebrik kartıma çiziyorum.

Adres defterindeki iki kişinin adını gösteren kişi seçici.
Adres defterinden kişi seçiciyle iki ad seçin.
Daha önce seçilen iki kişinin adı, tebrik kartına çizilir.
Ardından iki ad tebrik kartına çizilir.

Eşzamansız Clipboard API

Bir sonraki konu kopyalama ve yapıştırma. Yazılım geliştiricileri olarak en sevdiğimiz işlemlerden biri kopyalama ve yapıştırmadır. Kart yazarı olarak bazen aynısını yapmak isterim. Üzerinde çalıştığım bir tebrik kartına resim yapıştırmak veya tebrik kartımı başka bir yerden düzenlemeye devam edebilmek için kopyalamak istiyorum. Async Clipboard API hem metni hem de görselleri destekler. Fugu Greetings uygulamasına kopyalama ve yapıştırma desteğini nasıl eklediğimi adım adım anlatacağım.

Bir şeyi sistemin panosuna kopyalamak için panoya yazmam gerekiyor. navigator.clipboard.write() yöntemi, parametre olarak bir pano öğesi dizisi alır. Her panoya kopyalanan öğe, temelde değeri bir blob, anahtarı ise blob türü olan bir nesnedir.

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

Yapıştırmak için navigator.clipboard.read() çağrısını kullanarak elde ettiğim pano öğelerini döngüden geçirmem gerekiyor. Bunun nedeni, panoda farklı şekillerde birden fazla pano öğesinin bulunabilmesidir. Her pano öğesinde, mevcut kaynakların MIME türlerini belirten bir types alanı var. Daha önce aldığım MIME türünü ileterek, panosundaki öğenin getType() yöntemini çağırıyorum.

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

Bu konuda söylenecek çok şey var. Bu işlemi yalnızca desteklenen tarayıcılarda yaparım.

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

Peki bu nasıl çalışır? macOS Önizleme uygulamasında açık bir resmim var ve bu resmi panoya kopyalıyorum. Yapıştır'ı tıkladığımda Fugu Greetings uygulaması bana panodaki metin ve resimleri görmesine izin vermek isteyip istemediğimi soruyor.

Fugu Greetings uygulamasında gösterilen panos izni istemini gösteren resim.
Pano izin istemi.

Son olarak, izin kabul edildikten sonra resim uygulamaya yapıştırılır. Bunun tersi de geçerlidir. Bir tebrik kartını panoya kopyalayayım. Ardından Önizleme'yi açıp Dosya'yı ve ardından Kopya Kutusundan Yeni'yi tıkladığımda tebrik kartı, yeni bir adsız resme yapıştırılıyor.

Adsız ve az önce yapıştırılmış bir resmin bulunduğu macOS Önizleme uygulaması.
MacOS Önizleme uygulamasına yapıştırılmış bir resim.

Badging API

Badging API de faydalı API'lerden biridir. Yüklenebilir bir PWA olarak Fugu Greetings'in, kullanıcıların uygulama paneline veya ana ekrana yerleştirebileceği bir uygulama simgesi vardır. API'yi göstermenin eğlenceli ve kolay bir yolu, Fugu Greetings'da kalem vuruşları sayacı olarak kullanmaktır. pointerdown etkinliği gerçekleştiğinde kalem vuruşları sayacını artıran ve ardından güncellenmiş simge rozetini ayarlayan bir etkinlik işleyici ekledim. Kanvas temizlendiğinde sayaç sıfırlanır ve rozet kaldırılır.

let strokes = 0;

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

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

Bu özellik aşamalı bir geliştirme olduğundan yükleme mantığı normal şekildedir.

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

Bu örnekte, her sayı için bir kalem darbesi kullanarak bir ile yedi arasındaki sayıları çizdim. Simgedeki rozet sayacı artık yedi.

Tebrik kartına tek bir kalem darbesiyle çizilmiş bir ile yedi arasındaki sayılar.
Kalemle yedi vuruş yaparak 1'den 7'ye kadar olan sayıları çizme.
Fugu Greetings uygulamasında 7 rakamını gösteren rozet simgesi.
Uygulama simgesi rozeti şeklindeki kalem darbesi sayacı.

Periyodik Arka Plan Senkronizasyonu API'si

Her güne yeni bir şeyle başlamak ister misiniz? Fugu Greetings uygulamasının güzel özelliklerinden biri, her sabah yeni bir arka plan resmiyle sizi selam kartınıza başlamaya teşvik etmesidir. Uygulama bunu yapmak için Periodic Background Sync API'yi kullanır.

İlk adım, hizmet çalışanı kaydına düzenli senkronizasyon etkinliği kaydetmektir. 'image-of-the-day' adlı bir senkronizasyon etiketini dinler ve en az bir günlük bir aralığa sahiptir. Böylece kullanıcı 24 saatte bir yeni bir arka plan resmi alabilir.

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

İkinci adım, hizmet çalışanında periodicsync etkinliğine kulak vermek olacaktır. Etkinlik etiketi 'image-of-the-day' ise (yani daha önce kaydedilen etiket) günün resmi getImageOfTheDay() işlevi aracılığıyla alınır ve sonuç tüm istemcilere dağıtılır. Böylece istemciler tuvallerini ve önbelleğe alınmış verilerini güncelleyebilir.

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

Bu da yine aşamalı bir geliştirmedir. Bu nedenle kod yalnızca API tarayıcı tarafından desteklendiğinde yüklenir. Bu kural hem istemci kodu hem de Service Worker kodu için geçerlidir. Desteklenmeyen tarayıcılarda bu iki dosya da yüklenmez. Service Worker'da (henüz bir hizmet çalışanı bağlamında desteklenmeyen) dinamik import() yerine klasik importScripts() kullandığımı unutmayın.

// 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'da Duvar kağıdı düğmesine bastığınızda, Düzenli Arka Plan Senkronizasyonu API'si aracılığıyla her gün güncellenen günün tebrik kartı resmi gösterilir.

Günün yeni tebrik kartı resmini içeren Fugu Greetings uygulaması.
Duvar kağıdı düğmesine basıldığında günün resmi gösterilir.

Notification Triggers API

Bazen çok fazla ilham olsa bile, başladığınız bir tebrik kartını tamamlamak için yönlendirmeye ihtiyacınız olur. Bu özellik, Notification Triggers API tarafından etkinleştirilir. Kullanıcı olarak, tebrik kartımı tamamlamam için otomatik hatırlatma istediğim bir zaman girebilirim. Bu tarih geldiğinde, tebrik kartımın beklediğine dair bir bildirim alacağım.

Uygulama, hedef zaman istedikten sonra bildirimi showTrigger ile programlar. Bu, önceden seçilen hedef tarihi içeren bir TimestampTrigger olabilir. Hatırlatıcı bildirimi yerel olarak tetiklenir; ağ veya sunucu tarafı gerekmez.

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

Şimdiye kadar gösterdiğim diğer her şeyde olduğu gibi bu da progresif bir geliştirme olduğu için kod yalnızca koşullu olarak yükleniyor.

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

Fugu Greetings'da Hatırlatıcı onay kutusunu işaretlediğimde, tebrik kartımı tamamlamam gerektiğini ne zaman hatırlatılmasını istediğimi soran bir istem gösteriliyor.

Fugu Greetings uygulamasında, kullanıcıya tebrik kartını tamamlaması gerektiğinin ne zaman hatırlatılmasını istediğini soran bir istem gösteriliyor.
Tebrik kartı tamamlama hatırlatması için yerel bildirim planlama.

Fugu Greetings'da planlanmış bir bildirim tetiklendiğinde, diğer bildirimler gibi gösterilir ancak daha önce de belirttiğim gibi ağ bağlantısı gerektirmez.

Fugu Greetings&#39;dan gelen bir bildirimin tetiklendiğini gösteren macOS Bildirim Merkezi.
Tetiklenen bildirim, macOS Bildirim Merkezi'nde görünür.

Wake Lock API

Wake Lock API'yi de dahil etmek istiyorum. Bazen ilham size gelene kadar ekrana uzun süre bakmanız yeterlidir. Bu durumda en kötü ihtimal ekranın kapanmasıdır. Wake Lock API, bunun olmasını önleyebilir.

İlk adım, navigator.wakelock.request method() ile bir uyanık kalma kilidi edinmektir. Ekran uyanık kalma kilidi elde etmek için 'screen' dizesini iletiyorum. Ardından, uyanma kilidi açıldığında bilgilendirilmek için bir etkinlik dinleyici eklerim. Örneğin, sekme görünürlüğü değiştiğinde bu durumla karşılaşabilirsiniz. Bu durumda, sekme tekrar görünür hale geldiğinde uyanma kilidini yeniden edinebilirim.

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

Evet, bu aşamalı bir geliştirmedir. Bu nedenle, yalnızca tarayıcı API'yi desteklediğinde yüklemem gerekir.

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

Fugu Greetings'te, işaretlendiğinde ekranı açık tutan bir Uykusuzluk onay kutusu bulunur.

Uykusuzluk onay kutusu, işaretlenirse ekranı açık tutar.
Uykusuzluk onay kutusu, uygulamayı uyanık tutar.

Idle Detection API

Bazen saatlerce ekrana baksanız bile hiçbir şey aklınıza gelmez ve tebrik kartınızla ne yapacağınızı bilemezsiniz. Idle Detection API, uygulamanın kullanıcının boşta kalma süresini algılamasına olanak tanır. Kullanıcı uzun süre boyunca işlem yapmazsa uygulama ilk duruma sıfırlanır ve tuval temizlenir. Boş ekran algılamanın birçok üretim kullanım alanı bildirimlerle ilgili olduğundan (ör. yalnızca kullanıcının o anda etkin olarak kullandığı bir cihaza bildirim göndermek için) bu API şu anda bildirim izni kapsamındadır.

Bildirim izninin verildiğinden emin olduktan sonra boşta kalma algılayıcısını örneklendiririm. Kullanıcıyı ve ekran durumunu içeren boşta kalma değişikliklerini dinleyen bir etkinlik dinleyicisi kaydediyorum. Kullanıcı etkin veya boşta olabilir, ekran kilidi açılabilir veya kilitlenebilir. Kullanıcı işlem yapmazsa tuval temizlenir. Boşta kalma algılayıcısına 60 saniyelik bir eşik değeri veriyorum.

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

Her zaman olduğu gibi, bu kodu yalnızca tarayıcı desteklediğinde yüklerim.

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

Fugu Greetings uygulamasında, Geçici onay kutusu işaretlendiğinde ve kullanıcı çok uzun süre hareketsiz kaldığında tuval temizlenir.

Kullanıcı uzun süre hareketsiz kaldıktan sonra temiz bir tuval içeren Fugu Greetings uygulaması.
Geçici onay kutusu işaretliyse ve kullanıcı çok uzun süredir etkin değilse tuval temizlenir.

Kapanış

Vay canına. Tek bir örnek uygulamada bu kadar çok API var. Ayrıca, kullanıcının tarayıcısının desteklemediği bir özelliğin indirme maliyetini hiçbir zaman kullanıcıdan ödemesini istemem. Progresif geliştirmeyi kullanarak yalnızca alakalı kodun yüklenmesini sağlıyorum. HTTP/2'de istekler ucuz olduğundan bu kalıp birçok uygulama için işe yarayabilir. Ancak gerçekten büyük uygulamalar için bir paketleyici kullanmayı düşünebilirsiniz.

Yalnızca geçerli tarayıcının desteklediği koda sahip dosya isteklerini gösteren Chrome Geliştirici Araçları ağ paneli.
Chrome Geliştirici Araçları Ağ sekmesinde yalnızca geçerli tarayıcının desteklediği koda sahip dosya isteklerini gösterir.

Tüm platformlar tüm özellikleri desteklemediğinden uygulama her tarayıcıda biraz farklı görünebilir. Ancak temel işlevler her zaman mevcuttur ve ilgili tarayıcının özelliklerine göre aşamalı olarak geliştirilir. Bu özelliklerin, uygulamanın yüklü bir uygulama olarak mı yoksa tarayıcı sekmesinde mi çalıştığına bağlı olarak aynı tarayıcıda bile değişebileceğini unutmayın.

Fugu Greetings, Android Chrome&#39;da çalışıyor ve kullanılabilen birçok özellik gösteriliyor.
Android Chrome'da çalışan Fugu Greetings.
Fugu Greetings masaüstü Safari&#39;de çalışıyor ve daha az kullanılabilir özellik gösteriliyor.
Fugu Greetings, masaüstü Safari'de çalışıyor.
Masaüstü Chrome&#39;da çalışan Fugu Greetings&#39;in birçok özelliğini gösteren ekran.
Fugu Greetings, masaüstü Chrome'da çalışıyor.

Fugu Greetings uygulamasıyla ilgileniyorsanız GitHub'da bu uygulamayı alıp çatallayın.

GitHub&#39;da Fugu Greetings deposu.
GitHub'da Fugu Greetings uygulaması.

Chromium ekibi, gelişmiş Fugu API'leri konusunda daha iyi bir deneyim sunmak için yoğun şekilde çalışıyor. Uygulamamı geliştirirken aşamalı geliştirmeyi uygulayarak herkesin iyi ve sağlam bir temel deneyim elde etmesini sağlarken daha fazla web platformu API'sini destekleyen tarayıcılar kullanan kullanıcıların daha da iyi bir deneyim elde etmesini sağlıyorum. Uygulamalarınızda aşamalı geliştirme özelliğini nasıl kullanacağınızı görmeyi sabırsızlıkla bekliyorum.

Teşekkür

Fugu Selamlamaları'na katkıda bulunan Christian Liebel ve Hemanth HM'e minnettarım. Bu makale, Joe Medley ve Kayce Basques tarafından incelenmiştir. Jake Archibald, hizmet çalışanı bağlamında dinamik import() ile ilgili durumu öğrenmeme yardımcı oldu.