আপনার প্রগতিশীল ওয়েব অ্যাপকে ক্রমান্বয়ে উন্নত করুন

আধুনিক ব্রাউজারগুলির জন্য তৈরি করা এবং 2003 এর মতো ধীরে ধীরে উন্নত করা

2003 সালের মার্চ মাসে, নিক ফিঙ্ক এবং স্টিভ চ্যাম্পিয়ন প্রগতিশীল বর্ধনের ধারণা দিয়ে ওয়েব ডিজাইন বিশ্বকে হতবাক করে দিয়েছিলেন, ওয়েব ডিজাইনের একটি কৌশল যা প্রথমে মূল ওয়েব পৃষ্ঠার বিষয়বস্তু লোড করার উপর জোর দেয়, এবং এটি ধীরে ধীরে বিষয়বস্তুর উপরে উপস্থাপনা এবং বৈশিষ্ট্যগুলির আরও সূক্ষ্ম এবং প্রযুক্তিগতভাবে কঠোর স্তর যুক্ত করে। 2003 সালে, প্রগতিশীল বর্ধিতকরণ ছিল—সে সময়ে—আধুনিক CSS বৈশিষ্ট্য, অবাধ জাভাস্ক্রিপ্ট এবং এমনকি স্কেলেবল ভেক্টর গ্রাফিক্স ব্যবহার করা। 2020 এবং তার পরেও প্রগতিশীল উন্নতি হল আধুনিক ব্রাউজার ক্ষমতা ব্যবহার করা।

প্রগতিশীল বর্ধন সহ ভবিষ্যতের জন্য অন্তর্ভুক্তিমূলক ওয়েব ডিজাইন। Finck এবং Champeon এর মূল উপস্থাপনা থেকে শিরোনাম স্লাইড।
স্লাইড: প্রগতিশীল বর্ধন সহ ভবিষ্যতের জন্য অন্তর্ভুক্ত ওয়েব ডিজাইন। ( সূত্র )

আধুনিক জাভাস্ক্রিপ্ট

জাভাস্ক্রিপ্টের কথা বলতে গেলে, সর্বশেষ কোর ES 2015 জাভাস্ক্রিপ্ট বৈশিষ্ট্যগুলির জন্য ব্রাউজার সমর্থন পরিস্থিতি দুর্দান্ত। নতুন স্ট্যান্ডার্ডের মধ্যে রয়েছে প্রতিশ্রুতি, মডিউল, ক্লাস, টেমপ্লেট লিটারেল, অ্যারো ফাংশন, let এবং const , ডিফল্ট প্যারামিটার, জেনারেটর, ধ্বংসকারী অ্যাসাইনমেন্ট, বিশ্রাম এবং স্প্রেড, Map / Set , WeakMap / WeakSet এবং আরও অনেক কিছু। সব সমর্থিত হয় .

ES6 বৈশিষ্ট্যগুলির জন্য CanIUse সমর্থন টেবিলটি সমস্ত প্রধান ব্রাউজার জুড়ে সমর্থন দেখাচ্ছে।
ECMAScript 2015 (ES6) ব্রাউজার সমর্থন টেবিল। ( সূত্র )

Async ফাংশন, একটি ES 2017 বৈশিষ্ট্য এবং আমার ব্যক্তিগত পছন্দের একটি, সমস্ত প্রধান ব্রাউজারে ব্যবহার করা যেতে পারেasync এবং await কীওয়ার্ডগুলি অ্যাসিঙ্ক্রোনাস, প্রতিশ্রুতি-ভিত্তিক আচরণকে একটি পরিষ্কার শৈলীতে লিখতে সক্ষম করে, প্রতিশ্রুতি চেইনগুলি স্পষ্টভাবে কনফিগার করার প্রয়োজন এড়িয়ে যায়।

অ্যাসিঙ্ক ফাংশনের জন্য CanIUse সমর্থন টেবিলটি সমস্ত প্রধান ব্রাউজার জুড়ে সমর্থন দেখায়।
Async ফাংশন ব্রাউজার সমর্থন টেবিল। ( সূত্র )

এবং এমনকি অতি সাম্প্রতিক ES 2020 ভাষার সংযোজন যেমন ঐচ্ছিক চেইনিং এবং নালিশ কোলেসিং সত্যিই দ্রুত সমর্থনে পৌঁছেছে। আপনি নীচে একটি কোড নমুনা দেখতে পারেন. মূল জাভাস্ক্রিপ্ট বৈশিষ্ট্যগুলির ক্ষেত্রে, ঘাসটি আজকের তুলনায় খুব বেশি সবুজ হতে পারে না।

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
আইকনিক Windows XP সবুজ ঘাসের পটভূমির চিত্র।
মূল জাভাস্ক্রিপ্ট বৈশিষ্ট্যের ক্ষেত্রে ঘাস সবুজ। (Microsoft পণ্যের স্ক্রিনশট, অনুমতি নিয়ে ব্যবহার করা হয়েছে।)

নমুনা অ্যাপ: ফুগু শুভেচ্ছা

এই নিবন্ধটির জন্য, আমি একটি সাধারণ PWA এর সাথে কাজ করি, যার নাম Fugu Greetings ( GitHub )। এই অ্যাপটির নাম হল প্রোজেক্ট ফুগু 🐡 এর একটি টিপ, যা ওয়েবকে অ্যান্ড্রয়েড/আইওএস/ডেস্কটপ অ্যাপ্লিকেশনগুলির সমস্ত ক্ষমতা দেওয়ার একটি প্রচেষ্টা৷ আপনি এর ল্যান্ডিং পৃষ্ঠায় প্রকল্প সম্পর্কে আরও পড়তে পারেন।

ফুগু গ্রিটিংস একটি ড্রয়িং অ্যাপ যা আপনাকে ভার্চুয়াল গ্রিটিং কার্ড তৈরি করতে এবং আপনার প্রিয়জনকে পাঠাতে দেয়। এটি PWA এর মূল ধারণার উদাহরণ দেয়। এটি নির্ভরযোগ্য এবং সম্পূর্ণ অফলাইন সক্ষম, তাই আপনার নেটওয়ার্ক না থাকলেও আপনি এটি ব্যবহার করতে পারেন৷ এটি একটি ডিভাইসের হোম স্ক্রিনেও ইনস্টলযোগ্য এবং একটি স্বতন্ত্র অ্যাপ্লিকেশন হিসাবে অপারেটিং সিস্টেমের সাথে নির্বিঘ্নে সংহত করে৷

PWA সম্প্রদায়ের লোগোর অনুরূপ একটি অঙ্কন সহ ফুগু গ্রিটিংস PWA।
Fugu শুভেচ্ছা নমুনা অ্যাপ্লিকেশন.

প্রগতিশীল বর্ধন

এই পথের বাইরে, এটি প্রগতিশীল বর্ধন সম্পর্কে কথা বলার সময়। MDN ওয়েব ডক্স গ্লোসারী ধারণাটিকে নিম্নরূপ সংজ্ঞায়িত করে :

প্রোগ্রেসিভ এনহান্সমেন্ট হল একটি ডিজাইনের দর্শন যা যতটা সম্ভব ব্যবহারকারীদের জন্য প্রয়োজনীয় বিষয়বস্তু এবং কার্যকারিতার একটি বেসলাইন প্রদান করে, এবং শুধুমাত্র সবচেয়ে আধুনিক ব্রাউজারগুলির ব্যবহারকারীদের জন্য সর্বোত্তম সম্ভাব্য অভিজ্ঞতা প্রদান করে যা সমস্ত প্রয়োজনীয় কোড চালাতে পারে।

বৈশিষ্ট্য সনাক্তকরণ সাধারণত ব্রাউজারগুলি আরও আধুনিক কার্যকারিতা পরিচালনা করতে পারে কিনা তা নির্ধারণ করতে ব্যবহৃত হয়, যখন জাভাস্ক্রিপ্টের সাথে অনুপস্থিত বৈশিষ্ট্যগুলি যোগ করতে প্রায়শই পলিফিল ব্যবহার করা হয়।

[…]

প্রোগ্রেসিভ এনহান্সমেন্ট হল একটি দরকারী কৌশল যা ওয়েব ডেভেলপারদেরকে একাধিক অজানা ব্যবহারকারী এজেন্টের উপর কাজ করার সময় সম্ভাব্য সর্বোত্তম ওয়েবসাইট তৈরিতে ফোকাস করতে দেয়। করুণাময় অবক্ষয় সম্পর্কিত, কিন্তু একই জিনিস নয় এবং প্রায়শই প্রগতিশীল বর্ধনের বিপরীত দিকে যেতে দেখা যায়। বাস্তবে, উভয় পদ্ধতিই বৈধ এবং প্রায়শই একে অপরের পরিপূরক হতে পারে।

MDN অবদানকারীরা

স্ক্র্যাচ থেকে প্রতিটি শুভেচ্ছা কার্ড শুরু করা সত্যিই কষ্টকর হতে পারে। তাহলে কেন এমন একটি বৈশিষ্ট্য নেই যা ব্যবহারকারীদের একটি চিত্র আমদানি করতে এবং সেখান থেকে শুরু করতে দেয়? একটি ঐতিহ্যগত পদ্ধতির সাথে, আপনি এটি ঘটতে একটি <input type=file> উপাদান ব্যবহার করেছেন। প্রথমত, আপনি উপাদানটি তৈরি করবেন, এটির type 'file' এ সেট করুন এবং accept সম্পত্তিতে MIME প্রকারগুলি যোগ করুন এবং তারপর প্রোগ্রাম্যাটিকভাবে এটিতে "ক্লিক করুন" এবং পরিবর্তনগুলি শুনুন। আপনি যখন একটি ছবি নির্বাচন করেন, এটি সরাসরি ক্যানভাসে আমদানি করা হয়।

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 অ্যাট্রিবিউট এবং একটি ব্লব URL এর href হিসাবে একটি অ্যাঙ্কর লিঙ্ক তৈরি করা। আপনি ডাউনলোডটি ট্রিগার করার জন্য এটিকে প্রোগ্রাম্যাটিকভাবে "ক্লিক করুন" এবং মেমরি লিক প্রতিরোধ করতে, আশা করি ব্লব অবজেক্ট URL প্রত্যাহার করতে ভুলবেন না।

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

কিন্তু এক মিনিট অপেক্ষা করুন। মানসিকভাবে, আপনি একটি অভিবাদন কার্ড "ডাউনলোড" করেননি, আপনি এটি "সংরক্ষিত" করেছেন। আপনাকে একটি "সংরক্ষণ করুন" ডায়ালগ দেখানোর পরিবর্তে যা আপনাকে ফাইলটি কোথায় রাখতে হবে তা চয়ন করতে দেয়, ব্রাউজারটি ব্যবহারকারীর ইন্টারঅ্যাকশন ছাড়াই সরাসরি অভিবাদন কার্ডটি ডাউনলোড করেছে এবং এটি সরাসরি আপনার ডাউনলোড ফোল্ডারে রেখেছে৷ এই মহান না.

যদি একটি ভাল উপায় ছিল? যদি আপনি শুধুমাত্র একটি স্থানীয় ফাইল খুলতে পারেন, এটি সম্পাদনা করতে পারেন, এবং তারপরে পরিবর্তনগুলি সংরক্ষণ করতে পারেন, হয় একটি নতুন ফাইলে, অথবা আপনি যেটি শুরুতে খুলেছিলেন সেটিতে ফিরে যেতে? সেখানে দেখা যাচ্ছে. ফাইল সিস্টেম অ্যাক্সেস API আপনাকে ফাইল এবং ডিরেক্টরি খুলতে এবং তৈরি করতে দেয়, সেইসাথে সেগুলিকে সংশোধন এবং সংরক্ষণ করতে দেয়।

তাই আমি কিভাবে একটি API বৈশিষ্ট্য সনাক্ত করতে পারি? ফাইল সিস্টেম অ্যাক্সেস 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'),
    ]);
  }
};

কিন্তু আমি ফাইল সিস্টেম অ্যাক্সেস API বিবরণে ডুব দেওয়ার আগে, আমাকে এখানে প্রগতিশীল বর্ধিতকরণ প্যাটার্নটি দ্রুত হাইলাইট করতে দিন। বর্তমানে ফাইল সিস্টেম অ্যাক্সেস API সমর্থন করে না এমন ব্রাউজারগুলিতে, আমি লিগ্যাসি স্ক্রিপ্টগুলি লোড করি। আপনি নিচে Firefox এবং Safari এর নেটওয়ার্ক ট্যাব দেখতে পারেন।

সাফারি ওয়েব ইন্সপেক্টর লিগ্যাসি ফাইল লোড হচ্ছে দেখাচ্ছে।
সাফারি ওয়েব ইন্সপেক্টর নেটওয়ার্ক ট্যাব।
ফায়ারফক্স ডেভেলপার টুল লিগ্যাসি ফাইল লোড হচ্ছে দেখাচ্ছে।
ফায়ারফক্স ডেভেলপার টুলস নেটওয়ার্ক ট্যাব।

যাইহোক, Chrome এ, একটি ব্রাউজার যা API সমর্থন করে, শুধুমাত্র নতুন স্ক্রিপ্ট লোড করা হয়। ডাইনামিক import() এর জন্য এটি সুন্দরভাবে সম্ভব হয়েছে, যা সমস্ত আধুনিক ব্রাউজার সমর্থন করে । আমি আগেই বলেছি, ঘাস আজকাল বেশ সবুজ।

Chrome DevTools দেখাচ্ছে আধুনিক ফাইল লোড হচ্ছে।
Chrome DevTools নেটওয়ার্ক ট্যাব।

ফাইল সিস্টেম অ্যাক্সেস API

তাই এখন যেহেতু আমি এটিকে সম্বোধন করেছি, এটি ফাইল সিস্টেম অ্যাক্সেস 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);
  }
};

একটি ইমেজ রপ্তানি করা প্রায় একই, কিন্তু এবার আমাকে chooseFileSystemEntries() পদ্ধতিতে 'save-file' এর একটি টাইপ প্যারামিটার পাস করতে হবে। এটি থেকে আমি একটি ফাইল সংরক্ষণ ডায়ালগ পাই। ফাইল খোলার সাথে, এটি প্রয়োজনীয় ছিল না যেহেতু 'open-file' ডিফল্ট। আমি আগের মতই accepts প্যারামিটার সেট করেছি, কিন্তু এই সময় শুধু পিএনজি ইমেজে সীমাবদ্ধ। আবার আমি একটি ফাইল হ্যান্ডেল ফিরে পাই, কিন্তু ফাইলটি পাওয়ার পরিবর্তে, এবার আমি createWritable() কল করে একটি লিখনযোগ্য স্ট্রীম তৈরি করি। এর পরে, আমি ফাইলটিতে ব্লব লিখি, যা আমার শুভেচ্ছা কার্ডের ছবি। অবশেষে, আমি লিখনযোগ্য প্রবাহ বন্ধ.

সবকিছু সর্বদা ব্যর্থ হতে পারে: ডিস্কটি স্থানের বাইরে হতে পারে, লিখতে বা পড়ার ত্রুটি হতে পারে, অথবা সম্ভবত ব্যবহারকারী ফাইল ডায়ালগটি বাতিল করে। এই কারণেই আমি সবসময় 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);
  }
};

ফাইল সিস্টেম অ্যাক্সেস API এর সাথে প্রগতিশীল বর্ধন ব্যবহার করে, আমি আগের মতো একটি ফাইল খুলতে পারি। আমদানি করা ফাইলটি সরাসরি ক্যানভাসে আঁকা হয়। আমি আমার সম্পাদনা করতে পারি এবং অবশেষে সেগুলিকে একটি বাস্তব সংরক্ষণ ডায়ালগ বক্স দিয়ে সংরক্ষণ করতে পারি যেখানে আমি ফাইলটির নাম এবং স্টোরেজ অবস্থান চয়ন করতে পারি। এখন ফাইলটি অনন্তকালের জন্য সংরক্ষণ করার জন্য প্রস্তুত।

একটি ফাইল খোলা ডায়ালগ সহ Fugu শুভেচ্ছা অ্যাপ।
ফাইল খোলা ডায়ালগ.
ফুগু গ্রিটিংস অ্যাপ এখন একটি আমদানি করা চিত্র সহ।
আমদানি করা ছবি।
পরিবর্তিত চিত্র সহ Fugu শুভেচ্ছা অ্যাপ।
পরিবর্তিত ছবি একটি নতুন ফাইলে সংরক্ষণ করা হচ্ছে।

ওয়েব শেয়ার এবং ওয়েব শেয়ার টার্গেট API

অনন্তকালের জন্য সঞ্চয় করা ছাড়াও, আমি আসলে আমার শুভেচ্ছা কার্ড ভাগ করতে চাই। এটি এমন কিছু যা ওয়েব শেয়ার এপিআই এবং ওয়েব শেয়ার টার্গেট এপিআই আমাকে করতে দেয়। মোবাইল, এবং অতি সম্প্রতি ডেস্কটপ অপারেটিং সিস্টেম বিল্ট-ইন শেয়ারিং মেকানিজম অর্জন করেছে। উদাহরণস্বরূপ, ম্যাকওএস-এ ডেস্কটপ সাফারির শেয়ার শীটটি আমার ব্লগের একটি নিবন্ধ থেকে ট্রিগার করা হয়েছে। আপনি যখন নিবন্ধ শেয়ার করুন বোতামে ক্লিক করেন, আপনি নিবন্ধের একটি লিঙ্ক বন্ধুর সাথে ভাগ করতে পারেন, উদাহরণস্বরূপ, macOS বার্তা অ্যাপের মাধ্যমে৷

ম্যাকওএস-এ ডেস্কটপ সাফারির শেয়ার শীট একটি নিবন্ধের শেয়ার বোতাম থেকে ট্রিগার হয়েছে
MacOS-এ ডেস্কটপ সাফারিতে ওয়েব শেয়ার API।

এটি ঘটতে কোডটি বেশ সহজবোধ্য। আমি navigator.share() কল করি এবং এটিকে একটি অবজেক্টে একটি ঐচ্ছিক title , text এবং url পাস করি৷ কিন্তু আমি যদি একটি ছবি সংযুক্ত করতে চাই? ওয়েব শেয়ার API-এর লেভেল 1 এটি এখনও সমর্থন করে না। ভাল খবর হল ওয়েব শেয়ার লেভেল 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);
}

ফুগু গ্রিটিং কার্ড অ্যাপ্লিকেশন দিয়ে কীভাবে এই কাজটি করা যায় তা আমি আপনাকে দেখাই। প্রথমে, আমাকে একটি ব্লব সমন্বিত একটি files অ্যারে সহ একটি data অবজেক্ট প্রস্তুত করতে হবে, এবং তারপর একটি title এবং একটি text । এর পরে, একটি সর্বোত্তম অনুশীলন হিসাবে, আমি নতুন 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);
  }
};

আগের মতো, আমি প্রগতিশীল বর্ধন ব্যবহার করি। যদি navigator অবজেক্টে 'share' এবং 'canShare' উভয়ই বিদ্যমান থাকে, তবেই আমি এগিয়ে যাই এবং dynamic import() এর মাধ্যমে share.mjs লোড করি। মোবাইল সাফারির মতো ব্রাউজারে যা শুধুমাত্র দুটি শর্তের একটি পূরণ করে, আমি কার্যকারিতা লোড করি না।

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

ফুগু গ্রিটিংস-এ, আমি যদি অ্যান্ড্রয়েডে ক্রোমের মতো একটি সমর্থনকারী ব্রাউজারে শেয়ার বোতামে ট্যাপ করি, অন্তর্নির্মিত শেয়ার শীটটি খোলে। আমি, উদাহরণস্বরূপ, Gmail চয়ন করতে পারি, এবং ইমেল কম্পোজার উইজেটটি সংযুক্ত চিত্রের সাথে পপ আপ হয়৷

OS-স্তরের শেয়ার শীট ছবি শেয়ার করার জন্য বিভিন্ন অ্যাপ দেখাচ্ছে।
ফাইল শেয়ার করার জন্য একটি অ্যাপ নির্বাচন করা হচ্ছে।
জিমেইলের ইমেল কম্পোজ উইজেট ইমেজ সংযুক্ত করুন।
ফাইলটি Gmail-এর কম্পোজারে একটি নতুন ইমেলের সাথে সংযুক্ত হয়৷

যোগাযোগ পিকার API

এর পরে, আমি পরিচিতি সম্পর্কে কথা বলতে চাই, যার অর্থ একটি ডিভাইসের ঠিকানা বই বা পরিচিতি ম্যানেজার অ্যাপ৷ আপনি যখন একটি অভিবাদন কার্ড লেখেন, তখন সঠিকভাবে কারো নাম লেখা সবসময় সহজ নাও হতে পারে। উদাহরণস্বরূপ, আমার একজন বন্ধু সের্গেই আছে যে তার নাম সিরিলিক অক্ষরে বানান করা পছন্দ করে। আমি একটি জার্মান QWERTZ কীবোর্ড ব্যবহার করছি এবং তাদের নাম কীভাবে টাইপ করব তা আমার জানা নেই। এটি একটি সমস্যা যা কন্টাক্ট পিকার এপিআই সমাধান করতে পারে। যেহেতু আমার বন্ধু আমার ফোনের পরিচিতি অ্যাপে সংরক্ষিত আছে, পরিচিতি পিকার 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');
}

ফুগু গ্রিটিং-এ, যখন আমি পরিচিতি বোতামটি আলতো চাপি এবং আমার দুটি সেরা বন্ধু, Сергей Михайлович BRIN এবং劳伦斯·爱德华·"拉里"·佩奇নির্বাচন করি, আপনি দেখতে পারেন কিভাবে পরিচিতি বাছাইকারী তাদের নাম বা ইমেল ঠিকানা দেখাতে সীমাবদ্ধ নয়, শুধুমাত্র তাদের নাম দেখানোর জন্য সংখ্যা তাদের নাম তখন আমার শুভেচ্ছা কার্ডে টানা হয়।

পরিচিতি বাছাইকারী ঠিকানা বইতে দুটি পরিচিতির নাম দেখাচ্ছে।
ঠিকানা বই থেকে পরিচিতি বাছাইকারীর সাথে দুটি নাম নির্বাচন করা।
অভিবাদন কার্ডে আঁকা দুটি পূর্বে বাছাই করা পরিচিতির নাম।
এরপর দুটি নাম অভিবাদন কার্ডে আঁকা হয়।

অ্যাসিঙ্ক্রোনাস ক্লিপবোর্ড API

পরবর্তীতে কপি এবং পেস্ট করা হয়। সফ্টওয়্যার বিকাশকারী হিসাবে আমাদের প্রিয় অপারেশনগুলির মধ্যে একটি হল কপি এবং পেস্ট। একটি অভিবাদন কার্ড লেখক হিসাবে, মাঝে মাঝে, আমি একই কাজ করতে চাই। আমি হয়ত একটি অভিবাদন কার্ডে একটি ছবি পেস্ট করতে চাই যেটিতে আমি কাজ করছি, অথবা আমার অভিবাদন কার্ডটি অনুলিপি করতে চাই যাতে আমি অন্য কোথাও থেকে এটি সম্পাদনা চালিয়ে যেতে পারি৷ Async ক্লিপবোর্ড API , টেক্সট এবং ইমেজ উভয় সমর্থন করে। ফুগু গ্রিটিংস অ্যাপে আমি কীভাবে কপি এবং পেস্ট সমর্থন যোগ করেছি সে সম্পর্কে আমি আপনাকে পথ দেখাই।

সিস্টেমের ক্লিপবোর্ডে কিছু অনুলিপি করার জন্য, আমাকে এটিতে লিখতে হবে। navigator.clipboard.write() পদ্ধতি একটি প্যারামিটার হিসাবে ক্লিপবোর্ড আইটেমগুলির একটি অ্যারে নেয়। প্রতিটি ক্লিপবোর্ড আইটেম মূলত একটি বস্তু যার একটি মান হিসাবে একটি ব্লব এবং কী হিসাবে ব্লবের ধরন৷

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

তাহলে কিভাবে এই অনুশীলনে কাজ করে? ম্যাকোস প্রিভিউ অ্যাপে আমার একটি ছবি খোলা আছে এবং ক্লিপবোর্ডে কপি করুন। যখন আমি পেস্টে ক্লিক করি, তখন ফুগু গ্রিটিংস অ্যাপ আমাকে জিজ্ঞেস করে যে আমি অ্যাপটিকে ক্লিপবোর্ডে পাঠ্য এবং ছবি দেখতে দিতে চাই কিনা।

Fugu শুভেচ্ছা অ্যাপ ক্লিপবোর্ড অনুমতি প্রম্পট দেখাচ্ছে.
ক্লিপবোর্ড অনুমতি প্রম্পট.

অবশেষে, অনুমতি গ্রহণ করার পরে, ছবিটি অ্যাপ্লিকেশনটিতে পেস্ট করা হয়। অন্য উপায় রাউন্ড কাজ, খুব. আমাকে ক্লিপবোর্ডে একটি অভিবাদন কার্ড অনুলিপি করতে দিন। যখন আমি প্রিভিউ খুলি এবং File এবং তারপর New from Clipboard-এ ক্লিক করি, তখন অভিবাদন কার্ডটি একটি নতুন শিরোনামবিহীন ছবিতে আটকানো হয়।

একটি শিরোনামহীন, শুধু পেস্ট করা চিত্র সহ macOS পূর্বরূপ অ্যাপ।
একটি ছবি macOS পূর্বরূপ অ্যাপে আটকানো হয়েছে।

ব্যাজিং এপিআই

আরেকটি দরকারী API হল ব্যাজিং API । একটি ইনস্টলযোগ্য PWA হিসাবে, Fugu Greetings-এ অবশ্যই একটি অ্যাপ আইকন রয়েছে যা ব্যবহারকারীরা অ্যাপ ডক বা হোম স্ক্রিনে রাখতে পারেন। এপিআই প্রদর্শনের একটি মজার এবং সহজ উপায় হল ফুগু গ্রিটিংস-এ এটিকে পেন স্ট্রোক কাউন্টার হিসেবে ব্যবহার করা। আমি একটি ইভেন্ট লিসেনার যোগ করেছি যা 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 নম্বর দেখাচ্ছে।
অ্যাপ আইকন ব্যাজ আকারে পেন স্ট্রোক পাল্টা.

পর্যায়ক্রমিক পটভূমি সিঙ্ক API

প্রতিটি দিন নতুন কিছু দিয়ে শুরু করতে চান? ফুগু গ্রিটিংস অ্যাপের একটি ঝরঝরে বৈশিষ্ট্য হল যে এটি আপনাকে প্রতিদিন সকালে একটি নতুন ব্যাকগ্রাউন্ড ইমেজ দিয়ে আপনার শুভেচ্ছা কার্ড শুরু করতে অনুপ্রাণিত করতে পারে। এটি অর্জন করতে অ্যাপটি পর্যায়ক্রমিক পটভূমি সিঙ্ক 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);
  }
};

দ্বিতীয় ধাপ হল পরিষেবা কর্মীর 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 ব্রাউজার দ্বারা সমর্থিত হয়। এটি ক্লায়েন্ট কোড এবং পরিষেবা কর্মী কোড উভয় ক্ষেত্রেই প্রযোজ্য। অ-সমর্থক ব্রাউজারে, তাদের কোনটিই লোড হয় না। নোট করুন কিভাবে সার্ভিস ওয়ার্কারে, একটি ডাইনামিক import() এর পরিবর্তে (যা এখনও একটি পরিষেবা কর্মী প্রসঙ্গে সমর্থিত নয়), আমি ক্লাসিক importScripts() ব্যবহার করি।

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

ফুগু গ্রিটিংস-এ, ওয়ালপেপার বোতাম টিপলে সেই দিনের শুভেচ্ছা কার্ডের ছবি দেখা যায় যা পর্যায়ক্রমিক ব্যাকগ্রাউন্ড সিঙ্ক API-এর মাধ্যমে প্রতিদিন আপডেট করা হয়।

দিনের একটি নতুন অভিবাদন কার্ড ইমেজ সহ ফুগু গ্রিটিংস অ্যাপ।
ওয়ালপেপার বোতাম টিপলে দিনের ছবি দেখা যায়।

বিজ্ঞপ্তি ট্রিগার 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');
}

যখন আমি ফুগু গ্রিটিংস-এ অনুস্মারক চেকবক্সটি চেক করি, তখন একটি প্রম্পট আমাকে জিজ্ঞাসা করে যে আমি কখন আমার শুভেচ্ছা কার্ড শেষ করার জন্য স্মরণ করিয়ে দিতে চাই৷

ফুগু গ্রিটিংস অ্যাপ একটি প্রম্পট সহ ব্যবহারকারীকে জিজ্ঞাসা করে যে তারা কখন তাদের অভিবাদন কার্ড শেষ করার জন্য স্মরণ করিয়ে দিতে চায়।
একটি অভিবাদন কার্ড শেষ করার জন্য মনে করিয়ে দেওয়ার জন্য একটি স্থানীয় বিজ্ঞপ্তির সময়সূচী করা।

যখন ফুগু গ্রিটিংস-এ একটি নির্ধারিত বিজ্ঞপ্তি ট্রিগার হয়, তখন এটি অন্য যেকোনো বিজ্ঞপ্তির মতোই দেখানো হয়, কিন্তু আমি যেমনটি আগে লিখেছিলাম, এটির জন্য নেটওয়ার্ক সংযোগের প্রয়োজন নেই৷

macOS বিজ্ঞপ্তি কেন্দ্র ফুগু গ্রিটিংস থেকে একটি ট্রিগার করা বিজ্ঞপ্তি দেখাচ্ছে।
ট্রিগার করা বিজ্ঞপ্তিটি macOS বিজ্ঞপ্তি কেন্দ্রে প্রদর্শিত হয়।

ওয়েক লক এপিআই

আমি ওয়েক লক API অন্তর্ভুক্ত করতে চাই। কখনও কখনও আপনাকে স্ক্রিনের দিকে দীর্ঘক্ষণ তাকিয়ে থাকতে হবে যতক্ষণ না অনুপ্রেরণা আপনাকে চুম্বন করে। তারপরে সবচেয়ে খারাপ যেটা ঘটতে পারে তা হল স্ক্রীন বন্ধ হয়ে যাওয়া। ওয়েক লক API এটি ঘটতে বাধা দিতে পারে।

প্রথম ধাপ হল navigator.wakelock.request method() দিয়ে একটি ওয়েক লক পাওয়া। একটি স্ক্রীন ওয়েক লক পেতে আমি এটিকে 'screen' স্ট্রিং পাস করি। আমি তখন একটি ইভেন্ট শ্রোতা যোগ করি যাতে ওয়েক লক রিলিজ হলে জানানো হয়। এটি ঘটতে পারে, উদাহরণস্বরূপ, যখন ট্যাবের দৃশ্যমানতা পরিবর্তন হয়। যদি এটি ঘটে, আমি, ট্যাবটি আবার দৃশ্যমান হলে, ওয়েক লকটি পুনরায় প্রাপ্ত করতে পারি৷

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

ফুগু গ্রিটিংস-এ, একটি অনিদ্রা চেকবক্স রয়েছে যা চেক করা হলে, স্ক্রীনকে জাগ্রত রাখে।

অনিদ্রা চেকবক্স, চেক করা হলে, পর্দা জাগ্রত রাখে।
অনিদ্রা চেকবক্স অ্যাপটিকে জাগ্রত রাখে।

নিষ্ক্রিয় সনাক্তকরণ API

মাঝে মাঝে, এমনকি আপনি যদি ঘন্টার পর ঘন্টা স্ক্রিনের দিকে তাকিয়ে থাকেন, তবে এটি কেবল অকেজো এবং আপনি আপনার অভিবাদন কার্ডের সাথে কী করবেন তা সামান্যতম ধারণা নিয়ে আসতে পারবেন না। নিষ্ক্রিয় সনাক্তকরণ 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');
}

ফুগু গ্রিটিংস অ্যাপে, যখন ক্ষণস্থায়ী চেকবক্সটি চেক করা হয় এবং ব্যবহারকারী খুব বেশি সময় ধরে নিষ্ক্রিয় থাকে তখন ক্যানভাস পরিষ্কার হয়ে যায়।

ব্যবহারকারী অনেক দিন নিষ্ক্রিয় থাকার পরে একটি পরিষ্কার ক্যানভাস সহ ফুগু গ্রিটিংস অ্যাপ।
যখন ক্ষণস্থায়ী চেকবক্সটি চেক করা হয় এবং ব্যবহারকারী অনেক সময় ধরে নিষ্ক্রিয় থাকে, তখন ক্যানভাসটি সাফ করা হয়।

বন্ধ হচ্ছে

উফ, কি একটা যাত্রা। শুধুমাত্র একটি নমুনা অ্যাপে এতগুলি API। এবং, মনে রাখবেন, আমি কখনই ব্যবহারকারীকে এমন একটি বৈশিষ্ট্যের জন্য ডাউনলোড খরচ দিতে বাধ্য করি না যা তাদের ব্রাউজার সমর্থন করে না। প্রগতিশীল বর্ধন ব্যবহার করে, আমি নিশ্চিত করি যে শুধুমাত্র প্রাসঙ্গিক কোড লোড হয়। এবং যেহেতু HTTP/2 এর সাথে, অনুরোধগুলি সস্তা, এই প্যাটার্নটি অনেকগুলি অ্যাপ্লিকেশনের জন্য ভাল কাজ করা উচিত, যদিও আপনি সত্যিই বড় অ্যাপগুলির জন্য একটি বান্ডলার বিবেচনা করতে চাইতে পারেন।

Chrome DevTools নেটওয়ার্ক প্যানেল শুধুমাত্র কোড সহ ফাইলগুলির জন্য অনুরোধগুলি দেখায় যা বর্তমান ব্রাউজার সমর্থন করে৷
Chrome DevTools নেটওয়ার্ক ট্যাব বর্তমান ব্রাউজার সমর্থন করে এমন কোড সহ ফাইলগুলির জন্য শুধুমাত্র অনুরোধগুলি দেখায়৷

প্রতিটি ব্রাউজারে অ্যাপটি একটু আলাদা দেখাতে পারে কারণ সমস্ত প্ল্যাটফর্ম সমস্ত বৈশিষ্ট্য সমর্থন করে না, তবে মূল কার্যকারিতা সর্বদা সেখানে থাকে - নির্দিষ্ট ব্রাউজারের ক্ষমতা অনুসারে ক্রমাগতভাবে উন্নত করা হয়৷ মনে রাখবেন যে এই ক্ষমতাগুলি এক এবং একই ব্রাউজারেও পরিবর্তিত হতে পারে, অ্যাপটি ইনস্টল করা অ্যাপ হিসাবে বা ব্রাউজার ট্যাবে চলছে কিনা তার উপর নির্ভর করে।

ফুগু গ্রিটিংস অ্যান্ড্রয়েড ক্রোমে চলছে, অনেকগুলি উপলব্ধ বৈশিষ্ট্য দেখাচ্ছে।
ফুগু গ্রিটিংস অ্যান্ড্রয়েড ক্রোমে চলছে।
ফুগু গ্রিটিংস ডেস্কটপ সাফারিতে চলছে, কম উপলব্ধ বৈশিষ্ট্য দেখাচ্ছে।
ডেস্কটপ সাফারিতে চলছে ফুগু শুভেচ্ছা
ফুগু গ্রিটিংস ডেস্কটপ ক্রোমে চলছে, অনেকগুলি উপলব্ধ বৈশিষ্ট্য দেখাচ্ছে।
ফুগু গ্রিটিংস ডেস্কটপ ক্রোমে চলছে।

আপনি যদি Fugu গ্রিটিংস অ্যাপে আগ্রহী হন, তাহলে GitHub-এ এটিকে খুঁজে বের করুন

গিটহাবে ফুগু গ্রিটিংস রেপো।
গিটহাবে ফুগু গ্রিটিংস অ্যাপ।

উন্নত Fugu API-এর ক্ষেত্রে ক্রোমিয়াম দল ঘাসকে আরও সবুজ করার জন্য কঠোর পরিশ্রম করছে। আমার অ্যাপের ডেভেলপমেন্টে প্রগতিশীল বর্ধন প্রয়োগ করে, আমি নিশ্চিত করি যে প্রত্যেকে একটি ভাল, শক্ত বেসলাইন অভিজ্ঞতা পায়, কিন্তু যে লোকেরা আরও বেশি ওয়েব প্ল্যাটফর্ম API সমর্থন করে এমন ব্রাউজার ব্যবহার করে তারা আরও ভাল অভিজ্ঞতা পান। আমি আপনার অ্যাপে প্রগতিশীল বর্ধনের সাথে আপনি কী করেন তা দেখার জন্য অপেক্ষা করছি।

স্বীকৃতি

আমি ক্রিশ্চিয়ান লিবেল এবং হেমান্থ এইচএম-এর কাছে কৃতজ্ঞ যারা উভয়েই ফুগু শুভেচ্ছায় অবদান রেখেছেন। এই নিবন্ধটি Joe Medley এবং Kayce Basques দ্বারা পর্যালোচনা করা হয়েছে। জ্যাক আর্চিবল্ড আমাকে একটি পরিষেবা কর্মী প্রসঙ্গে গতিশীল import() এর সাথে পরিস্থিতি খুঁজে বের করতে সাহায্য করেছে।

,

আধুনিক ব্রাউজারগুলির জন্য তৈরি করা এবং 2003 এর মতো ধীরে ধীরে উন্নত করা

2003 সালের মার্চ মাসে, নিক ফিঙ্ক এবং স্টিভ চ্যাম্পিয়ন প্রগতিশীল বর্ধনের ধারণা দিয়ে ওয়েব ডিজাইন বিশ্বকে হতবাক করে দিয়েছিলেন, ওয়েব ডিজাইনের একটি কৌশল যা প্রথমে মূল ওয়েব পৃষ্ঠার বিষয়বস্তু লোড করার উপর জোর দেয়, এবং এটি ধীরে ধীরে বিষয়বস্তুর উপরে উপস্থাপনা এবং বৈশিষ্ট্যগুলির আরও সূক্ষ্ম এবং প্রযুক্তিগতভাবে কঠোর স্তর যুক্ত করে। 2003 সালে, প্রগতিশীল বর্ধিতকরণ ছিল—সে সময়ে—আধুনিক CSS বৈশিষ্ট্য, অবাধ জাভাস্ক্রিপ্ট এবং এমনকি স্কেলেবল ভেক্টর গ্রাফিক্স ব্যবহার করা। 2020 এবং তার পরেও প্রগতিশীল উন্নতি হল আধুনিক ব্রাউজার ক্ষমতা ব্যবহার করা।

প্রগতিশীল বর্ধন সহ ভবিষ্যতের জন্য অন্তর্ভুক্তিমূলক ওয়েব ডিজাইন। Finck এবং Champeon এর মূল উপস্থাপনা থেকে শিরোনাম স্লাইড।
স্লাইড: প্রগতিশীল বর্ধন সহ ভবিষ্যতের জন্য অন্তর্ভুক্ত ওয়েব ডিজাইন। ( সূত্র )

আধুনিক জাভাস্ক্রিপ্ট

জাভাস্ক্রিপ্টের কথা বলতে গেলে, সর্বশেষ কোর ES 2015 জাভাস্ক্রিপ্ট বৈশিষ্ট্যগুলির জন্য ব্রাউজার সমর্থন পরিস্থিতি দুর্দান্ত। নতুন স্ট্যান্ডার্ডের মধ্যে রয়েছে প্রতিশ্রুতি, মডিউল, ক্লাস, টেমপ্লেট লিটারেল, অ্যারো ফাংশন, let এবং const , ডিফল্ট প্যারামিটার, জেনারেটর, ধ্বংসকারী অ্যাসাইনমেন্ট, বিশ্রাম এবং স্প্রেড, Map / Set , WeakMap / WeakSet এবং আরও অনেক কিছু। সব সমর্থিত হয় .

ES6 বৈশিষ্ট্যগুলির জন্য CanIUse সমর্থন টেবিলটি সমস্ত প্রধান ব্রাউজার জুড়ে সমর্থন দেখাচ্ছে।
ECMAScript 2015 (ES6) ব্রাউজার সমর্থন টেবিল। ( সূত্র )

Async ফাংশন, একটি ES 2017 বৈশিষ্ট্য এবং আমার ব্যক্তিগত পছন্দের একটি, সমস্ত প্রধান ব্রাউজারে ব্যবহার করা যেতে পারেasync এবং await কীওয়ার্ডগুলি অ্যাসিঙ্ক্রোনাস, প্রতিশ্রুতি-ভিত্তিক আচরণকে একটি পরিষ্কার শৈলীতে লিখতে সক্ষম করে, প্রতিশ্রুতি চেইনগুলি স্পষ্টভাবে কনফিগার করার প্রয়োজন এড়িয়ে যায়।

অ্যাসিঙ্ক ফাংশনের জন্য CanIUse সমর্থন টেবিলটি সমস্ত প্রধান ব্রাউজার জুড়ে সমর্থন দেখায়।
Async ফাংশন ব্রাউজার সমর্থন টেবিল। ( সূত্র )

এবং এমনকি অতি সাম্প্রতিক ES 2020 ভাষার সংযোজন যেমন ঐচ্ছিক চেইনিং এবং নালিশ কোলেসিং সত্যিই দ্রুত সমর্থনে পৌঁছেছে। আপনি নীচে একটি কোড নমুনা দেখতে পারেন. মূল জাভাস্ক্রিপ্ট বৈশিষ্ট্যগুলির ক্ষেত্রে, ঘাসটি আজকের তুলনায় খুব বেশি সবুজ হতে পারে না।

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
আইকনিক Windows XP সবুজ ঘাসের পটভূমির চিত্র।
মূল জাভাস্ক্রিপ্ট বৈশিষ্ট্যের ক্ষেত্রে ঘাস সবুজ। (Microsoft পণ্যের স্ক্রিনশট, অনুমতি নিয়ে ব্যবহার করা হয়েছে।)

নমুনা অ্যাপ: ফুগু শুভেচ্ছা

এই নিবন্ধটির জন্য, আমি একটি সাধারণ PWA এর সাথে কাজ করি, যার নাম Fugu Greetings ( GitHub )। এই অ্যাপটির নাম হল প্রোজেক্ট ফুগু 🐡 এর একটি টিপ, যা ওয়েবকে অ্যান্ড্রয়েড/আইওএস/ডেস্কটপ অ্যাপ্লিকেশনগুলির সমস্ত ক্ষমতা দেওয়ার একটি প্রচেষ্টা৷ আপনি এর ল্যান্ডিং পৃষ্ঠায় প্রকল্প সম্পর্কে আরও পড়তে পারেন।

ফুগু গ্রিটিংস একটি ড্রয়িং অ্যাপ যা আপনাকে ভার্চুয়াল গ্রিটিং কার্ড তৈরি করতে এবং আপনার প্রিয়জনকে পাঠাতে দেয়। এটি PWA এর মূল ধারণার উদাহরণ দেয়। এটি নির্ভরযোগ্য এবং সম্পূর্ণ অফলাইন সক্ষম, তাই আপনার নেটওয়ার্ক না থাকলেও আপনি এটি ব্যবহার করতে পারেন৷ এটি একটি ডিভাইসের হোম স্ক্রিনেও ইনস্টলযোগ্য এবং একটি স্বতন্ত্র অ্যাপ্লিকেশন হিসাবে অপারেটিং সিস্টেমের সাথে নির্বিঘ্নে সংহত করে৷

PWA সম্প্রদায়ের লোগোর অনুরূপ একটি অঙ্কন সহ ফুগু গ্রিটিংস PWA।
Fugu শুভেচ্ছা নমুনা অ্যাপ্লিকেশন.

প্রগতিশীল বর্ধন

এই পথের বাইরে, এটি প্রগতিশীল বর্ধন সম্পর্কে কথা বলার সময়। MDN ওয়েব ডক্স গ্লোসারী ধারণাটিকে নিম্নরূপ সংজ্ঞায়িত করে :

প্রোগ্রেসিভ এনহান্সমেন্ট হল একটি ডিজাইনের দর্শন যা যতটা সম্ভব ব্যবহারকারীদের জন্য প্রয়োজনীয় বিষয়বস্তু এবং কার্যকারিতার একটি বেসলাইন প্রদান করে, এবং শুধুমাত্র সবচেয়ে আধুনিক ব্রাউজারগুলির ব্যবহারকারীদের জন্য সর্বোত্তম সম্ভাব্য অভিজ্ঞতা প্রদান করে যা সমস্ত প্রয়োজনীয় কোড চালাতে পারে।

বৈশিষ্ট্য সনাক্তকরণ সাধারণত ব্রাউজারগুলি আরও আধুনিক কার্যকারিতা পরিচালনা করতে পারে কিনা তা নির্ধারণ করতে ব্যবহৃত হয়, যখন জাভাস্ক্রিপ্টের সাথে অনুপস্থিত বৈশিষ্ট্যগুলি যোগ করতে প্রায়শই পলিফিল ব্যবহার করা হয়।

[…]

প্রোগ্রেসিভ এনহান্সমেন্ট হল একটি দরকারী কৌশল যা ওয়েব ডেভেলপারদেরকে একাধিক অজানা ব্যবহারকারী এজেন্টের উপর কাজ করার সময় সম্ভাব্য সর্বোত্তম ওয়েবসাইট তৈরিতে ফোকাস করতে দেয়। করুণাময় অবক্ষয় সম্পর্কিত, কিন্তু একই জিনিস নয় এবং প্রায়শই প্রগতিশীল বর্ধনের বিপরীত দিকে যেতে দেখা যায়। বাস্তবে, উভয় পদ্ধতিই বৈধ এবং প্রায়শই একে অপরের পরিপূরক হতে পারে।

MDN অবদানকারীরা

স্ক্র্যাচ থেকে প্রতিটি শুভেচ্ছা কার্ড শুরু করা সত্যিই কষ্টকর হতে পারে। তাহলে কেন এমন একটি বৈশিষ্ট্য নেই যা ব্যবহারকারীদের একটি চিত্র আমদানি করতে এবং সেখান থেকে শুরু করতে দেয়? একটি ঐতিহ্যগত পদ্ধতির সাথে, আপনি এটি ঘটতে একটি <input type=file> উপাদান ব্যবহার করেছেন। প্রথমত, আপনি উপাদানটি তৈরি করবেন, এটির type 'file' এ সেট করুন এবং accept সম্পত্তিতে MIME প্রকারগুলি যোগ করুন এবং তারপর প্রোগ্রাম্যাটিকভাবে এটিতে "ক্লিক করুন" এবং পরিবর্তনগুলি শুনুন। আপনি যখন একটি ছবি নির্বাচন করেন, এটি সরাসরি ক্যানভাসে আমদানি করা হয়।

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 অ্যাট্রিবিউট এবং একটি ব্লব URL এর href হিসাবে একটি অ্যাঙ্কর লিঙ্ক তৈরি করা। আপনি ডাউনলোডটি ট্রিগার করার জন্য এটিকে প্রোগ্রাম্যাটিকভাবে "ক্লিক করুন" এবং মেমরি লিক প্রতিরোধ করতে, আশা করি ব্লব অবজেক্ট URL প্রত্যাহার করতে ভুলবেন না।

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

কিন্তু এক মিনিট অপেক্ষা করুন। মানসিকভাবে, আপনি একটি অভিবাদন কার্ড "ডাউনলোড" করেননি, আপনি এটি "সংরক্ষিত" করেছেন। আপনাকে একটি "সংরক্ষণ করুন" ডায়ালগ দেখানোর পরিবর্তে যা আপনাকে ফাইলটি কোথায় রাখতে হবে তা চয়ন করতে দেয়, ব্রাউজারটি ব্যবহারকারীর ইন্টারঅ্যাকশন ছাড়াই সরাসরি অভিবাদন কার্ডটি ডাউনলোড করেছে এবং এটি সরাসরি আপনার ডাউনলোড ফোল্ডারে রেখেছে৷ এই মহান না.

যদি একটি ভাল উপায় ছিল? যদি আপনি শুধুমাত্র একটি স্থানীয় ফাইল খুলতে পারেন, এটি সম্পাদনা করতে পারেন, এবং তারপরে পরিবর্তনগুলি সংরক্ষণ করতে পারেন, হয় একটি নতুন ফাইলে, অথবা আপনি যেটি শুরুতে খুলেছিলেন সেটিতে ফিরে যেতে? সেখানে দেখা যাচ্ছে. ফাইল সিস্টেম অ্যাক্সেস API আপনাকে ফাইল এবং ডিরেক্টরি খুলতে এবং তৈরি করতে দেয়, সেইসাথে সেগুলিকে সংশোধন এবং সংরক্ষণ করতে দেয়।

তাই আমি কিভাবে একটি API বৈশিষ্ট্য সনাক্ত করতে পারি? ফাইল সিস্টেম অ্যাক্সেস 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'),
    ]);
  }
};

কিন্তু আমি ফাইল সিস্টেম অ্যাক্সেস API বিবরণে ডুব দেওয়ার আগে, আমাকে এখানে প্রগতিশীল বর্ধিতকরণ প্যাটার্নটি দ্রুত হাইলাইট করতে দিন। বর্তমানে ফাইল সিস্টেম অ্যাক্সেস API সমর্থন করে না এমন ব্রাউজারগুলিতে, আমি লিগ্যাসি স্ক্রিপ্টগুলি লোড করি। আপনি নিচে Firefox এবং Safari এর নেটওয়ার্ক ট্যাব দেখতে পারেন।

সাফারি ওয়েব ইন্সপেক্টর লিগ্যাসি ফাইল লোড হচ্ছে দেখাচ্ছে।
সাফারি ওয়েব ইন্সপেক্টর নেটওয়ার্ক ট্যাব।
ফায়ারফক্স ডেভেলপার টুল লিগ্যাসি ফাইল লোড হচ্ছে দেখাচ্ছে।
ফায়ারফক্স ডেভেলপার টুলস নেটওয়ার্ক ট্যাব।

যাইহোক, Chrome এ, একটি ব্রাউজার যা API সমর্থন করে, শুধুমাত্র নতুন স্ক্রিপ্ট লোড করা হয়। ডাইনামিক import() এর জন্য এটি সুন্দরভাবে সম্ভব হয়েছে, যা সমস্ত আধুনিক ব্রাউজার সমর্থন করে । আমি আগেই বলেছি, ঘাস আজকাল বেশ সবুজ।

Chrome DevTools দেখাচ্ছে আধুনিক ফাইল লোড হচ্ছে।
Chrome DevTools নেটওয়ার্ক ট্যাব।

ফাইল সিস্টেম অ্যাক্সেস API

তাই এখন যেহেতু আমি এটিকে সম্বোধন করেছি, এটি ফাইল সিস্টেম অ্যাক্সেস 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);
  }
};

একটি ইমেজ রপ্তানি করা প্রায় একই, কিন্তু এবার আমাকে chooseFileSystemEntries() পদ্ধতিতে 'save-file' এর একটি টাইপ প্যারামিটার পাস করতে হবে। এটি থেকে আমি একটি ফাইল সংরক্ষণ ডায়ালগ পাই। ফাইল খোলার সাথে, এটি প্রয়োজনীয় ছিল না যেহেতু 'open-file' ডিফল্ট। আমি আগের মতই accepts প্যারামিটার সেট করেছি, কিন্তু এই সময় শুধু পিএনজি ইমেজে সীমাবদ্ধ। আবার আমি একটি ফাইল হ্যান্ডেল ফিরে পাই, কিন্তু ফাইলটি পাওয়ার পরিবর্তে, এবার আমি createWritable() কল করে একটি লিখনযোগ্য স্ট্রীম তৈরি করি। এর পরে, আমি ফাইলটিতে ব্লব লিখি, যা আমার শুভেচ্ছা কার্ডের ছবি। অবশেষে, আমি লিখনযোগ্য প্রবাহ বন্ধ.

সবকিছু সর্বদা ব্যর্থ হতে পারে: ডিস্কটি স্থানের বাইরে হতে পারে, লিখতে বা পড়ার ত্রুটি হতে পারে, অথবা সম্ভবত ব্যবহারকারী ফাইল ডায়ালগটি বাতিল করে। এই কারণেই আমি সবসময় 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);
  }
};

ফাইল সিস্টেম অ্যাক্সেস API এর সাথে প্রগতিশীল বর্ধন ব্যবহার করে, আমি আগের মতো একটি ফাইল খুলতে পারি। আমদানি করা ফাইলটি সরাসরি ক্যানভাসে আঁকা হয়। আমি আমার সম্পাদনা করতে পারি এবং অবশেষে সেগুলিকে একটি বাস্তব সংরক্ষণ ডায়ালগ বক্স দিয়ে সংরক্ষণ করতে পারি যেখানে আমি ফাইলটির নাম এবং স্টোরেজ অবস্থান চয়ন করতে পারি। এখন ফাইলটি অনন্তকালের জন্য সংরক্ষণ করার জন্য প্রস্তুত।

একটি ফাইল খোলা ডায়ালগ সহ Fugu শুভেচ্ছা অ্যাপ।
ফাইল খোলা ডায়ালগ.
ফুগু গ্রিটিংস অ্যাপ এখন একটি আমদানি করা চিত্র সহ।
আমদানি করা ছবি।
পরিবর্তিত চিত্র সহ Fugu শুভেচ্ছা অ্যাপ।
একটি নতুন ফাইলে পরিবর্তিত চিত্র সংরক্ষণ করা।

ওয়েব শেয়ার এবং ওয়েব শেয়ার লক্ষ্য এপিআই

অনন্তকাল সংরক্ষণ করা ছাড়াও, আমি আসলে আমার গ্রিটিং কার্ডটি ভাগ করতে চাই। এটি এমন কিছু যা ওয়েব শেয়ার এপিআই এবং ওয়েব শেয়ার টার্গেট এপিআই আমাকে করার অনুমতি দেয়। মোবাইল, এবং আরও সম্প্রতি ডেস্কটপ অপারেটিং সিস্টেমগুলি অন্তর্নির্মিত ভাগ করে নেওয়ার প্রক্রিয়া অর্জন করেছে। উদাহরণস্বরূপ, নীচে আমার ব্লগের একটি নিবন্ধ থেকে ট্রিগার করা ম্যাকোসে ডেস্কটপ সাফারির শেয়ার শীট রয়েছে। আপনি যখন শেয়ার নিবন্ধ বোতামটি ক্লিক করেন, আপনি কোনও বন্ধুর সাথে নিবন্ধের একটি লিঙ্ক ভাগ করতে পারেন, উদাহরণস্বরূপ, ম্যাকোস বার্তা অ্যাপের মাধ্যমে।

ম্যাকোসে ডেস্কটপ সাফারির শেয়ার শিটটি একটি নিবন্ধের শেয়ার বোতাম থেকে ট্রিগার করেছে
ম্যাকোসে ডেস্কটপ সাফারিটিতে ওয়েব শেয়ার এপিআই শেয়ার করুন।

এটি ঘটানোর কোডটি বেশ সোজা। আমি navigator.share() কল করি এবং এটিকে কোনও অবজেক্টে একটি al চ্ছিক title , text এবং url পাস করি। তবে আমি যদি কোনও চিত্র সংযুক্ত করতে চাই? ওয়েব শেয়ার এপিআইয়ের স্তর 1 এখনও এটি সমর্থন করে না। সুসংবাদটি হ'ল ওয়েব শেয়ার স্তর 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);
}

ফুগু গ্রিটিং কার্ড অ্যাপ্লিকেশন দিয়ে কীভাবে এই কাজটি করবেন তা আমাকে দেখাতে দিন। প্রথমত, আমাকে একটি ব্লব সমন্বিত একটি files অ্যারে এবং তারপরে একটি title এবং একটি text সহ একটি data অবজেক্ট প্রস্তুত করতে হবে। এরপরে, একটি সেরা অনুশীলন হিসাবে, আমি নতুন 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);
  }
};

আগের মতো, আমি প্রগতিশীল বর্ধন ব্যবহার করি। যদি navigator অবজেক্টে 'share' এবং 'canShare' উভয়ই উপস্থিত থাকে তবে আমি কেবল এগিয়ে যাই এবং গতিশীল import() এর মাধ্যমে share.mjs লোড করি। মোবাইল সাফারির মতো ব্রাউজারগুলিতে যা কেবল দুটি শর্তের মধ্যে একটি পূরণ করে, আমি কার্যকারিতাটি লোড করি না।

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

ফুগু গ্রিটিংসে, আমি যদি অ্যান্ড্রয়েডের ক্রোমের মতো কোনও সমর্থনকারী ব্রাউজারে শেয়ার বোতামটি ট্যাপ করি তবে অন্তর্নির্মিত শেয়ার শীটটি খোলে। আমি উদাহরণস্বরূপ, জিমেইল চয়ন করতে পারি এবং ইমেল সুরকার উইজেটটি চিত্রটি সংযুক্ত করে পপ আপ করতে পারে।

ওএস-লেভেল শেয়ার শিটটি চিত্রটি ভাগ করে নেওয়ার জন্য বিভিন্ন অ্যাপ্লিকেশন দেখায়।
ফাইলটি ভাগ করে নেওয়ার জন্য একটি অ্যাপ্লিকেশন নির্বাচন করা।
জিমেইলের ইমেলটি চিত্রটি সংযুক্ত করে উইজেট রচনা করে।
ফাইলটি জিমেইলের সুরকারে একটি নতুন ইমেলের সাথে সংযুক্ত হয়ে যায়।

যোগাযোগ পিকার এপিআই

এরপরে, আমি পরিচিতিগুলি সম্পর্কে কথা বলতে চাই, যার অর্থ কোনও ডিভাইসের ঠিকানা বই বা পরিচিতি পরিচালক অ্যাপ্লিকেশন। আপনি যখন কোনও গ্রিটিং কার্ড লিখেন, কারও নাম সঠিকভাবে লিখতে সর্বদা সহজ নাও হতে পারে। উদাহরণস্বরূপ, আমার এক বন্ধু সের্গেই রয়েছে যিনি তার নামটি সিরিলিক চিঠিতে বানান করতে পছন্দ করেন। আমি একটি জার্মান কিউয়ার্টজ কীবোর্ড ব্যবহার করছি এবং কীভাবে তাদের নাম টাইপ করবেন তা কোনও ধারণা নেই। এটি এমন একটি সমস্যা যা যোগাযোগের পিকার এপিআই সমাধান করতে পারে। যেহেতু আমার বন্ধুটি আমার ফোনের পরিচিতি অ্যাপে সঞ্চিত আছে, পরিচিতি পিকার এপিআইয়ের মাধ্যমে, আমি ওয়েব থেকে আমার পরিচিতিগুলিতে ট্যাপ করতে পারি।

প্রথমত, আমি অ্যাক্সেস করতে চাই এমন সম্পত্তিগুলির তালিকা নির্দিষ্ট করতে হবে। এই ক্ষেত্রে, আমি কেবল নামগুলি চাই, তবে অন্যান্য ব্যবহারের ক্ষেত্রে আমি টেলিফোন নম্বর, ইমেল, অবতার আইকন বা শারীরিক ঠিকানায় আগ্রহী হতে পারি। এরপরে, আমি একটি 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);
  }
};

এবং এতক্ষণে আপনি সম্ভবত প্যাটার্নটি শিখেছেন: আমি কেবল ফাইলটি লোড করি যখন এপিআই আসলে সমর্থিত হয়।

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

ফুগু গ্রিটিংয়ে, যখন আমি পরিচিতি বোতামটি ট্যাপ করি এবং আমার দুটি সেরা পাল নির্বাচন করি, сергей майловч б б брин এবং劳伦斯 劳伦斯 爱德华 · "拉里" 拉里 "拉里" 拉里 · 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇সংখ্যা তাদের নামগুলি তখন আমার গ্রিটিং কার্ডে আঁকা হয়।

ঠিকানা বইতে দুটি পরিচিতির নাম দেখানো পরিচিতি পিকার।
ঠিকানা বই থেকে যোগাযোগের পিকার সহ দুটি নাম নির্বাচন করা।
গ্রিটিং কার্ডে আঁকা দুটি আগে বাছাই করা পরিচিতির নাম।
তারপরে দুটি নাম গ্রিটিং কার্ডের দিকে আঁকুন।

অ্যাসিঙ্ক্রোনাস ক্লিপবোর্ড API

পরের দিকে অনুলিপি করা এবং আটকানো হচ্ছে। সফ্টওয়্যার বিকাশকারী হিসাবে আমাদের প্রিয় অপারেশনগুলির মধ্যে একটি হ'ল অনুলিপি এবং পেস্ট। একজন গ্রিটিং কার্ড লেখক হিসাবে, মাঝে মাঝে আমিও এটি করতে চাই। আমি হয় আমি কাজ করছি এমন একটি গ্রিটিং কার্ডে কোনও চিত্র পেস্ট করতে চাই, বা আমার গ্রিটিং কার্ডটি অনুলিপি করতে চাই যাতে আমি এটি অন্য কোথাও থেকে সম্পাদনা চালিয়ে যেতে পারি। অ্যাসিঙ্ক ক্লিপবোর্ড এপিআই , পাঠ্য এবং চিত্র উভয়কেই সমর্থন করে। আমি কীভাবে ফুগু গ্রিটিংস অ্যাপ্লিকেশনটিতে অনুলিপি এবং পেস্ট সমর্থন যুক্ত করেছি তা দিয়ে আমাকে আপনাকে চলতে দিন।

সিস্টেমের ক্লিপবোর্ডে কিছু অনুলিপি করার জন্য, আমার এটিতে লিখতে হবে। navigator.clipboard.write() পদ্ধতিটি প্যারামিটার হিসাবে ক্লিপবোর্ড আইটেমগুলির একটি অ্যারে নেয়। প্রতিটি ক্লিপবোর্ড আইটেমটি মূলত একটি মান হিসাবে একটি ব্লব সহ একটি বস্তু এবং কী হিসাবে ব্লবের ধরণ।

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 ক্ষেত্র রয়েছে যা আমাকে উপলব্ধ সংস্থানগুলির মাইম প্রকারগুলি বলে। আমি ক্লিপবোর্ড আইটেমটির getType() পদ্ধতিটি কল করি, আমি আগে প্রাপ্ত মাইম টাইপটি পাস করি।

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

তাহলে কিভাবে এই অনুশীলনে কাজ করে? আমার কাছে ম্যাকোস পূর্বরূপ অ্যাপ্লিকেশনটিতে একটি চিত্র খোলা আছে এবং এটি ক্লিপবোর্ডে অনুলিপি করে। আমি যখন পেস্ট ক্লিক করি, তখন ফুগু গ্রিটিংস অ্যাপটি আমাকে জিজ্ঞাসা করে যে আমি অ্যাপ্লিকেশনটিকে ক্লিপবোর্ডে পাঠ্য এবং চিত্রগুলি দেখতে অনুমতি দিতে চাই কিনা।

ফুগু গ্রিটিংস অ্যাপ্লিকেশন ক্লিপবোর্ডের অনুমতি প্রম্পট দেখায়।
ক্লিপবোর্ড অনুমতি প্রম্পট।

অবশেষে, অনুমতি গ্রহণের পরে, চিত্রটি পরে অ্যাপ্লিকেশনটিতে আটকানো হয়। অন্যভাবে রাউন্ডও কাজ করে। আমাকে ক্লিপবোর্ডে একটি গ্রিটিং কার্ড অনুলিপি করতে দিন। আমি তখন পূর্বরূপ খুলি এবং ফাইল এবং তারপরে ক্লিপবোর্ড থেকে নতুন ক্লিক করে, গ্রিটিং কার্ডটি একটি নতুন শিরোনামহীন চিত্রের মধ্যে আটকানো হয়।

একটি শিরোনামহীন, সবেমাত্র পেস্ট করা চিত্র সহ ম্যাকোস পূর্বরূপ অ্যাপ্লিকেশন।
একটি চিত্র ম্যাকোস পূর্বরূপ অ্যাপ্লিকেশনটিতে আটকানো হয়েছে।

ব্যাজিং এপিআই

আর একটি দরকারী এপিআই হ'ল ব্যাজিং এপিআই । একটি ইনস্টলযোগ্য পিডব্লিউএ হিসাবে, ফুগু গ্রিটিংসের অবশ্যই একটি অ্যাপ আইকন রয়েছে যা ব্যবহারকারীরা অ্যাপ ডক বা হোম স্ক্রিনে রাখতে পারেন। এপিআই প্রদর্শনের একটি মজাদার এবং সহজ উপায় হ'ল এটি কলম স্ট্রোক কাউন্টার হিসাবে ফুগু গ্রিটিংসে এটি ব্যবহার করা। আমি একটি ইভেন্ট শ্রোতা যুক্ত করেছি যা যখনই 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 নম্বর দেখায়।
পেন স্ট্রোকগুলি অ্যাপ্লিকেশন আইকন ব্যাজ আকারে কাউন্টার।

পর্যায়ক্রমিক পটভূমি সিঙ্ক এপিআই

নতুন কিছু দিয়ে সতেজ প্রতিটি দিন শুরু করতে চান? ফুগু গ্রিটিংস অ্যাপ্লিকেশনটির একটি ঝরঝরে বৈশিষ্ট্য হ'ল এটি আপনার গ্রিটিং কার্ডটি শুরু করতে একটি নতুন ব্যাকগ্রাউন্ড চিত্র সহ প্রতিদিন সকালে আপনাকে অনুপ্রাণিত করতে পারে। এটি অর্জন করতে অ্যাপ্লিকেশনটি পর্যায়ক্রমিক পটভূমি সিঙ্ক এপিআই ব্যবহার করে।

প্রথম পদক্ষেপটি হ'ল পরিষেবা কর্মী নিবন্ধনে পর্যায়ক্রমিক সিঙ্ক ইভেন্টটি নিবন্ধিত করা । এটি '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);
  }
};

দ্বিতীয় পদক্ষেপটি হ'ল পরিষেবা কর্মীর মধ্যে 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,
          });
        });
      })()
    );
  }
});

আবার এটি সত্যই একটি প্রগতিশীল বর্ধন, সুতরাং কোডটি কেবল তখনই লোড হয় যখন এপিআই ব্রাউজার দ্বারা সমর্থিত হয়। এটি ক্লায়েন্ট কোড এবং পরিষেবা কর্মী কোড উভয়ের ক্ষেত্রেই প্রযোজ্য। সমর্থনকারী ব্রাউজারগুলিতে, তাদের কোনওটিই লোড হয় না। কীভাবে পরিষেবা কর্মীর মধ্যে গতিশীল import() এর পরিবর্তে (এটি কোনও পরিষেবা কর্মী প্রসঙ্গে সমর্থিত নয়) এর পরিবর্তে আমি ক্লাসিক importScripts() ব্যবহার করি।

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

ফুগু গ্রিটিংসে, ওয়ালপেপার বোতাম টিপে পর্যায়ক্রমিক পটভূমি সিঙ্ক এপিআইয়ের মাধ্যমে প্রতিদিন আপডেট হওয়া দিনের গ্রিটিং কার্ড চিত্রটি প্রকাশ করে।

দিনের একটি নতুন গ্রিটিং কার্ড চিত্র সহ ফুগু গ্রিটিংস অ্যাপ।
ওয়ালপেপার বোতাম টিপে দিনের চিত্রটি প্রদর্শন করে।

বিজ্ঞপ্তি ট্রিগার এপিআই

কখনও কখনও এমনকি প্রচুর অনুপ্রেরণা সহ, আপনার একটি সূচনা গ্রিটিং কার্ড শেষ করতে একটি নজর দরকার। এটি এমন একটি বৈশিষ্ট্য যা বিজ্ঞপ্তি ট্রিগার এপিআই দ্বারা সক্ষম করা হয়। একজন ব্যবহারকারী হিসাবে, আমি এমন একটি সময় প্রবেশ করতে পারি যখন আমি আমার গ্রিটিং কার্ডটি শেষ করতে নগ্ন হতে চাই। যখন সেই সময়টি আসে, আমি একটি বিজ্ঞপ্তি পাব যে আমার গ্রিটিং কার্ডটি অপেক্ষা করছে।

লক্ষ্য সময়ের জন্য অনুরোধ জানানোর পরে, অ্যাপ্লিকেশনটি একটি 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');
}

আমি যখন ফুগু গ্রিটিংসে অনুস্মারক চেকবক্সটি পরীক্ষা করি, তখন একটি প্রম্পট আমাকে জিজ্ঞাসা করে যখন আমি আমার গ্রিটিং কার্ডটি শেষ করতে স্মরণ করিয়ে দিতে চাই।

ফুগু গ্রিটিংস অ্যাপ্লিকেশনটি একটি প্রম্পট সহ ব্যবহারকারীকে তাদের কখন তাদের গ্রিটিং কার্ড শেষ করতে মনে করিয়ে দিতে চায় তা জিজ্ঞাসা করে।
একটি গ্রিটিং কার্ড শেষ করার জন্য একটি স্থানীয় বিজ্ঞপ্তি নির্ধারণের সময় নির্ধারণ করা।

যখন কোনও নির্ধারিত বিজ্ঞপ্তি ফুগু গ্রিটিংসে ট্রিগার করে, তখন এটি অন্য কোনও বিজ্ঞপ্তির মতোই দেখানো হয়, তবে আমি যেমন আগে লিখেছি, এটির জন্য কোনও নেটওয়ার্ক সংযোগের প্রয়োজন নেই।

ম্যাকোস বিজ্ঞপ্তি কেন্দ্র ফুগু গ্রিটিংস থেকে একটি ট্রিগারযুক্ত বিজ্ঞপ্তি দেখায়।
ট্রিগারযুক্ত বিজ্ঞপ্তি ম্যাকোস বিজ্ঞপ্তি কেন্দ্রে উপস্থিত হয়।

ওয়েক লক এপিআই

আমি ওয়েক লক এপিআই অন্তর্ভুক্ত করতে চাই। কখনও কখনও আপনাকে অনুপ্রেরণা আপনাকে চুম্বন না করা পর্যন্ত স্ক্রিনে যথেষ্ট দীর্ঘ তাকাতে হবে। তখন সবচেয়ে খারাপটি ঘটতে পারে তা হ'ল স্ক্রিনটি বন্ধ করা। ওয়েক লক এপিআই এটি ঘটতে বাধা দিতে পারে।

প্রথম পদক্ষেপটি হ'ল navigator.wakelock.request method() এর সাথে একটি জাগ্রত লক পাওয়া। আমি স্ক্রিন ওয়েক লক পেতে এটি 'screen' এর স্ট্রিংটি পাস করি। আমি তখন ওয়েক লকটি প্রকাশের সময় অবহিত করার জন্য একটি ইভেন্ট শ্রোতা যুক্ত করি। এটি ঘটতে পারে, উদাহরণস্বরূপ, যখন ট্যাব দৃশ্যমানতা পরিবর্তন হয়। যদি এটি ঘটে থাকে তবে আমি করতে পারি, যখন ট্যাবটি আবার দৃশ্যমান হয়ে যায়, তখন ওয়েক লকটি পুনরায় আবদ্ধ করুন।

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

হ্যাঁ, এটি একটি প্রগতিশীল বর্ধন, তাই ব্রাউজারটি এপিআই সমর্থন করলে আমার কেবল এটি লোড করা দরকার।

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

ফুগু গ্রিটিংসে, একটি অনিদ্রা চেকবক্স রয়েছে যা পরীক্ষা করা হলে স্ক্রিনটি জাগ্রত রাখে।

অনিদ্রা চেকবক্স, যদি চেক করা হয় তবে স্ক্রিনটি জাগ্রত রাখে।
অনিদ্রা চেকবক্স অ্যাপকে জাগ্রত রাখে।

নিষ্ক্রিয় সনাক্তকরণ 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');
}

ফুগু গ্রিটিংস অ্যাপ্লিকেশনটিতে, যখন ইফেমেরাল চেকবক্সটি চেক করা হয় এবং ব্যবহারকারী খুব দীর্ঘ সময়ের জন্য অলস থাকে তখন ক্যানভাস পরিষ্কার হয়।

ব্যবহারকারী খুব বেশি সময় ধরে অলস হওয়ার পরে একটি ক্লিয়ারড ক্যানভাস সহ ফুগু গ্রিটিংস অ্যাপ।
যখন ইফেমেরাল চেকবক্সটি পরীক্ষা করা হয় এবং ব্যবহারকারী খুব বেশি সময় ধরে নিষ্ক্রিয় হয়ে থাকে, ক্যানভাসটি সাফ হয়ে যায়।

বন্ধ হচ্ছে

ভাই, কি যাত্রা। কেবলমাত্র একটি নমুনা অ্যাপে অনেকগুলি এপিআই। এবং, মনে রাখবেন, আমি ব্যবহারকারীকে তাদের ব্রাউজারটি সমর্থন করে না এমন কোনও বৈশিষ্ট্যের জন্য ডাউনলোডের ব্যয়টি কখনই প্রদান করতে পারি না। প্রগতিশীল বর্ধন ব্যবহার করে, আমি নিশ্চিত করি যে কেবল প্রাসঙ্গিক কোডটি লোড হয়ে যায়। এবং যেহেতু এইচটিটিপি/2 এর সাথে, অনুরোধগুলি সস্তা, এই প্যাটার্নটি প্রচুর অ্যাপ্লিকেশনগুলির জন্য ভাল কাজ করা উচিত, যদিও আপনি সত্যিই বড় অ্যাপ্লিকেশনগুলির জন্য একটি বান্ডলার বিবেচনা করতে চাইতে পারেন।

ক্রোম ডেভটুলস নেটওয়ার্ক প্যানেল কেবলমাত্র কোড সহ ফাইলগুলির জন্য অনুরোধগুলি দেখায় যা বর্তমান ব্রাউজারটি সমর্থন করে।
ক্রোম ডেভটুলস নেটওয়ার্ক ট্যাব কেবলমাত্র কোড সহ ফাইলগুলির জন্য অনুরোধগুলি দেখায় যা বর্তমান ব্রাউজারটি সমর্থন করে।

অ্যাপ্লিকেশনটি প্রতিটি ব্রাউজারে কিছুটা আলাদা দেখতে পারে যেহেতু সমস্ত প্ল্যাটফর্মগুলি সমস্ত বৈশিষ্ট্য সমর্থন করে না, তবে মূল কার্যকারিতা সর্বদা সেখানে থাকে - নির্দিষ্ট ব্রাউজারের ক্ষমতা অনুযায়ী প্রোগ্রামভাবে বর্ধিত। নোট করুন যে অ্যাপ্লিকেশনটি ইনস্টল করা অ্যাপ্লিকেশন হিসাবে বা ব্রাউজার ট্যাবে চলছে কিনা তার উপর নির্ভর করে এই ক্ষমতাগুলিও এক এবং একই ব্রাউজারে পরিবর্তিত হতে পারে।

ফুগু গ্রিটিংস অ্যান্ড্রয়েড ক্রোমে চলছে, অনেকগুলি উপলভ্য বৈশিষ্ট্য দেখানো হচ্ছে।
অ্যান্ড্রয়েড ক্রোমে ফুগু শুভেচ্ছা চলছে।
ফুগু গ্রিটিংস ডেস্কটপ সাফারিতে চলছে, কম উপলভ্য বৈশিষ্ট্যগুলি দেখায়।
ফুগু শুভেচ্ছা ডেস্কটপ সাফারিতে চলছে।
ফুগু গ্রিটিংস ডেস্কটপ ক্রোমে চলছে, অনেকগুলি উপলভ্য বৈশিষ্ট্য দেখানো হয়েছে।
ডেস্কটপ ক্রোমে ফুগু শুভেচ্ছা চলছে।

আপনি যদি ফুগু গ্রিটিংস অ্যাপ্লিকেশনটিতে আগ্রহী হন তবে গিথুবে এটি সন্ধান করুন এবং কাঁটাচামচ করুন

গিথুবের উপর ফুগু গ্রিটিংস রেপো।
গিথুবে ফুগু গ্রিটিংস অ্যাপ।

উন্নত ফুগু এপিআইয়ের ক্ষেত্রে ক্রোমিয়াম দল ঘাসকে সবুজ করে তুলতে কঠোর পরিশ্রম করছে। আমার অ্যাপ্লিকেশনটির বিকাশে প্রগতিশীল বর্ধন প্রয়োগ করে, আমি নিশ্চিত হয়েছি যে প্রত্যেকে একটি ভাল, শক্ত বেসলাইন অভিজ্ঞতা পেয়েছে তবে আরও ওয়েব প্ল্যাটফর্ম এপিআইগুলিকে সমর্থনকারী ব্রাউজারগুলি ব্যবহার করে এমন লোকেরা আরও ভাল অভিজ্ঞতা অর্জন করে। আমি আপনার অ্যাপ্লিকেশনগুলিতে প্রগতিশীল বর্ধনের সাথে আপনি কী করেন তা দেখার অপেক্ষায় রয়েছি।

স্বীকৃতি

আমি খ্রিস্টান লাইবেল এবং হেমানথ এইচএম এর প্রতি কৃতজ্ঞ যারা দুজনেই ফুগু শুভেচ্ছায় অবদান রেখেছেন। এই নিবন্ধটি জো মেডলে এবং কায়েস বাস্কস পর্যালোচনা করেছেন। জ্যাক আর্চিবাল্ড আমাকে পরিষেবা কর্মী প্রসঙ্গে গতিশীল import() দিয়ে পরিস্থিতি খুঁজে পেতে সহায়তা করেছিলেন।

,

আধুনিক ব্রাউজারগুলির জন্য বিল্ডিং এবং এটি 2003 এর মতো ক্রমান্বয়ে বাড়ানো

২০০৩ সালের মার্চ মাসে, নিক ফিনক এবং স্টিভ চ্যাম্পিয়ন প্রগ্রেসিভ বর্ধনের ধারণাটি দিয়ে ওয়েব ডিজাইন বিশ্বকে স্তম্ভিত করেছিলেন, ওয়েব ডিজাইনের জন্য একটি কৌশল যা প্রথমে কোর ওয়েব পৃষ্ঠার সামগ্রী লোড করার উপর জোর দেয় এবং তারপরে ক্রমবর্ধমানভাবে সামগ্রীর শীর্ষে উপস্থাপনা এবং বৈশিষ্ট্যগুলির আরও সংক্ষিপ্ত এবং প্রযুক্তিগতভাবে কঠোর স্তর যুক্ত করে। 2003 সালে, প্রগতিশীল বর্ধন ছিল - সময়ে - সময়ের মধ্যে - মিনিট সিএসএস বৈশিষ্ট্যগুলি, অবিচ্ছিন্ন জাভাস্ক্রিপ্ট এবং এমনকি কেবল স্কেলেবল ভেক্টর গ্রাফিক্স ব্যবহার সম্পর্কে। 2020 এবং এর বাইরেও প্রগতিশীল বর্ধন আধুনিক ব্রাউজার ক্ষমতা ব্যবহার সম্পর্কে।

প্রগতিশীল বর্ধনের সাথে ভবিষ্যতের জন্য অন্তর্ভুক্ত ওয়েব ডিজাইন। ফিনক এবং চ্যাম্পিয়নের মূল উপস্থাপনা থেকে শিরোনাম স্লাইড।
স্লাইড: প্রগতিশীল বর্ধনের সাথে ভবিষ্যতের জন্য অন্তর্ভুক্ত ওয়েব ডিজাইন। ( সূত্র )

আধুনিক জাভাস্ক্রিপ্ট

জাভাস্ক্রিপ্টের কথা বললে, সর্বশেষ কোর ইএস 2015 জাভাস্ক্রিপ্ট বৈশিষ্ট্যগুলির জন্য ব্রাউজার সমর্থন পরিস্থিতি দুর্দান্ত। নতুন স্ট্যান্ডার্ডটিতে প্রতিশ্রুতি, মডিউল, ক্লাস, টেমপ্লেট আক্ষরিক, তীর ফাংশন, let এবং const , ডিফল্ট পরামিতি, জেনারেটর, ধ্বংসাত্মক অ্যাসাইনমেন্ট, বিশ্রাম এবং স্প্রেড, Map / Set , WeakMap / WeakSet এবং আরও অনেকগুলি অন্তর্ভুক্ত রয়েছে। সমস্ত সমর্থিত

ES6 বৈশিষ্ট্যগুলির জন্য ক্যানিউজ সমর্থন টেবিলটি সমস্ত বড় ব্রাউজারগুলিতে সমর্থন দেখায়।
ইসমাস্ক্রিপ্ট 2015 (ইএস 6) ব্রাউজার সমর্থন টেবিল। ( সূত্র )

অ্যাসিঙ্ক ফাংশন, একটি ইএস 2017 বৈশিষ্ট্য এবং আমার ব্যক্তিগত পছন্দের একটি, সমস্ত বড় ব্রাউজারে ব্যবহার করা যেতে পারেasync এবং await কীওয়ার্ডগুলি অ্যাসিনক্রোনাস, প্রতিশ্রুতি-ভিত্তিক আচরণকে একটি ক্লিনার স্টাইলে লিখতে সক্ষম করে, প্রতিশ্রুতি চেইনগুলি স্পষ্টভাবে কনফিগার করার প্রয়োজনীয়তা এড়িয়ে।

সমস্ত বড় ব্রাউজারগুলিতে সমর্থন দেখানো অ্যাসিঙ্ক ফাংশনগুলির জন্য ক্যানিউজ সমর্থন টেবিল।
অ্যাসিঙ্ক ফাংশন ব্রাউজার সমর্থন টেবিল। ( সূত্র )

এমনকি সুপার সাম্প্রতিক ইএস 2020 ভাষা সংযোজন যেমন al চ্ছিক চেইনিং এবং নালিশ কোয়েলেসিংয়ের মতো খুব দ্রুত সমর্থনে পৌঁছেছে। আপনি নীচে একটি কোড নমুনা দেখতে পারেন। যখন এটি মূল জাভাস্ক্রিপ্ট বৈশিষ্ট্যগুলির কথা আসে তখন ঘাসটি আজকের চেয়ে বেশি সবুজ হতে পারে না।

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
আইকনিক উইন্ডোজ এক্সপি সবুজ ঘাসের পটভূমি চিত্র।
কোর জাভাস্ক্রিপ্ট বৈশিষ্ট্যগুলির ক্ষেত্রে ঘাস সবুজ হয়। (মাইক্রোসফ্ট পণ্য স্ক্রিনশট, অনুমতি সহ ব্যবহৃত))

নমুনা অ্যাপ: ফুগু শুভেচ্ছা

এই নিবন্ধটির জন্য, আমি ফুগু গ্রিটিংস ( গিথুব ) নামে একটি সাধারণ পিডব্লিউএ নিয়ে কাজ করি। এই অ্যাপ্লিকেশনটির নামটি ফুগু প্রজেক্টের জন্য হাটের একটি টিপ, যা ওয়েবকে অ্যান্ড্রয়েড/আইওএস/ডেস্কটপ অ্যাপ্লিকেশনগুলির সমস্ত ক্ষমতা দেওয়ার চেষ্টা করে। আপনি প্রকল্পটি এর ল্যান্ডিং পৃষ্ঠায় আরও পড়তে পারেন।

ফুগু গ্রিটিংস একটি অঙ্কন অ্যাপ্লিকেশন যা আপনাকে ভার্চুয়াল গ্রিটিং কার্ড তৈরি করতে দেয় এবং সেগুলি আপনার প্রিয়জনদের কাছে প্রেরণ করতে দেয়। এটি পিডব্লিউএর মূল ধারণার উদাহরণ দেয়। এটি নির্ভরযোগ্য এবং সম্পূর্ণ অফলাইন সক্ষম, সুতরাং আপনার যদি নেটওয়ার্ক না থাকে তবে আপনি এখনও এটি ব্যবহার করতে পারেন। এটি কোনও ডিভাইসের হোম স্ক্রিনে ইনস্টলযোগ্য এবং অপারেটিং সিস্টেমের সাথে স্ট্যান্ড-একা অ্যাপ্লিকেশন হিসাবে নির্বিঘ্নে সংহত করে।

ফুগু পিডব্লিউএকে একটি অঙ্কন দিয়ে শুভেচ্ছা জানায় যা পিডব্লিউএ সম্প্রদায়ের লোগোর সাথে সাদৃশ্যপূর্ণ।
ফুগু গ্রিটিংস নমুনা অ্যাপ্লিকেশন।

প্রগতিশীল বর্ধন

এই পথটি ছাড়িয়ে যাওয়ার সাথে সাথে প্রগতিশীল বর্ধনের বিষয়ে কথা বলার সময় এসেছে। এমডিএন ওয়েব ডকস গ্লসারিটি নিম্নলিখিত হিসাবে ধারণাটি সংজ্ঞায়িত করে :

প্রগ্রেসিভ বর্ধন হ'ল একটি নকশা দর্শন যা যথাসম্ভব অনেক ব্যবহারকারীর জন্য প্রয়োজনীয় সামগ্রী এবং কার্যকারিতার একটি বেসলাইন সরবরাহ করে, কেবলমাত্র প্রয়োজনীয় সমস্ত কোড চালাতে পারে এমন সর্বাধিক আধুনিক ব্রাউজারগুলির ব্যবহারকারীদের কাছে সর্বোত্তম সম্ভাব্য অভিজ্ঞতা সরবরাহ করে।

বৈশিষ্ট্য সনাক্তকরণ সাধারণত ব্রাউজারগুলি আরও আধুনিক কার্যকারিতা পরিচালনা করতে পারে কিনা তা নির্ধারণ করতে ব্যবহৃত হয়, যখন পলিফিলগুলি প্রায়শই জাভাস্ক্রিপ্টের সাথে অনুপস্থিত বৈশিষ্ট্যগুলি যুক্ত করতে ব্যবহৃত হয়।

[…]

প্রগ্রেসিভ বর্ধন একটি দরকারী কৌশল যা ওয়েব বিকাশকারীদের একাধিক অজানা ব্যবহারকারী এজেন্টগুলিতে কাজ করার সময় সেরা সম্ভাব্য ওয়েবসাইটগুলি বিকাশের দিকে মনোনিবেশ করতে দেয়। করুণ অবক্ষয় সম্পর্কিত, তবে এটি একই জিনিস নয় এবং প্রায়শই প্রগতিশীল বর্ধনের ক্ষেত্রে বিপরীত দিকে যেতে দেখা যায়। বাস্তবে, উভয় পন্থা বৈধ এবং প্রায়শই একে অপরের পরিপূরক করতে পারে।

এমডিএন অবদানকারী

স্ক্র্যাচ থেকে প্রতিটি গ্রিটিং কার্ড শুরু করা সত্যিই জটিল হতে পারে। তাহলে কেন এমন একটি বৈশিষ্ট্য নেই যা ব্যবহারকারীদের একটি চিত্র আমদানি করতে এবং সেখান থেকে শুরু করতে দেয়? একটি traditional তিহ্যবাহী পদ্ধতির সাথে, আপনি এটি ঘটানোর জন্য একটি <input type=file> উপাদান ব্যবহার করেছেন। প্রথমত, আপনি উপাদানটি তৈরি করবেন, এর type 'file' এ সেট করুন এবং 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();
  });
};

যখন কোনও আমদানি বৈশিষ্ট্য থাকে, সম্ভবত একটি রফতানি বৈশিষ্ট্য থাকা উচিত যাতে ব্যবহারকারীরা স্থানীয়ভাবে তাদের গ্রিটিং কার্ডগুলি সংরক্ষণ করতে পারেন। ফাইলগুলি সংরক্ষণের traditional তিহ্যবাহী উপায়টি হ'ল download বৈশিষ্ট্য সহ একটি অ্যাঙ্কর লিঙ্ক তৈরি করা এবং এর href হিসাবে একটি ব্লব ইউআরএল দিয়ে। আপনি ডাউনলোডটি ট্রিগার করতে এবং মেমরি ফাঁস রোধ করতে এটি প্রোগ্রামিকভাবে "ক্লিক" করতে চাইবেন, আশা করি ব্লব অবজেক্ট ইউআরএলটি প্রত্যাহার করতে ভুলবেন না।

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

কিন্তু এক মিনিট অপেক্ষা করুন। মানসিকভাবে, আপনি একটি গ্রিটিং কার্ড "ডাউনলোড" করেন নি, আপনি এটি "সংরক্ষণ" করেছেন। আপনাকে এমন একটি "সেভ" ডায়ালগ দেখানোর পরিবর্তে যা আপনাকে ফাইলটি কোথায় রাখবে তা চয়ন করতে দেয়, ব্রাউজারটি সরাসরি ব্যবহারকারীর ইন্টারঅ্যাকশন ছাড়াই গ্রিটিং কার্ডটি ডাউনলোড করেছে এবং এটি সরাসরি আপনার ডাউনলোড ফোল্ডারে রেখেছে। এই মহান না.

যদি একটি ভাল উপায় ছিল? আপনি যদি কেবল কোনও স্থানীয় ফাইল খুলতে পারেন, এটি সম্পাদনা করতে পারেন এবং তারপরে পরিবর্তনগুলি কোনও নতুন ফাইলের কাছে সংরক্ষণ করতে পারেন, বা আপনি প্রাথমিকভাবে যে মূল ফাইলটি খুলেছিলেন সেই মূল ফাইলটিতে ফিরে যেতে পারেন? সেখানে দেখা যাচ্ছে. ফাইল সিস্টেম অ্যাক্সেস এপিআই আপনাকে ফাইল এবং ডিরেক্টরিগুলি খুলতে এবং তৈরি করতে, পাশাপাশি সেগুলি সংশোধন ও সংরক্ষণ করতে দেয়।

তাহলে আমি কীভাবে একটি এপিআই বৈশিষ্ট্যযুক্ত করব? ফাইল সিস্টেম অ্যাক্সেস এপিআই একটি নতুন পদ্ধতি 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'),
    ]);
  }
};

তবে আমি ফাইল সিস্টেম অ্যাক্সেস এপিআই বিশদগুলিতে ডুব দেওয়ার আগে, আমাকে এখানে দ্রুত প্রগতিশীল বর্ধন প্যাটার্নটি এখানে হাইলাইট করতে দিন। ব্রাউজারগুলিতে যা বর্তমানে ফাইল সিস্টেম অ্যাক্সেস এপিআই সমর্থন করে না, আমি উত্তরাধিকার স্ক্রিপ্টগুলি লোড করি। আপনি নীচে ফায়ারফক্স এবং সাফারির নেটওয়ার্ক ট্যাবগুলি দেখতে পারেন।

সাফারি ওয়েব ইন্সপেক্টর উত্তরাধিকার ফাইলগুলি লোড হচ্ছে।
সাফারি ওয়েব ইন্সপেক্টর নেটওয়ার্ক ট্যাব।
ফায়ারফক্স বিকাশকারী সরঞ্জামগুলি লোড হয়ে যাওয়া উত্তরাধিকারী ফাইলগুলি দেখায়।
ফায়ারফক্স বিকাশকারী সরঞ্জাম নেটওয়ার্ক ট্যাব।

যাইহোক, ক্রোমে, একটি ব্রাউজার যা এপিআই সমর্থন করে, কেবল নতুন স্ক্রিপ্টগুলি লোড করা হয়। এটি ডায়নামিক import() এর জন্য মার্জিতভাবে সম্ভাব্য ধন্যবাদ তৈরি করা হয়েছে, যা সমস্ত আধুনিক ব্রাউজার সমর্থন করে । যেমনটি আমি আগেই বলেছি, আজকাল ঘাসটি বেশ সবুজ।

আধুনিক ফাইলগুলি লোড হচ্ছে দেখানো ক্রোম ডিভটুলগুলি।
Chrome devtools নেটওয়ার্ক ট্যাব।

ফাইল সিস্টেম অ্যাক্সেস API

সুতরাং এখন যেহেতু আমি এটি সম্বোধন করেছি, ফাইল সিস্টেম অ্যাক্সেস এপিআইয়ের উপর ভিত্তি করে প্রকৃত বাস্তবায়নটি দেখার সময় এসেছে। একটি চিত্র আমদানির জন্য, accepts window.chooseFileSystemEntries() উভয় ফাইল এক্সটেনশনের পাশাপাশি মাইম প্রকারগুলি সমর্থিত। এটি একটি ফাইল হ্যান্ডেল হিসাবে ফলাফল, যা থেকে আমি 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 প্যারামিটারটি সেট করেছি, তবে এবার কেবল পিএনজি চিত্রগুলিতে সীমাবদ্ধ। আবার আমি একটি ফাইল হ্যান্ডেল ফিরে পেয়েছি, তবে ফাইলটি পাওয়ার পরিবর্তে, এবার আমি createWritable() কল করে একটি লিখিত স্ট্রিম তৈরি করি। এরপরে, আমি ব্লবটি লিখি, যা আমার গ্রিটিং কার্ডের চিত্র, ফাইলটিতে। অবশেষে, আমি লিখিত প্রবাহটি বন্ধ করি।

সবকিছু সর্বদা ব্যর্থ হতে পারে: ডিস্কটি স্থানের বাইরে থাকতে পারে, একটি লেখার বা পড়ার ত্রুটি হতে পারে, বা সম্ভবত ব্যবহারকারী ফাইল ডায়ালগটি বাতিল করে দিতে পারে। এই কারণেই আমি সর্বদা 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);
  }
};

ফাইল সিস্টেম অ্যাক্সেস এপিআইয়ের সাথে প্রগতিশীল বর্ধন ব্যবহার করে আমি আগের মতো একটি ফাইল খুলতে পারি। আমদানি করা ফাইলটি ক্যানভাসে ডানদিকে আঁকা। আমি আমার সম্পাদনাগুলি তৈরি করতে পারি এবং শেষ পর্যন্ত তাদেরকে একটি রিয়েল সেভ ডায়ালগ বাক্স দিয়ে সংরক্ষণ করতে পারি যেখানে আমি ফাইলের নাম এবং সঞ্চয় স্থানটি চয়ন করতে পারি। এখন ফাইলটি অনন্তকাল সংরক্ষণের জন্য প্রস্তুত।

একটি ফাইল ওপেন ডায়ালগ সহ ফুগু গ্রিটিংস অ্যাপ।
ফাইল ওপেন ডায়ালগ।
ফুগু গ্রিটিংস অ্যাপটি এখন একটি আমদানি করা চিত্র সহ।
আমদানি করা চিত্র।
পরিবর্তিত চিত্র সহ ফুগু গ্রিটিংস অ্যাপ।
একটি নতুন ফাইলে পরিবর্তিত চিত্র সংরক্ষণ করা।

ওয়েব শেয়ার এবং ওয়েব শেয়ার লক্ষ্য এপিআই

অনন্তকাল সংরক্ষণ করা ছাড়াও, আমি আসলে আমার গ্রিটিং কার্ডটি ভাগ করতে চাই। এটি এমন কিছু যা ওয়েব শেয়ার এপিআই এবং ওয়েব শেয়ার টার্গেট এপিআই আমাকে করার অনুমতি দেয়। মোবাইল, এবং আরও সম্প্রতি ডেস্কটপ অপারেটিং সিস্টেমগুলি অন্তর্নির্মিত ভাগ করে নেওয়ার প্রক্রিয়া অর্জন করেছে। উদাহরণস্বরূপ, নীচে আমার ব্লগের একটি নিবন্ধ থেকে ট্রিগার করা ম্যাকোসে ডেস্কটপ সাফারির শেয়ার শীট রয়েছে। আপনি যখন শেয়ার নিবন্ধ বোতামটি ক্লিক করেন, আপনি কোনও বন্ধুর সাথে নিবন্ধের একটি লিঙ্ক ভাগ করতে পারেন, উদাহরণস্বরূপ, ম্যাকোস বার্তা অ্যাপের মাধ্যমে।

ম্যাকোসে ডেস্কটপ সাফারির শেয়ার শিটটি একটি নিবন্ধের শেয়ার বোতাম থেকে ট্রিগার করেছে
ম্যাকোসে ডেস্কটপ সাফারিটিতে ওয়েব শেয়ার এপিআই শেয়ার করুন।

এটি ঘটানোর কোডটি বেশ সোজা। আমি navigator.share() কল করি এবং এটিকে কোনও অবজেক্টে একটি al চ্ছিক title , text এবং url পাস করি। তবে আমি যদি কোনও চিত্র সংযুক্ত করতে চাই? ওয়েব শেয়ার এপিআইয়ের স্তর 1 এখনও এটি সমর্থন করে না। সুসংবাদটি হ'ল ওয়েব শেয়ার স্তর 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);
}

ফুগু গ্রিটিং কার্ড অ্যাপ্লিকেশন দিয়ে কীভাবে এই কাজটি করবেন তা আমাকে দেখাতে দিন। প্রথমত, আমাকে একটি ব্লব সমন্বিত একটি files অ্যারে এবং তারপরে একটি title এবং একটি text সহ একটি data অবজেক্ট প্রস্তুত করতে হবে। এরপরে, একটি সেরা অনুশীলন হিসাবে, আমি নতুন 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);
  }
};

আগের মতো, আমি প্রগতিশীল বর্ধন ব্যবহার করি। যদি navigator অবজেক্টে 'share' এবং 'canShare' উভয়ই উপস্থিত থাকে তবে আমি কেবল এগিয়ে যাই এবং গতিশীল import() এর মাধ্যমে share.mjs লোড করি। মোবাইল সাফারির মতো ব্রাউজারগুলিতে যা কেবল দুটি শর্তের মধ্যে একটি পূরণ করে, আমি কার্যকারিতাটি লোড করি না।

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

ফুগু গ্রিটিংসে, আমি যদি অ্যান্ড্রয়েডের ক্রোমের মতো কোনও সমর্থনকারী ব্রাউজারে শেয়ার বোতামটি ট্যাপ করি তবে অন্তর্নির্মিত শেয়ার শীটটি খোলে। আমি উদাহরণস্বরূপ, জিমেইল চয়ন করতে পারি এবং ইমেল সুরকার উইজেটটি চিত্রটি সংযুক্ত করে পপ আপ করতে পারে।

ওএস-লেভেল শেয়ার শিটটি চিত্রটি ভাগ করে নেওয়ার জন্য বিভিন্ন অ্যাপ্লিকেশন দেখায়।
ফাইলটি ভাগ করে নেওয়ার জন্য একটি অ্যাপ্লিকেশন নির্বাচন করা।
জিমেইলের ইমেলটি চিত্রটি সংযুক্ত করে উইজেট রচনা করে।
ফাইলটি জিমেইলের সুরকারে একটি নতুন ইমেলের সাথে সংযুক্ত হয়ে যায়।

যোগাযোগ পিকার এপিআই

এরপরে, আমি পরিচিতিগুলি সম্পর্কে কথা বলতে চাই, যার অর্থ কোনও ডিভাইসের ঠিকানা বই বা পরিচিতি পরিচালক অ্যাপ্লিকেশন। আপনি যখন কোনও গ্রিটিং কার্ড লিখেন, কারও নাম সঠিকভাবে লিখতে সর্বদা সহজ নাও হতে পারে। উদাহরণস্বরূপ, আমার এক বন্ধু সের্গেই রয়েছে যিনি তার নামটি সিরিলিক চিঠিতে বানান করতে পছন্দ করেন। আমি একটি জার্মান কিউয়ার্টজ কীবোর্ড ব্যবহার করছি এবং কীভাবে তাদের নাম টাইপ করবেন তা কোনও ধারণা নেই। এটি এমন একটি সমস্যা যা যোগাযোগের পিকার এপিআই সমাধান করতে পারে। যেহেতু আমার বন্ধুটি আমার ফোনের পরিচিতি অ্যাপে সঞ্চিত আছে, পরিচিতি পিকার এপিআইয়ের মাধ্যমে, আমি ওয়েব থেকে আমার পরিচিতিগুলিতে ট্যাপ করতে পারি।

প্রথমত, আমি অ্যাক্সেস করতে চাই এমন সম্পত্তিগুলির তালিকা নির্দিষ্ট করতে হবে। এই ক্ষেত্রে, আমি কেবল নামগুলি চাই, তবে অন্যান্য ব্যবহারের ক্ষেত্রে আমি টেলিফোন নম্বর, ইমেল, অবতার আইকন বা শারীরিক ঠিকানায় আগ্রহী হতে পারি। এরপরে, আমি একটি 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);
  }
};

এবং এতক্ষণে আপনি সম্ভবত প্যাটার্নটি শিখেছেন: আমি কেবল ফাইলটি লোড করি যখন এপিআই আসলে সমর্থিত হয়।

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

ফুগু গ্রিটিংয়ে, যখন আমি পরিচিতি বোতামটি ট্যাপ করি এবং আমার দুটি সেরা পাল নির্বাচন করি, сергей майловч б б брин এবং劳伦斯 劳伦斯 爱德华 · "拉里" 拉里 "拉里" 拉里 · 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇 佩奇সংখ্যা তাদের নামগুলি তখন আমার গ্রিটিং কার্ডে আঁকা হয়।

ঠিকানা বইতে দুটি পরিচিতির নাম দেখানো পরিচিতি পিকার।
ঠিকানা বই থেকে যোগাযোগের পিকার সহ দুটি নাম নির্বাচন করা।
গ্রিটিং কার্ডে আঁকা দুটি আগে বাছাই করা পরিচিতির নাম।
তারপরে দুটি নাম গ্রিটিং কার্ডের দিকে আঁকুন।

অ্যাসিঙ্ক্রোনাস ক্লিপবোর্ড API

পরের দিকে অনুলিপি করা এবং আটকানো হচ্ছে। সফ্টওয়্যার বিকাশকারী হিসাবে আমাদের প্রিয় অপারেশনগুলির মধ্যে একটি হ'ল অনুলিপি এবং পেস্ট। একজন গ্রিটিং কার্ড লেখক হিসাবে, মাঝে মাঝে আমিও এটি করতে চাই। আমি হয় আমি কাজ করছি এমন একটি গ্রিটিং কার্ডে কোনও চিত্র পেস্ট করতে চাই, বা আমার গ্রিটিং কার্ডটি অনুলিপি করতে চাই যাতে আমি এটি অন্য কোথাও থেকে সম্পাদনা চালিয়ে যেতে পারি। অ্যাসিঙ্ক ক্লিপবোর্ড এপিআই , পাঠ্য এবং চিত্র উভয়কেই সমর্থন করে। আমি কীভাবে ফুগু গ্রিটিংস অ্যাপ্লিকেশনটিতে অনুলিপি এবং পেস্ট সমর্থন যুক্ত করেছি তা দিয়ে আমাকে আপনাকে চলতে দিন।

সিস্টেমের ক্লিপবোর্ডে কিছু অনুলিপি করার জন্য, আমার এটিতে লিখতে হবে। navigator.clipboard.write() পদ্ধতিটি প্যারামিটার হিসাবে ক্লিপবোর্ড আইটেমগুলির একটি অ্যারে নেয়। প্রতিটি ক্লিপবোর্ড আইটেমটি মূলত একটি মান হিসাবে একটি ব্লব সহ একটি বস্তু এবং কী হিসাবে ব্লবের ধরণ।

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 ক্ষেত্র রয়েছে যা আমাকে উপলব্ধ সংস্থানগুলির মাইম প্রকারগুলি বলে। আমি ক্লিপবোর্ড আইটেমটির getType() পদ্ধতিটি কল করি, আমি আগে প্রাপ্ত মাইম টাইপটি পাস করি।

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

তাহলে কিভাবে এই অনুশীলনে কাজ করে? আমার কাছে ম্যাকোস পূর্বরূপ অ্যাপ্লিকেশনটিতে একটি চিত্র খোলা আছে এবং এটি ক্লিপবোর্ডে অনুলিপি করে। আমি যখন পেস্ট ক্লিক করি, তখন ফুগু গ্রিটিংস অ্যাপটি আমাকে জিজ্ঞাসা করে যে আমি অ্যাপ্লিকেশনটিকে ক্লিপবোর্ডে পাঠ্য এবং চিত্রগুলি দেখতে অনুমতি দিতে চাই কিনা।

ফুগু গ্রিটিংস অ্যাপ্লিকেশন ক্লিপবোর্ডের অনুমতি প্রম্পট দেখায়।
ক্লিপবোর্ড অনুমতি প্রম্পট।

অবশেষে, অনুমতি গ্রহণের পরে, চিত্রটি পরে অ্যাপ্লিকেশনটিতে আটকানো হয়। অন্যভাবে রাউন্ডও কাজ করে। আমাকে ক্লিপবোর্ডে একটি গ্রিটিং কার্ড অনুলিপি করতে দিন। আমি তখন পূর্বরূপ খুলি এবং ফাইল এবং তারপরে ক্লিপবোর্ড থেকে নতুন ক্লিক করে, গ্রিটিং কার্ডটি একটি নতুন শিরোনামহীন চিত্রের মধ্যে আটকানো হয়।

একটি শিরোনামহীন, সবেমাত্র পেস্ট করা চিত্র সহ ম্যাকোস পূর্বরূপ অ্যাপ্লিকেশন।
একটি চিত্র ম্যাকোস পূর্বরূপ অ্যাপ্লিকেশনটিতে আটকানো হয়েছে।

ব্যাজিং এপিআই

আর একটি দরকারী এপিআই হ'ল ব্যাজিং এপিআই । একটি ইনস্টলযোগ্য পিডব্লিউএ হিসাবে, ফুগু গ্রিটিংসের অবশ্যই একটি অ্যাপ আইকন রয়েছে যা ব্যবহারকারীরা অ্যাপ ডক বা হোম স্ক্রিনে রাখতে পারেন। এপিআই প্রদর্শনের একটি মজাদার এবং সহজ উপায় হ'ল এটি কলম স্ট্রোক কাউন্টার হিসাবে ফুগু গ্রিটিংসে এটি ব্যবহার করা। আমি একটি ইভেন্ট শ্রোতা যুক্ত করেছি যা যখনই 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 নম্বর দেখায়।
পেন স্ট্রোকগুলি অ্যাপ্লিকেশন আইকন ব্যাজ আকারে কাউন্টার।

পর্যায়ক্রমিক পটভূমি সিঙ্ক এপিআই

নতুন কিছু দিয়ে সতেজ প্রতিটি দিন শুরু করতে চান? ফুগু গ্রিটিংস অ্যাপ্লিকেশনটির একটি ঝরঝরে বৈশিষ্ট্য হ'ল এটি আপনার গ্রিটিং কার্ডটি শুরু করতে একটি নতুন ব্যাকগ্রাউন্ড চিত্র সহ প্রতিদিন সকালে আপনাকে অনুপ্রাণিত করতে পারে। এটি অর্জন করতে অ্যাপ্লিকেশনটি পর্যায়ক্রমিক পটভূমি সিঙ্ক এপিআই ব্যবহার করে।

প্রথম পদক্ষেপটি হ'ল পরিষেবা কর্মী নিবন্ধনে পর্যায়ক্রমিক সিঙ্ক ইভেন্টটি নিবন্ধিত করা । এটি '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);
  }
};

দ্বিতীয় পদক্ষেপটি হ'ল পরিষেবা কর্মীর মধ্যে 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,
          });
        });
      })()
    );
  }
});

আবার এটি সত্যই একটি প্রগতিশীল বর্ধন, সুতরাং কোডটি কেবল তখনই লোড হয় যখন এপিআই ব্রাউজার দ্বারা সমর্থিত হয়। এটি ক্লায়েন্ট কোড এবং পরিষেবা কর্মী কোড উভয়ের ক্ষেত্রেই প্রযোজ্য। সমর্থনকারী ব্রাউজারগুলিতে, তাদের কোনওটিই লোড হয় না। কীভাবে পরিষেবা কর্মীর মধ্যে গতিশীল import() এর পরিবর্তে (এটি কোনও পরিষেবা কর্মী প্রসঙ্গে সমর্থিত নয়) এর পরিবর্তে আমি ক্লাসিক importScripts() ব্যবহার করি।

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

ফুগু গ্রিটিংসে, ওয়ালপেপার বোতাম টিপে পর্যায়ক্রমিক পটভূমি সিঙ্ক এপিআইয়ের মাধ্যমে প্রতিদিন আপডেট হওয়া দিনের গ্রিটিং কার্ড চিত্রটি প্রকাশ করে।

দিনের একটি নতুন গ্রিটিং কার্ড চিত্র সহ ফুগু গ্রিটিংস অ্যাপ।
ওয়ালপেপার বোতাম টিপে দিনের চিত্রটি প্রদর্শন করে।

বিজ্ঞপ্তি ট্রিগার এপিআই

Sometimes even with a lot of inspiration, you need a nudge to finish a started greeting card. This is a feature that is enabled by the Notification Triggers API . As a user, I can enter a time when I want to be nudged to finish my greeting card. When that time comes, I will get a notification that my greeting card is waiting.

After prompting for the target time, the application schedules the notification with a showTrigger . This can be a TimestampTrigger with the previously selected target date. The reminder notification will be triggered locally, no network or server side is needed.

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

As with everything else I have shown so far, this is a progressive enhancement, so the code is only conditionally loaded.

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

When I check the Reminder checkbox in Fugu Greetings, a prompt asks me when I want to be reminded to finish my greeting card.

Fugu Greetings app with a prompt asking the user when they want to be reminded to finish their greeting card.
Scheduling a local notification to be reminded to finish a greeting card.

When a scheduled notification triggers in Fugu Greetings, it is shown just like any other notification, but as I wrote before, it didn't require a network connection.

macOS Notification Center showing a triggered notification from Fugu Greetings.
The triggered notification appears in the macOS Notification Center.

The Wake Lock API

I also want to include the Wake Lock API . Sometimes you just need to stare long enough at the screen until inspiration kisses you. The worst that can happen then is for the screen to turn off. The Wake Lock API can prevent this from happening.

The first step is to obtain a wake lock with the navigator.wakelock.request method() . I pass it the string 'screen' to obtain a screen wake lock. I then add an event listener to be informed when the wake lock is released. This can happen, for example, when the tab visibility changes. If this happens, I can, when the tab becomes visible again, re-obtain the 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);

Yes, this is a progressive enhancement, so I only need to load it when the browser supports the API.

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

In Fugu Greetings, there's an Insomnia checkbox that, when checked, keeps the screen awake.

The insomnia checkbox, if checked, keeps the screen awake.
The Insomnia checkbox keeps app awake.

The Idle Detection API

At times, even if you stare at the screen for hours, it's just useless and you can't come up with the slightest idea what to do with your greeting card. The Idle Detection API allows the app to detect user idle time. If the user is idle for too long, the app resets to the initial state and clears the canvas. This API is currently gated behind the notifications permission , since a lot of production use cases of idle detection are notifications-related, for example, to only send a notification to a device the user is currently actively using.

After making sure that the notifications permission is granted, I then instantiate the idle detector. I register an event listener that listens for idle changes, which includes the user and the screen state. The user can be active or idle, and the screen can be unlocked or locked. If the user is idle, the canvas clears. I give the idle detector a threshold of 60 seconds.

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

And as always, I only load this code when the browser supports it.

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

In the Fugu Greetings app, the canvas clears when the Ephemeral checkbox is checked and the user is idle for for too long.

Fugu Greetings app with a cleared canvas after the user has been idle for too long.
When the Ephemeral checkbox is checked and the user has been idle for too long, the canvas is cleared.

বন্ধ হচ্ছে

Phew, what a ride. So many APIs in just one sample app. And, remember, I never make the user pay the download cost for a feature that their browser doesn't support. By using progressive enhancement, I make sure only the relevant code gets loaded. And since with HTTP/2, requests are cheap, this pattern should work well for a lot of applications, although you might want to consider a bundler for really large apps.

Chrome DevTools Network panel showing only requests for files with code that the current browser supports.
Chrome DevTools Network tab showing only requests for files with code that the current browser supports.

The app may look a little different on each browser since not all platforms support all features, but the core functionality is always there—progressively enhanced according to the particular browser's capabilities. Note that these capabilities may change even in one and the same browser, depending on whether the app is running as an installed app or in a browser tab.

Fugu Greetings running on Android Chrome, showing many available features.
Fugu Greetings running on Android Chrome.
Fugu Greetings running on desktop Safari, showing fewer available features.
Fugu Greetings running on desktop Safari.
Fugu Greetings running on desktop Chrome, showing many available features.
Fugu Greetings running on desktop Chrome.

If you're interested in the Fugu Greetings app, go find and fork it on GitHub .

Fugu Greetings repo on GitHub.
Fugu Greetings app on GitHub.

The Chromium team is working hard on making the grass greener when it comes to advanced Fugu APIs. By applying progressive enhancement in the development of my app, I make sure that everybody gets a good, solid baseline experience, but that people using browsers that support more Web platform APIs get an even better experience. I'm looking forward to seeing what you do with progressive enhancement in your apps.

স্বীকৃতি

I'm grateful to Christian Liebel and Hemanth HM who both have contributed to Fugu Greetings. This article was reviewed by Joe Medley and Kayce Basques . Jake Archibald helped me find out the situation with dynamic import() in a service worker context.

,

Building for modern browsers and progressively enhancing like it's 2003

Back in March 2003, Nick Finck and Steve Champeon stunned the web design world with the concept of progressive enhancement , a strategy for web design that emphasizes loading core web page content first, and that then progressively adds more nuanced and technically rigorous layers of presentation and features on top of the content. While in 2003, progressive enhancement was about using—at the time—modern CSS features, unobtrusive JavaScript, and even just Scalable Vector Graphics. Progressive enhancement in 2020 and beyond is about using modern browser capabilities .

Inclusive web design for the future with progressive enhancement. Title slide from Finck and Champeon's original presentation.
Slide: Inclusive Web Design for the Future With Progressive Enhancement. ( সূত্র )

Modern JavaScript

Speaking of JavaScript, the browser support situation for the latest core ES 2015 JavaScript features is great. The new standard includes promises, modules, classes, template literals, arrow functions, let and const , default parameters, generators, the destructuring assignment, rest and spread, Map / Set , WeakMap / WeakSet , and many more. All are supported .

The CanIUse support table for ES6 features showing support across all major browsers.
The ECMAScript 2015 (ES6) browser support table. ( সূত্র )

Async functions, an ES 2017 feature and one of my personal favorites, can be used in all major browsers. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains.

The CanIUse support table for async functions showing support across all major browsers.
The Async functions browser support table. ( সূত্র )

And even super recent ES 2020 language additions like optional chaining and nullish coalescing have reached support really quickly. You can see a code sample below. When it comes to core JavaScript features, the grass couldn't be much greener than it is today.

const adventurer = {
  name: 'Alice',
  cat: {
    name: 'Dinah',
  },
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0
The iconic Windows XP green grass background image.
The grass is green when it comes to core JavaScript features. (Microsoft product screenshot, used with permission .)

The sample app: Fugu Greetings

For this article, I work with a simple PWA, called Fugu Greetings ( GitHub ). The name of this app is a tip of the hat to Project Fugu 🐡, an effort to give the web all the powers of Android/iOS/desktop applications. You can read more about the project on its landing page .

Fugu Greetings is a drawing app that lets you create virtual greeting cards, and send them to your loved ones. It exemplifies PWA's core concepts . It's reliable and fully offline enabled, so even if you don't have a network, you can still use it. It's also Installable to a device's home screen and integrates seamlessly with the operating system as a stand-alone application.

Fugu Greetings PWA with a drawing that resembles the PWA community logo.
The Fugu Greetings sample app.

প্রগতিশীল বর্ধন

With this out of the way, it's time to talk about progressive enhancement . The MDN Web Docs Glossary defines the concept as follows:

Progressive enhancement is a design philosophy that provides a baseline of essential content and functionality to as many users as possible, while delivering the best possible experience only to users of the most modern browsers that can run all the required code.

Feature detection is generally used to determine whether browsers can handle more modern functionality, while polyfills are often used to add missing features with JavaScript.

[…]

Progressive enhancement is a useful technique that allows web developers to focus on developing the best possible websites while making those websites work on multiple unknown user agents. Graceful degradation is related, but is not the same thing and is often seen as going in the opposite direction to progressive enhancement. In reality, both approaches are valid and can often complement one another.

MDN contributors

Starting each greeting card from scratch can be really cumbersome. So why not have a feature that allows users to import an image, and start from there? With a traditional approach, you'd have used an <input type=file> element to make this happen. First, you'd create the element, set its type to 'file' and add MIME types to the accept property, and then programmatically "click" it and listen for changes. When you select an image, it is imported straight onto the canvas.

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

When there's an import feature, there probably should be an export feature so users can save their greeting cards locally. The traditional way of saving files is to create an anchor link with a download attribute and with a blob URL as its href . You'd also programmatically "click" it to trigger the download, and, to prevent memory leaks, hopefully not forget to revoke the blob object URL.

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

কিন্তু এক মিনিট অপেক্ষা করুন। Mentally, you haven't "downloaded" a greeting card, you have "saved" it. Rather than showing you a "save" dialog that lets you choose where to put the file, the browser has directly downloaded the greeting card without user interaction and has put it straight into your Downloads folder. এই মহান না.

যদি একটি ভাল উপায় ছিল? What if you could just open a local file, edit it, and then save the modifications, either to a new file, or back to the original file that you had initially opened? সেখানে দেখা যাচ্ছে. The File System Access API allows you to open and create files and directories, as well as modify and save them .

So how do I feature-detect an API? The File System Access API exposes a new method window.chooseFileSystemEntries() . Consequently, I need to conditionally load different import and export modules depending on whether this method is available. I've shown how to do this below.

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

But before I dive into the File System Access API details, let me just quickly highlight the progressive enhancement pattern here. On browsers that currently don't support the File System Access API, I load the legacy scripts. You can see the network tabs of Firefox and Safari below.

Safari Web Inspector showing the legacy files getting loaded.
Safari Web Inspector network tab.
Firefox Developer Tools showing the legacy files getting loaded.
Firefox Developer Tools network tab.

However, on Chrome, a browser that supports the API, only the new scripts are loaded. This is made elegantly possible thanks to dynamic import() , which all modern browsers support . As I said earlier, the grass is pretty green these days.

Chrome DevTools showing the modern files getting loaded.
Chrome DevTools network tab.

ফাইল সিস্টেম অ্যাক্সেস API

So now that I have addressed this, it's time to look at the actual implementation based on the File System Access API. For importing an image, I call window.chooseFileSystemEntries() and pass it an accepts property where I say I want image files. Both file extensions as well as MIME types are supported. This results in a file handle, from which I can get the actual file by calling 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);
  }
};

Exporting an image is almost the same, but this time I need to pass a type parameter of 'save-file' to the chooseFileSystemEntries() method. From this I get a file save dialog. With file open, this wasn't necessary since 'open-file' is the default. I set the accepts parameter similarly to before, but this time limited to just PNG images. Again I get back a file handle, but rather than getting the file, this time I create a writable stream by calling createWritable() . Next, I write the blob, which is my greeting card image, to the file. Finally, I close the writable stream.

Everything can always fail: The disk could be out of space, there could be a write or read error, or maybe simply the user cancels the file dialog. This is why I always wrap the calls in a try...catch statement.

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

Using progressive enhancement with the File System Access API, I can open a file as before. The imported file is drawn right onto the canvas. I can make my edits and finally save them with a real save dialog box where I can choose the name and storage location of the file. Now the file is ready to be preserved for eternity.

Fugu Greetings app with a file open dialog.
The file open dialog.
Fugu Greetings app now with an imported image.
The imported image.
Fugu Greetings app with the modified image.
Saving the modified image to a new file.

The Web Share and Web Share Target APIs

Apart from storing for eternity, maybe I actually want to share my greeting card. This is something that the Web Share API and Web Share Target API allow me to do. Mobile, and more recently desktop operating systems have gained built-in sharing mechanisms. For example, below is desktop Safari's share sheet on macOS triggered from an article on my blog . When you click the Share Article button, you can share a link to the article with a friend, for example, via the macOS Messages app.

Desktop Safari's share sheet on macOS triggered from an article's Share button
Web Share API on desktop Safari on macOS.

The code to make this happen is pretty straightforward. I call navigator.share() and pass it an optional title , text , and url in an object. But what if I want to attach an image? Level 1 of the Web Share API doesn't support this yet. The good news is that Web Share Level 2 has added file sharing capabilities.

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

Let me show you how to make this work with the Fugu Greeting card application. First, I need to prepare a data object with a files array consisting of one blob, and then a title and a text . Next, as a best practice, I use the new navigator.canShare() method which does what its name suggests: It tells me if the data object I'm trying to share can technically be shared by the browser. If navigator.canShare() tells me the data can be shared, I'm ready to call navigator.share() as before. Because everything can fail, I'm again using a try...catch block.

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

As before, I use progressive enhancement. If both 'share' and 'canShare' exist on the navigator object, only then I go forward and load share.mjs via dynamic import() . On browsers like mobile Safari that only fulfill one of the two conditions, I don't load the functionality.

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

In Fugu Greetings, if I tap the Share button on a supporting browser like Chrome on Android, the built-in share sheet opens. I can, for example, choose Gmail, and the email composer widget pops up with the image attached.

OS-level share sheet showing various apps to share the image to.
Choosing an app to share the file to.
Gmail's email compose widget with the image attached.
The file gets attached to a new email in Gmail's composer.

The Contact Picker API

Next, I want to talk about contacts, meaning a device's address book or contacts manager app. When you write a greeting card, it may not always be easy to correctly write someone's name. For example, I have a friend Sergey who prefers his name to be spelled in Cyrillic letters. I'm using a German QWERTZ keyboard and have no idea how to type their name. This is a problem that the Contact Picker API can solve. Since I have my friend stored in my phone's contacts app, via the Contacts Picker API, I can tap into my contacts from the web.

First, I need to specify the list of properties I want to access. In this case, I only want the names, but for other use cases I might be interested in telephone numbers, emails, avatar icons, or physical addresses. Next, I configure an options object and set multiple to true , so that I can select more than one entry. Finally, I can call navigator.contacts.select() , which returns the desired properties for the user-selected contacts.

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

And by now you've probably learned the pattern: I only load the file when the API is actually supported.

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

In Fugu Greeting, when I tap the Contacts button and select my two best pals, Сергей Михайлович Брин and劳伦斯·爱德华·"拉里"·佩奇, you can see how the contacts picker is limited to only show their names, but not their email addresses, or other information like their phone সংখ্যা Their names are then drawn onto my greeting card.

Contacts picker showing the names of two contacts in the address book.
Selecting two names with the contact picker from the address book.
The names of the two previously picked contacts drawn on the greeting card.
The two names then get drawn onto the greeting card.

অ্যাসিঙ্ক্রোনাস ক্লিপবোর্ড API

Up next is copying and pasting. One of our favorite operations as software developers is copy and paste. As a greeting card author, at times, I may want to do the same. I may want to either paste an image into a greeting card I'm working on, or copy my greeting card so I can continue editing it from somewhere else. The Async Clipboard API , supports both text and images. Let me walk you through how I added copy and paste support to the Fugu Greetings app.

In order to copy something onto the system's clipboard, I need to write to it. The navigator.clipboard.write() method takes an array of clipboard items as a parameter. Each clipboard item is essentially an object with a blob as a value, and the blob's type as the key.

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

To paste, I need to loop over the clipboard items that I obtain by calling navigator.clipboard.read() . The reason for this is that multiple clipboard items might be on the clipboard in different representations. Each clipboard item has a types field that tells me the MIME types of the available resources. I call the clipboard item's getType() method, passing the MIME type I obtained before.

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

And it's almost needless to say by now. I only do this on supporting browsers.

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

তাহলে কিভাবে এই অনুশীলনে কাজ করে? I have an image open in the macOS Preview app and copy it to the clipboard. When I click Paste , the Fugu Greetings app then asks me whether I want to allow the app to see text and images on the clipboard.

Fugu Greetings app showing the clipboard permission prompt.
The clipboard permission prompt.

Finally, after accepting the permission, the image is then pasted into the application. The other way round works, too. Let me copy a greeting card to the clipboard. When I then open Preview and click File and then New from Clipboard , the greeting card gets pasted into a new untitled image.

The macOS Preview app with an untitled, just pasted image.
An image pasted into the macOS Preview app.

The Badging API

Another useful API is the Badging API . As an installable PWA, Fugu Greetings of course does have an app icon that users can place on the app dock or the home screen. A fun and easy way to demonstrate the API is to (ab)use it in Fugu Greetings as a pen strokes counter. I have added an event listener that increments the pen strokes counter whenever the pointerdown event occurs and then sets the updated icon badge. Whenever the canvas gets cleared, the counter resets, and the badge is removed.

let strokes = 0;

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

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

This feature is a progressive enhancement, so the loading logic is as usual.

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

In this example, I have drawn the numbers from one to seven, using one pen stroke per number. The badge counter on the icon is now at seven.

The numbers from one to seven drawn onto the greeting card, each with just one pen stroke.
Drawing the numbers from 1 to 7, using seven pen strokes.
Badge icon on the Fugu Greetings app showing the number 7.
The pen strokes counter in the form of the app icon badge.

The Periodic Background Sync API

Want to start each day fresh with something new? A neat feature of the Fugu Greetings app is that it can inspire you each morning with a new background image to start your greeting card. The app uses the Periodic Background Sync API to achieve this.

The first step is to register a periodic sync event in the service worker registration. It listens for a sync tag called 'image-of-the-day' and has a minimum interval of one day, so the user can get a new background image every 24 hours.

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

The second step is to listen for the periodicsync event in the service worker. If the event tag is 'image-of-the-day' , that is, the one that was registered before, the image of the day is retrieved via the getImageOfTheDay() function, and the result propagated to all clients, so they can update their canvases and caches.

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

Again this is truly a progressive enhancement, so the code is only loaded when the API is supported by the browser. This applies to both the client code and the service worker code. On non-supporting browsers, neither of them is loaded. Note how in the service worker, instead of a dynamic import() (that isn't supported in a service worker context yet ), I use the classic importScripts() .

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

In Fugu Greetings, pressing the Wallpaper button reveals the greeting card image of the day that is updated every day via the Periodic Background Sync API.

Fugu Greetings app with a new greeting card image of the day.
Pressing the Wallpaper button displays the image of the day.

Notification Triggers API

Sometimes even with a lot of inspiration, you need a nudge to finish a started greeting card. This is a feature that is enabled by the Notification Triggers API . As a user, I can enter a time when I want to be nudged to finish my greeting card. When that time comes, I will get a notification that my greeting card is waiting.

After prompting for the target time, the application schedules the notification with a showTrigger . This can be a TimestampTrigger with the previously selected target date. The reminder notification will be triggered locally, no network or server side is needed.

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

As with everything else I have shown so far, this is a progressive enhancement, so the code is only conditionally loaded.

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

When I check the Reminder checkbox in Fugu Greetings, a prompt asks me when I want to be reminded to finish my greeting card.

Fugu Greetings app with a prompt asking the user when they want to be reminded to finish their greeting card.
Scheduling a local notification to be reminded to finish a greeting card.

When a scheduled notification triggers in Fugu Greetings, it is shown just like any other notification, but as I wrote before, it didn't require a network connection.

macOS Notification Center showing a triggered notification from Fugu Greetings.
The triggered notification appears in the macOS Notification Center.

The Wake Lock API

I also want to include the Wake Lock API . Sometimes you just need to stare long enough at the screen until inspiration kisses you. The worst that can happen then is for the screen to turn off. The Wake Lock API can prevent this from happening.

The first step is to obtain a wake lock with the navigator.wakelock.request method() . I pass it the string 'screen' to obtain a screen wake lock. I then add an event listener to be informed when the wake lock is released. This can happen, for example, when the tab visibility changes. If this happens, I can, when the tab becomes visible again, re-obtain the 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);

Yes, this is a progressive enhancement, so I only need to load it when the browser supports the API.

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

In Fugu Greetings, there's an Insomnia checkbox that, when checked, keeps the screen awake.

The insomnia checkbox, if checked, keeps the screen awake.
The Insomnia checkbox keeps app awake.

The Idle Detection API

At times, even if you stare at the screen for hours, it's just useless and you can't come up with the slightest idea what to do with your greeting card. The Idle Detection API allows the app to detect user idle time. If the user is idle for too long, the app resets to the initial state and clears the canvas. This API is currently gated behind the notifications permission , since a lot of production use cases of idle detection are notifications-related, for example, to only send a notification to a device the user is currently actively using.

After making sure that the notifications permission is granted, I then instantiate the idle detector. I register an event listener that listens for idle changes, which includes the user and the screen state. The user can be active or idle, and the screen can be unlocked or locked. If the user is idle, the canvas clears. I give the idle detector a threshold of 60 seconds.

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

And as always, I only load this code when the browser supports it.

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

In the Fugu Greetings app, the canvas clears when the Ephemeral checkbox is checked and the user is idle for for too long.

Fugu Greetings app with a cleared canvas after the user has been idle for too long.
When the Ephemeral checkbox is checked and the user has been idle for too long, the canvas is cleared.

বন্ধ হচ্ছে

Phew, what a ride. So many APIs in just one sample app. And, remember, I never make the user pay the download cost for a feature that their browser doesn't support. By using progressive enhancement, I make sure only the relevant code gets loaded. And since with HTTP/2, requests are cheap, this pattern should work well for a lot of applications, although you might want to consider a bundler for really large apps.

Chrome DevTools Network panel showing only requests for files with code that the current browser supports.
Chrome DevTools Network tab showing only requests for files with code that the current browser supports.

The app may look a little different on each browser since not all platforms support all features, but the core functionality is always there—progressively enhanced according to the particular browser's capabilities. Note that these capabilities may change even in one and the same browser, depending on whether the app is running as an installed app or in a browser tab.

Fugu Greetings running on Android Chrome, showing many available features.
Fugu Greetings running on Android Chrome.
Fugu Greetings running on desktop Safari, showing fewer available features.
Fugu Greetings running on desktop Safari.
Fugu Greetings running on desktop Chrome, showing many available features.
Fugu Greetings running on desktop Chrome.

If you're interested in the Fugu Greetings app, go find and fork it on GitHub .

Fugu Greetings repo on GitHub.
Fugu Greetings app on GitHub.

The Chromium team is working hard on making the grass greener when it comes to advanced Fugu APIs. By applying progressive enhancement in the development of my app, I make sure that everybody gets a good, solid baseline experience, but that people using browsers that support more Web platform APIs get an even better experience. I'm looking forward to seeing what you do with progressive enhancement in your apps.

স্বীকৃতি

I'm grateful to Christian Liebel and Hemanth HM who both have contributed to Fugu Greetings. This article was reviewed by Joe Medley and Kayce Basques . Jake Archibald helped me find out the situation with dynamic import() in a service worker context.