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

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

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

এমনকি অতি সাম্প্রতিক ES 2020 ভাষা সংযোজন যেমন ঐচ্ছিক চেইনিং এবং nullish coalescing খুব দ্রুত সমর্থনে পৌঁছেছে। মূল জাভাস্ক্রিপ্ট বৈশিষ্ট্যগুলির ক্ষেত্রে, এটি এর চেয়ে বেশি ভালো হতে পারে না।
উদাহরণস্বরূপ:
const adventurer = {
name: 'Alice',
cat: {
name: 'Dinah',
},
};
console.log(adventurer.dog?.name);
// Expected output: undefined
console.log(0 ?? 42);
// Expected output: 0

নমুনা অ্যাপ: ফুগু শুভেচ্ছা
এই ডকুমেন্টটির জন্য, আমি একটি PWA এর সাথে কাজ করি, যার নাম Fugu Greetings ( GitHub )। এই অ্যাপটির নাম Project Fugu 🐡 এর একটি উদাহরণ, যা ওয়েবকে Android, iOS এবং ডেস্কটপ অ্যাপ্লিকেশনের সমস্ত ক্ষমতা প্রদানের একটি প্রচেষ্টা। আপনি প্রকল্পটি সম্পর্কে আরও জানতে পারবেন এর ল্যান্ডিং পৃষ্ঠায় ।
ফুগু গ্রিটিংস হল একটি অঙ্কন অ্যাপ যা আপনাকে ভার্চুয়াল গ্রিটিং কার্ড তৈরি করতে এবং আপনার প্রিয়জনদের কাছে পাঠাতে দেয়। এটি PWA-এর মূল ধারণাগুলিকে উদাহরণ হিসেবে তুলে ধরে। এটি নির্ভরযোগ্য এবং সম্পূর্ণ অফলাইন সক্ষম, তাই আপনার নেটওয়ার্ক না থাকলেও আপনি এটি ব্যবহার করতে পারেন। এটি একটি ডিভাইসের হোম স্ক্রিনেও ইনস্টল করা যায় এবং একটি স্বতন্ত্র অ্যাপ্লিকেশন হিসাবে অপারেটিং সিস্টেমের সাথে নির্বিঘ্নে সংহত হয়।

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


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

ফাইল সিস্টেম অ্যাক্সেস 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);
}
};
ছবি রপ্তানি করা প্রায় একই রকম, কিন্তু এবার আমাকে 'save-file'
টাইপ প্যারামিটার chooseFileSystemEntries()
পদ্ধতিতে পাস করতে হবে। এখান থেকে আমি একটি ফাইল সংরক্ষণ ডায়ালগ পাই। ফাইল খোলার সাথে, এটি প্রয়োজনীয় ছিল না কারণ 'open-file'
ডিফল্ট। আমি আগের মতোই accepts
প্যারামিটার সেট করেছি, কিন্তু এবার কেবল PNG চিত্রের মধ্যে সীমাবদ্ধ। আবার আমি একটি ফাইল হ্যান্ডেল ফিরে পাই, কিন্তু ফাইলটি পাওয়ার পরিবর্তে, এবার আমি 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-এর সাহায্যে প্রগতিশীল বর্ধন ব্যবহার করে, আমি আগের মতোই একটি ফাইল খুলতে পারি। আমদানি করা ফাইলটি সরাসরি ক্যানভাসে আঁকা হয়। আমি আমার সম্পাদনাগুলি করতে পারি এবং অবশেষে একটি বাস্তব সংরক্ষণ ডায়ালগের মাধ্যমে সেগুলি সংরক্ষণ করতে পারি, যেখানে আমি ফাইলটির নাম এবং স্টোরেজ অবস্থান চয়ন করতে পারি। এখন ফাইলটি চিরকালের জন্য সংরক্ষণের জন্য প্রস্তুত।



ওয়েব শেয়ার এবং ওয়েব শেয়ার টার্গেট API গুলি
চিরকালের জন্য সংরক্ষণ করা ছাড়াও, হয়তো আমি আসলে আমার শুভেচ্ছা কার্ডটি শেয়ার করতে চাই। এটি এমন কিছু যা ওয়েব শেয়ার এপিআই এবং ওয়েব শেয়ার টার্গেট এপিআই আমাকে করার অনুমতি দেয়। মোবাইল এবং সম্প্রতি ডেস্কটপ অপারেটিং সিস্টেমগুলি বিল্ট-ইন শেয়ারিং মেকানিজম অর্জন করেছে।
উদাহরণস্বরূপ, যখন একজন ব্যবহারকারী " আমার ব্লগে নিবন্ধ ভাগ করুন" এ ক্লিক করেন, তখন macOS-এ ডেস্কটপ Safari-এর শেয়ার শিট ট্রিগার হয়। আপনি macOS Messages অ্যাপ ব্যবহার করে আপনার বন্ধুর সাথে নিবন্ধটির লিঙ্ক শেয়ার করতে পারেন।
এটি করার জন্য, আমি navigator.share()
কল করি এবং একটি অবজেক্টে একটি ঐচ্ছিক title
, text
এবং url
পাস করি। কিন্তু যদি আমি একটি ছবি সংযুক্ত করতে চাই? Web Share API এর লেভেল ১ এখনও এটি সমর্থন করে না। ভালো খবর হল Web Share লেভেল ২ ফাইল শেয়ারিং ক্ষমতা যোগ করেছে।
try {
await navigator.share({
title: 'Check out this article:',
text: `"${document.title}" by @tomayac:`,
url: document.querySelector('link[rel=canonical]').href,
});
} catch (err) {
console.warn(err.name, err.message);
}
Fugu Greeting card অ্যাপ্লিকেশনের মাধ্যমে এটি কীভাবে কাজ করবে তা আমি আপনাকে দেখাবো। প্রথমে, আমাকে একটি data
অবজেক্ট প্রস্তুত করতে হবে যার মধ্যে একটি files
অ্যারে থাকবে যার মধ্যে একটি ব্লব থাকবে, তারপর একটি 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
লোড করি। মোবাইল Safari এর মতো ব্রাউজারগুলিতে যেগুলি কেবল দুটি শর্তের একটি পূরণ করে, আমি কার্যকারিতা লোড করি না।
const loadShare = () => {
if ('share' in navigator && 'canShare' in navigator) {
import('./share.mjs');
}
};
ফুগু গ্রিটিংস-এ, যদি আমি অ্যান্ড্রয়েডে ক্রোমের মতো কোনও সাপোর্টিং ব্রাউজারে শেয়ার বোতামটি ট্যাপ করি, তাহলে বিল্ট-ইন শেয়ার শিটটি খোলে। উদাহরণস্বরূপ, আমি জিমেইল বেছে নিতে পারি, এবং ইমেল কম্পোজার উইজেটটি সংযুক্ত ছবিটি সহ পপ আপ হয়।


যোগাযোগ পিকার API
এরপর, আমি পরিচিতি সম্পর্কে কথা বলতে চাই, যার অর্থ একটি ডিভাইসের ঠিকানা বই বা পরিচিতি পরিচালক অ্যাপ। যখন আপনি একটি শুভেচ্ছা কার্ড লেখেন, তখন কারো নাম সঠিকভাবে লেখা সবসময় সহজ নাও হতে পারে। উদাহরণস্বরূপ, আমার এক বন্ধু সের্গেই আছে যে তার নাম সিরিলিক অক্ষরে লেখা পছন্দ করে। আমি একটি জার্মান QWERTZ কীবোর্ড ব্যবহার করি এবং তাদের নাম কীভাবে টাইপ করতে হয় তা জানি না। এটি এমন একটি সমস্যা যা Contact Picker API সমাধান করতে পারে। যেহেতু আমার বন্ধু আমার ফোনের পরিচিতি অ্যাপে সংরক্ষণ করেছে, Contacts Picker API ব্যবহার করে, আমি ওয়েব থেকে আমার পরিচিতিগুলিতে ট্যাপ করতে পারি।
প্রথমে, আমি যে বৈশিষ্ট্যগুলি অ্যাক্সেস করতে চাই তার তালিকা নির্দিষ্ট করতে হবে। এই ক্ষেত্রে, আমি কেবল নামগুলি চাই, তবে অন্যান্য ব্যবহারের ক্ষেত্রে আমি টেলিফোন নম্বর, ইমেল, অবতার আইকন বা ভৌত ঠিকানাগুলিতে আগ্রহী হতে পারি। এরপর, আমি একটি options
object কনফিগার করি এবং 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');
}
ফুগু গ্রিটিং-এ, যখন আমি " কন্টাক্টস " বোতামে ট্যাপ করি এবং আমার দুই সেরা বন্ধু, সার্গেই মিখাইলোভিচ ব্রিন এবং劳伦斯·爱德华·"拉里"·佩奇নির্বাচন করি, তখন আপনি দেখতে পাবেন যে কীভাবে পরিচিতি পিকার কেবল তাদের নাম দেখানোর মধ্যেই সীমাবদ্ধ, তাদের ইমেল ঠিকানা বা তাদের ফোন নম্বরের মতো অন্যান্য তথ্য নয়। তারপর তাদের নাম আমার শুভেচ্ছা কার্ডে আঁকা হয়।


অ্যাসিঙ্ক্রোনাস ক্লিপবোর্ড API
এরপরের কাজ হল কপি এবং পেস্ট করা। সফটওয়্যার ডেভেলপার হিসেবে আমাদের প্রিয় কাজগুলির মধ্যে একটি হল কপি এবং পেস্ট। একজন শুভেচ্ছা কার্ড লেখক হিসেবে, মাঝে মাঝে আমিও একই কাজ করতে চাই। আমি হয়ত আমার কাজ করা শুভেচ্ছা কার্ডে একটি ছবি পেস্ট করতে চাই, অথবা আমার শুভেচ্ছা কার্ডটি কপি করতে চাই যাতে আমি অন্য কোথাও থেকে এটি সম্পাদনা চালিয়ে যেতে পারি। Async Clipboard API টেক্সট এবং ছবি উভয়ই সমর্থন করে। আমি আপনাকে Fugu Greetings অ্যাপে কপি এবং পেস্ট সাপোর্ট কীভাবে যোগ করেছি তা দেখাই।
সিস্টেমের ক্লিপবোর্ডে কিছু কপি করার জন্য, আমাকে এটিতে লিখতে হবে। 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');
}
তাহলে বাস্তবে এটি কীভাবে কাজ করে? আমি macOS Preview অ্যাপে একটি ছবি খুলেছি এবং এটি ক্লিপবোর্ডে কপি করছি। যখন আমি Paste এ ক্লিক করি, তখন Fugu Greetings অ্যাপটি আমাকে জিজ্ঞাসা করে যে আমি কি অ্যাপটিকে ক্লিপবোর্ডে টেক্সট এবং ছবি দেখার অনুমতি দিতে চাই?

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

ব্যাজিং এপিআই
আরেকটি কার্যকর API হল Badging API । ইনস্টলযোগ্য PWA হিসেবে, Fugu Greetings-এ অবশ্যই একটি অ্যাপ আইকন থাকে যা ব্যবহারকারীরা অ্যাপ ডক বা হোম স্ক্রিনে রাখতে পারেন। API প্রদর্শনের একটি মজার উপায় হল 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');
}
এই উদাহরণে, আমি প্রতি সংখ্যার জন্য একটি কলমের স্ট্রোক ব্যবহার করে এক থেকে সাত পর্যন্ত সংখ্যাগুলি আঁকছি। আইকনের ব্যাজ কাউন্টারটি এখন সাত নম্বরে রয়েছে।


পর্যায়ক্রমিক পটভূমি সিঙ্ক 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
অনেক সময় অনেক অনুপ্রেরণা থাকা সত্ত্বেও, শুরু করা শুভেচ্ছা কার্ডটি শেষ করার জন্য আপনার একটি ধাক্কার প্রয়োজন হয়। এটি এমন একটি বৈশিষ্ট্য যা Notification Triggers API দ্বারা সক্রিয় করা হয়েছে। একজন ব্যবহারকারী হিসেবে, আমি এমন একটি সময় লিখতে পারি যখন আমি আমার শুভেচ্ছা কার্ডটি শেষ করার জন্য ধাক্কা দিতে চাই। যখন সেই সময় আসবে, তখন আমি একটি বিজ্ঞপ্তি পাব যে আমার শুভেচ্ছা কার্ডটি অপেক্ষা করছে।
লক্ষ্য সময়ের জন্য অনুরোধ করার পর, অ্যাপ্লিকেশনটি একটি showTrigger
দিয়ে বিজ্ঞপ্তিটি নির্ধারণ করে। এটি পূর্বে নির্বাচিত লক্ষ্য তারিখ সহ একটি TimestampTrigger
হতে পারে। অনুস্মারক বিজ্ঞপ্তি স্থানীয়ভাবে ট্রিগার করা হবে, কোনও নেটওয়ার্ক বা সার্ভার সাইডের প্রয়োজন হবে না।
const targetDate = promptTargetDate();
if (targetDate) {
const registration = await navigator.serviceWorker.ready;
registration.showNotification('Reminder', {
tag: 'reminder',
body: "It's time to finish your greeting card!",
showTrigger: new TimestampTrigger(targetDate),
});
}
আমি এখন পর্যন্ত যা কিছু দেখিয়েছি তার মতো, এটি একটি প্রগতিশীল বর্ধিতকরণ, তাই কোডটি কেবল শর্তসাপেক্ষে লোড করা হয়েছে।
if ('Notification' in window && 'showTrigger' in Notification.prototype) {
import('./notification_triggers.mjs');
}
যখন আমি ফুগু গ্রিটিংস-এ রিমাইন্ডার চেকবক্সটি চেক করি, তখন একটি প্রম্পট আমাকে জিজ্ঞাসা করে যে কখন আমাকে আমার শুভেচ্ছা কার্ডটি শেষ করার জন্য মনে করিয়ে দিতে হবে।

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

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

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



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