নেক্সট পেইন্টের সাথে মিথস্ক্রিয়া বোঝা (আইএনপি)

১. ভূমিকা

ইন্টারঅ্যাকশন টু নেক্সট পেইন্ট (INP) সম্পর্কে শেখার জন্য একটি ইন্টারেক্টিভ ডেমো এবং কোডল্যাব।

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

পূর্বশর্ত

  • এইচটিএমএল এবং জাভাস্ক্রিপ্ট ডেভেলপমেন্টের জ্ঞান।
  • পরামর্শ দেওয়া হচ্ছে: INP ডকুমেন্টেশনটি পড়ুন।

আপনি যা শিখবেন

  • ব্যবহারকারীর কার্যকলাপ এবং আপনার সেই কার্যকলাপ পরিচালনার পারস্পরিক ক্রিয়া কীভাবে পৃষ্ঠার প্রতিক্রিয়াশীলতাকে প্রভাবিত করে।
  • ব্যবহারকারীর মসৃণ অভিজ্ঞতার জন্য বিলম্ব কীভাবে কমানো ও দূর করা যায়।

তোমার যা দরকার

  • এমন একটি কম্পিউটার যা গিটহাব থেকে কোড ক্লোন করতে এবং এনপিএম কমান্ড চালাতে সক্ষম।
  • একটি টেক্সট এডিটর।
  • সমস্ত ইন্টারঅ্যাকশন পরিমাপ সঠিকভাবে কাজ করার জন্য ক্রোমের একটি সাম্প্রতিক সংস্করণ প্রয়োজন।

২. প্রস্তুত হন

কোডটি নিন এবং চালান

কোডটি web-vitals-codelabs রিপোজিটরিতে পাওয়া যায়।

  1. আপনার টার্মিনালে রিপোটি ক্লোন করুন: git clone https://github.com/GoogleChromeLabs/web-vitals-codelabs.git
  2. ক্লোন করা ডিরেক্টরিতে প্রবেশ করুন: cd web-vitals-codelabs/understanding-inp
  3. নির্ভরতা ইনস্টল করুন: npm ci
  4. ওয়েব সার্ভার চালু করুন: npm run start
  5. আপনার ব্রাউজারে http://localhost:5173/understanding-inp/ ভিজিট করুন

অ্যাপটির সংক্ষিপ্ত বিবরণ

পেজের একেবারে উপরে একটি স্কোর কাউন্টার এবং ইনক্রিমেন্ট বাটন রয়েছে। এটি রিঅ্যাকটিভিটি এবং রেসপন্সিভনেসের একটি চমৎকার উদাহরণ!

এই কোডল্যাবের ডেমো অ্যাপটির একটি স্ক্রিনশট।

বাটনটির নিচে চারটি পরিমাপ রয়েছে:

  • INP: বর্তমান INP স্কোর, যা সাধারণত সবচেয়ে খারাপ ইন্টারঅ্যাকশন।
  • মিথস্ক্রিয়া: সর্বশেষ মিথস্ক্রিয়ার স্কোর।
  • FPS: পেজটির প্রধান থ্রেডের ফ্রেম-প্রতি-সেকেন্ড।
  • টাইমার: জ্যাঙ্ক ভালোভাবে দেখানোর জন্য একটি চলমান টাইমার অ্যানিমেশন।

ইন্টারঅ্যাকশন পরিমাপের জন্য FPS এবং টাইমার এন্ট্রিগুলো মোটেও প্রয়োজনীয় নয়। রেসপন্সিভনেসকে আরেকটু সহজে দৃশ্যমান করার জন্যই কেবল এগুলো যোগ করা হয়েছে।

চেষ্টা করে দেখুন

ইনক্রিমেন্ট বাটনটির সাথে ইন্টারঅ্যাক্ট করে স্কোর বাড়তে দেখুন। প্রতিটি ইনক্রিমেন্টের সাথে কি INP এবং ইন্টারঅ্যাকশন ভ্যালুগুলো পরিবর্তিত হয়?

ব্যবহারকারীর ইন্টারঅ্যাকশনের মুহূর্ত থেকে শুরু করে পেজটিতে রেন্ডার করা আপডেটটি প্রদর্শিত হওয়া পর্যন্ত কতক্ষণ সময় লাগে, তা INP পরিমাপ করে।

৩. ক্রোম ডেভটুলস-এর সাথে মিথস্ক্রিয়া পরিমাপ করা

More Tools > Developer Tools মেনু থেকে, পেজটিতে রাইট-ক্লিক করে Inspect নির্বাচন করে , অথবা কিবোর্ড শর্টকাট ব্যবহার করে DevTools খুলুন

পারফরম্যান্স প্যানেলে যান, যেটি আপনি ইন্টারঅ্যাকশন পরিমাপ করতে ব্যবহার করবেন।

অ্যাপটির পাশে ডেভটুলস পারফরম্যান্স প্যানেলের একটি স্ক্রিনশট।

এরপরে, পারফরম্যান্স প্যানেলে একটি ইন্টারঅ্যাকশন ক্যাপচার করুন।

  1. প্রেস রেকর্ড।
  2. পৃষ্ঠাটির সাথে ইন্টারঅ্যাক্ট করুন ( ইনক্রিমেন্ট বাটনটি চাপুন)।
  3. রেকর্ডিং বন্ধ করুন।

ফলস্বরূপ টাইমলাইনে, আপনি একটি ইন্টারঅ্যাকশনস ট্র্যাক পাবেন। বাম দিকের ত্রিভুজটিতে ক্লিক করে এটিকে প্রসারিত করুন।

DevTools পারফরম্যান্স প্যানেল ব্যবহার করে কোনো ইন্টারঅ্যাকশন রেকর্ড করার একটি অ্যানিমেটেড প্রদর্শনী।

দুটি ইন্টারঅ্যাকশন দেখা যাবে। স্ক্রোল করে অথবা W কী চেপে ধরে দ্বিতীয়টিতে জুম করুন।

ডেভটুলস পারফরম্যান্স প্যানেলের একটি স্ক্রিনশট, যেখানে কার্সারটি প্যানেলের ইন্টারঅ্যাকশনের উপর রয়েছে এবং একটি টুলটিপ ইন্টারঅ্যাকশনটির সংক্ষিপ্ত সময়কাল তালিকাভুক্ত করছে।

ইন্টারঅ্যাকশনটির উপর মাউস রাখলে আপনি দেখতে পাবেন যে, ইন্টারঅ্যাকশনটি দ্রুত ছিল; এটি প্রসেসিং- এ কোনো সময় ব্যয় করেনি এবং ইনপুট ডিলেপ্রেজেন্টেশন ডিলে- তে ন্যূনতম সময় নিয়েছে, যেগুলোর সঠিক দৈর্ঘ্য আপনার মেশিনের গতির উপর নির্ভর করবে।

৪. দীর্ঘস্থায়ী ইভেন্ট শ্রোতা

index.js ফাইলটি খুলুন এবং ইভেন্ট লিসেনারের ভিতরে থাকা blockFor ফাংশনটির কমেন্ট তুলে দিন।

সম্পূর্ণ কোড দেখুন: click_block.html

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();
});

ফাইলটি সংরক্ষণ করুন। সার্ভার পরিবর্তনটি দেখতে পাবে এবং আপনার জন্য পৃষ্ঠাটি রিফ্রেশ করে দেবে।

পৃষ্ঠাটির সাথে আবার ইন্টারঅ্যাক্ট করার চেষ্টা করুন। এখন ইন্টারঅ্যাকশনগুলো লক্ষণীয়ভাবে ধীর হবে।

পারফরম্যান্স ট্রেস

পারফরম্যান্স প্যানেলে এটি কেমন দেখায় তা দেখতে আরেকটি রেকর্ডিং করুন।

পারফরম্যান্স প্যানেলে এক সেকেন্ড-ব্যাপী একটি মিথস্ক্রিয়া

যে আলাপচারিতা একসময় সংক্ষিপ্ত ছিল, এখন তাতে পুরো এক সেকেন্ড সময় লাগে।

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

৫. পরীক্ষা: প্রক্রিয়াকরণের সময়কাল

INP-এর উপর এর প্রভাব দেখতে ইভেন্ট-লিসেনারের কাজ পুনর্বিন্যাস করার বিভিন্ন উপায় পরীক্ষা করে দেখুন।

প্রথমে UI আপডেট করুন

js কলগুলোর ক্রম উল্টে দিলে কী হবে—প্রথমে UI আপডেট করে, তারপর ব্লক করলে?

সম্পূর্ণ কোড দেখুন: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

আপনি কি লক্ষ্য করেছেন যে UI আগে প্রদর্শিত হয়েছে? ক্রমটি কি INP স্কোরকে প্রভাবিত করে?

একটি ট্রেস নিয়ে মিথস্ক্রিয়াটি পরীক্ষা করে দেখুন কোনো পার্থক্য ছিল কিনা।

পৃথক শ্রোতা

কাজটি একটি আলাদা ইভেন্ট লিসেনারে সরিয়ে নিলে কেমন হয়? একটি ইভেন্ট লিসেনারে UI আপডেট করুন, এবং অন্য একটি লিসেনার থেকে পেজটি ব্লক করুন।

সম্পূর্ণ কোড দেখুন: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

পারফরম্যান্স প্যানেলে এখন এটি দেখতে কেমন লাগছে?

বিভিন্ন ধরণের ইভেন্ট

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

অনেক আসল পেজেই বিভিন্ন অনুষ্ঠানের জন্য শ্রোতা থাকে।

ইভেন্ট লিসেনারগুলোর ইভেন্ট টাইপ পরিবর্তন করলে কী হবে? উদাহরণস্বরূপ, click ইভেন্ট লিসেনারগুলোর একটিকে pointerup বা mouseup দিয়ে প্রতিস্থাপন করলে কী হবে?

সম্পূর্ণ কোড দেখুন: diff_handlers.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

কোন UI আপডেট নেই

ইভেন্ট লিসেনার থেকে UI আপডেট করার কলটি সরিয়ে দিলে কী হবে?

সম্পূর্ণ কোড দেখুন: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});

৬. প্রক্রিয়াকরণের সময়কাল পরীক্ষার ফলাফল

পারফরম্যান্স ট্রেস: প্রথমে UI আপডেট করুন

সম্পূর্ণ কোড দেখুন: ui_first.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  blockFor(1000);
});

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

পারফরম্যান্স প্যানেলে একটি স্থির এক-সেকেন্ড-ব্যাপী মিথস্ক্রিয়া

পারফরম্যান্স ট্রেস: পৃথক লিসেনার

সম্পূর্ণ কোড দেখুন: two_click.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('click', () => {
  blockFor(1000);
});

আবার, কার্যগতভাবে কোনো পার্থক্য নেই। মিথস্ক্রিয়াটি সম্পন্ন হতে এখনও পুরো এক সেকেন্ড সময় লাগে।

আপনি যদি ক্লিক ইন্টারঅ্যাকশনটি ভালোভাবে দেখেন, তাহলে দেখতে পাবেন যে click ইভেন্টের ফলে প্রকৃতপক্ষে দুটি ভিন্ন ফাংশন কল করা হচ্ছে।

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

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

পারফরম্যান্স ট্রেস: বিভিন্ন ইভেন্টের ধরণ

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

button.addEventListener('pointerup', () => {
  blockFor(1000);
});

এই ফলাফলগুলো খুবই সাদৃশ্যপূর্ণ। ইন্টারঅ্যাকশনটি এখনও পুরো এক সেকেন্ডের; একমাত্র পার্থক্য হলো, সংক্ষিপ্ততর এবং শুধুমাত্র UI-আপডেট-ভিত্তিক click লিসেনারটি এখন ব্লকিং pointerup লিসেনারের পরে চলে।

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

পারফরম্যান্স ট্রেস: কোন UI আপডেট নেই

সম্পূর্ণ কোড দেখুন: no_ui.html

button.addEventListener('click', () => {
  blockFor(1000);
  // score.incrementAndUpdateUI();
});
  • স্কোর আপডেট হয় না, কিন্তু পেজটি ঠিকই দেখা যায়!
  • অ্যানিমেশন, সিএসএস এফেক্ট, ডিফল্ট ওয়েব কম্পোনেন্ট অ্যাকশন (ফর্ম ইনপুট), টেক্সট এন্ট্রি, টেক্সট হাইলাইটিং—সবকিছু ক্রমাগত আপডেট হতে থাকে।

এক্ষেত্রে বাটনটি ক্লিক করলে সক্রিয় অবস্থায় যায় এবং আবার আগের অবস্থায় ফিরে আসে, যার জন্য ব্রাউজারকে রঙ করতে হয়, যার মানে এখানে এখনও একটি INP রয়েছে।

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

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

পারফরম্যান্স প্যানেলে একটি স্থির এক-সেকেন্ড-ব্যাপী মিথস্ক্রিয়া

টেকঅ্যাওয়ে

যেকোনো ইভেন্ট লিসেনারে চলমান যেকোনো কোড ইন্টারঅ্যাকশনকে বিলম্বিত করবে।

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

এটা একটা সাধারণ সমস্যা!

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

৭. পরীক্ষা: ইনপুট বিলম্ব

ইভেন্ট লিসেনারের বাইরে দীর্ঘক্ষণ ধরে চলা কোডের ক্ষেত্রে কী হবে? উদাহরণস্বরূপ:

  • যদি আপনার এমন কোনো <script> থাকতো যা দেরিতে লোড হতো এবং লোড হওয়ার সময় হুট করে পেজটিকে আটকে দিত।
  • setInterval মতো কোনো এপিআই কল, যা পর্যায়ক্রমে পেজটিকে ব্লক করে রাখে?

ইভেন্ট লিসেনার থেকে blockFor সরিয়ে দিয়ে এটিকে setInterval() এ যোগ করার চেষ্টা করুন:

সম্পূর্ণ কোড দেখুন: input_delay.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

কী ঘটে?

৮. ইনপুট বিলম্ব পরীক্ষার ফলাফল

সম্পূর্ণ কোড দেখুন: input_delay.html

setInterval(() => {
  blockFor(1000);
}, 3000);


button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
});

setInterval ব্লকিং টাস্কটি চলার সময় কোনো বাটন ক্লিক রেকর্ড করলে, সেই ইন্টারঅ্যাকশনে কোনো ব্লকিং কাজ না হওয়া সত্ত্বেও একটি দীর্ঘস্থায়ী ইন্টারঅ্যাকশন তৈরি হয়!

এই দীর্ঘস্থায়ী সময়কালগুলোকে প্রায়শই দীর্ঘ কাজ বলা হয়।

DevTools-এ ইন্টারঅ্যাকশনটির উপর মাউস রাখলে আপনি দেখতে পাবেন যে, ইন্টারঅ্যাকশনের সময় এখন প্রধানত ইনপুট বিলম্বের কারণে নির্ধারিত হয়, প্রসেসিং সময়কালের কারণে নয়।

DevTools পারফরম্যান্স প্যানেলে একটি এক-সেকেন্ডের ব্লকিং টাস্ক, সেই টাস্কটি মাঝপথে শুরু হওয়া একটি ইন্টারঅ্যাকশন এবং একটি ৬৪২ মিলিসেকেন্ডের ইন্টারঅ্যাকশন দেখা যাচ্ছে, যার প্রধান কারণ হিসেবে ইনপুট বিলম্বকে দায়ী করা হচ্ছে।

লক্ষ্য করুন, এটি সবসময় ইন্টারঅ্যাকশনকে প্রভাবিত করে না! টাস্কটি চলার সময় আপনি যদি ক্লিক না করেন, তাহলে আপনার ভাগ্য ভালো থাকতে পারে। এই ধরনের "হঠাৎ" ঘটা সমস্যাগুলো ডিবাগ করা দুঃস্বপ্নের মতো হতে পারে, বিশেষ করে যখন এগুলো কেবল মাঝে মাঝে সমস্যা সৃষ্টি করে।

এগুলো খুঁজে বের করার একটি উপায় হলো দীর্ঘ টাস্ক (বা দীর্ঘ অ্যানিমেশন ফ্রেম ) এবং মোট ব্লকিং টাইম পরিমাপ করা।

৯. ধীর উপস্থাপনা

এখন পর্যন্ত আমরা ইনপুট ডিলে বা ইভেন্ট লিসেনারের মাধ্যমে জাভাস্ক্রিপ্টের পারফরম্যান্স দেখেছি, কিন্তু পরবর্তী পেইন্ট রেন্ডারিংকে আর কী কী প্রভাবিত করে?

আচ্ছা, দামী ইফেক্ট দিয়ে পেজটা আপডেট করা হচ্ছে!

পৃষ্ঠাটি দ্রুত আপডেট হলেও, সেগুলোকে রেন্ডার করতে ব্রাউজারকে বেশ পরিশ্রম করতে হতে পারে!

মূল থ্রেডে:

  • UI ফ্রেমওয়ার্ক যেগুলোকে অবস্থার পরিবর্তনের পর আপডেট রেন্ডার করতে হয়
  • DOM-এর পরিবর্তন, অথবা অনেক ব্যয়বহুল CSS কোয়েরি সিলেক্টর টগল করার ফলে প্রচুর Style, Layout, এবং Paint ট্রিগার হতে পারে।

মূল আলোচনার বাইরে:

  • GPU ইফেক্টকে শক্তিশালী করতে CSS ব্যবহার করা
  • খুব বড় উচ্চ-রেজোলিউশনের ছবি যোগ করা
  • SVG/ক্যানভাস ব্যবহার করে জটিল দৃশ্য আঁকা

ওয়েবে রেন্ডারিংয়ের বিভিন্ন উপাদানের রূপরেখা

রেন্ডারিংএনজি

ওয়েবে সচরাচর পাওয়া যায় এমন কিছু উদাহরণ:

  • একটি SPA সাইট যা কোনো লিঙ্কে ক্লিক করার পর প্রাথমিক ভিজ্যুয়াল ফিডব্যাক দেওয়ার জন্য বিরতি না নিয়েই সম্পূর্ণ DOM পুনর্নির্মাণ করে।
  • একটি সার্চ পেজ যা ডাইনামিক ইউজার ইন্টারফেস সহ জটিল সার্চ ফিল্টার প্রদান করে, কিন্তু তা করার জন্য ব্যয়বহুল লিসেনার চালায়।
  • একটি ডার্ক মোড টগল যা পুরো পৃষ্ঠার জন্য স্টাইল/লেআউট চালু করে।

১০. পরীক্ষা: উপস্থাপনায় বিলম্ব

ধীর requestAnimationFrame

চলুন requestAnimationFrame() API ব্যবহার করে একটি দীর্ঘ প্রেজেন্টেশন বিলম্ব অনুকরণ করি।

blockFor কলটিকে একটি requestAnimationFrame কলব্যাকে সরিয়ে নিন, যাতে এটি ইভেন্ট লিসেনার রিটার্ন করার পরে চলে:

সম্পূর্ণ কোড দেখুন: presentation_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

কী ঘটে?

১১. উপস্থাপনা বিলম্ব পরীক্ষার ফলাফল

সম্পূর্ণ কোড দেখুন: presentation_delay.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();
  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

কথোপকথনটি এক সেকেন্ড স্থায়ী হয়, তাহলে কী ঘটল?

requestAnimationFrame পরবর্তী পেইন্টের আগে একটি কলব্যাক অনুরোধ করে। যেহেতু INP ইন্টারঅ্যাকশন থেকে পরবর্তী পেইন্ট পর্যন্ত সময় পরিমাপ করে, তাই requestAnimationFrame এর blockFor(1000) পরবর্তী পেইন্টকে পুরো এক সেকেন্ডের জন্য ব্লক করে রাখে।

পারফরম্যান্স প্যানেলে একটি স্থির এক-সেকেন্ড-ব্যাপী মিথস্ক্রিয়া

তবে, দুটি বিষয় লক্ষ্য করুন:

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

১২. মিথস্ক্রিয়া নির্ণয়

এই টেস্ট পেজটিতে, স্কোর, টাইমার এবং কাউন্টার UI-এর কারণে রেসপন্সিভনেস অত্যন্ত দৃশ্যমান... কিন্তু সাধারণ পেজ পরীক্ষা করার সময় এটি আরও সূক্ষ্ম।

যখন আলাপচারিতা দীর্ঘায়িত হয়, তখন এর কারণটা সবসময় স্পষ্ট বোঝা যায় না। এর কারণ কি:

  • ইনপুট বিলম্ব?
  • ইভেন্ট প্রক্রিয়াকরণের সময়কাল?
  • উপস্থাপনায় বিলম্ব?

আপনার ইচ্ছামত যেকোনো পেজে রেসপন্সিভনেস পরিমাপ করতে আপনি ডেভটুলস ব্যবহার করতে পারেন। এই অভ্যাসটি গড়ে তুলতে, এই পদ্ধতিটি অনুসরণ করুন:

  1. আপনি স্বাভাবিকভাবেই ওয়েব ব্যবহার করুন।
  2. DevTools পারফরম্যান্স প্যানেলের লাইভ মেট্রিক্স ভিউতে থাকা ইন্টারঅ্যাকশন লগটির উপর নজর রাখুন।
  3. যদি আপনি কোনো ত্রুটিপূর্ণ ইন্টারঅ্যাকশন দেখতে পান, তবে সেটি পুনরাবৃত্তি করার চেষ্টা করুন:
  • যদি আপনি এটি পুনরাবৃত্তি করতে না পারেন, তাহলে তথ্য পেতে ইন্টারঅ্যাকশন লগ ব্যবহার করুন।
  • আপনি যদি এটি পুনরাবৃত্তি করতে পারেন, তাহলে পারফরম্যান্স প্যানেলে একটি ট্রেস রেকর্ড করুন।

সমস্ত বিলম্ব

পৃষ্ঠাটিতে এই সমস্যাগুলোর সবকটি থেকে কিছুটা করে যোগ করার চেষ্টা করুন:

সম্পূর্ণ কোড দেখুন: all_the_things.html

setInterval(() => {
  blockFor(1000);
}, 3000);

button.addEventListener('click', () => {
  blockFor(1000);
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    blockFor(1000);
  });
});

তারপর সমস্যাগুলো নির্ণয় করতে কনসোল এবং পারফরম্যান্স প্যানেল ব্যবহার করুন!

১৩. পরীক্ষা: অ্যাসিঙ্ক কাজ

যেহেতু আপনি ইন্টারঅ্যাকশনের ভেতরেই নন-ভিজ্যুয়াল ইফেক্ট শুরু করতে পারেন, যেমন নেটওয়ার্ক রিকোয়েস্ট পাঠানো, টাইমার চালু করা, বা শুধু গ্লোবাল স্টেট আপডেট করা, তাহলে যখন সেগুলো অবশেষে পেজটি আপডেট করে, তখন কী ঘটে?

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

এটি পরীক্ষা করার জন্য, ক্লিক লিসেনার থেকে UI আপডেট করা চালিয়ে যান, কিন্তু ব্লকিং কাজটি টাইমআউট থেকে চালান।

সম্পূর্ণ কোড দেখুন: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

এখন কী হবে?

১৪. অ্যাসিঙ্ক ওয়ার্ক পরীক্ষার ফলাফল

সম্পূর্ণ কোড দেখুন: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

এক সেকেন্ড দীর্ঘ একটি কাজের সাথে ২৭ মিলিসেকেন্ডের একটি মিথস্ক্রিয়া, যা এখন ট্রেসের পরবর্তী অংশে ঘটছে।

ইন্টারঅ্যাকশনটি এখন সংক্ষিপ্ত, কারণ UI আপডেট হওয়ার সাথে সাথেই প্রধান থ্রেডটি উপলব্ধ হয়ে যায়। দীর্ঘ ব্লকিং টাস্কটি তখনও চলে, তবে এটি পেইন্টের কিছুক্ষণ পরে চলে, ফলে ব্যবহারকারী তাৎক্ষণিক UI ফিডব্যাক পাবেন।

শিক্ষা: যদি সরাতে না পারেন, অন্তত সরিয়ে দিন!

পদ্ধতি

আমরা কি একটি নির্দিষ্ট ১০০ মিলিসেকেন্ডের setTimeout চেয়ে ভালো কিছু করতে পারি? আমরা সম্ভবত এখনও চাই যে কোডটি যত দ্রুত সম্ভব চলুক, নইলে আমাদের এটি সরিয়েই দেওয়া উচিত ছিল!

লক্ষ্য:

  • ইন্টারঅ্যাকশনটি incrementAndUpdateUI() চালাবে।
  • blockFor() যত তাড়াতাড়ি সম্ভব চলবে, কিন্তু পরবর্তী পেইন্টকে বাধা দেবে না।
  • এর ফলে কোনো ‘জাদুকরী টাইমআউট’ ছাড়াই অনুমানযোগ্য আচরণ পাওয়া যায়।

এটি সম্পন্ন করার কয়েকটি উপায় হলো:

  • setTimeout(0)
  • Promise.then()
  • requestAnimationFrame
  • requestIdleCallback
  • scheduler.postTask()

"requestPostAnimationFrame"

শুধুমাত্র requestAnimationFrame বিপরীতে (যা পরবর্তী পেইন্টের আগে চলার চেষ্টা করে এবং সাধারণত ইন্টারঅ্যাকশনকে ধীরগতির করে তোলে), requestAnimationFramesetTimeout একত্রে requestPostAnimationFrame জন্য একটি সহজ পলিফিল হিসেবে কাজ করে, যা পরবর্তী পেইন্টের পরে কলব্যাকটি চালায়।

সম্পূর্ণ কোড দেখুন: raf+task.html

function afterNextPaint(callback) {
  requestAnimationFrame(() => {
    setTimeout(callback, 0);
  });
}

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  afterNextPaint(() => {
    blockFor(1000);
  });
});

আরামদায়ক ব্যবহারের সুবিধার জন্য, আপনি এটিকে একটি প্রতিশ্রুতির আবরণেও মুড়ে দিতে পারেন:

সম্পূর্ণ কোড দেখুন: raf+task2.html

async function nextPaint() {
  return new Promise(resolve => afterNextPaint(resolve));
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  await nextPaint();
  blockFor(1000);
});

১৫. একাধিক মিথস্ক্রিয়া (এবং রাগের বশে ক্লিক)

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

পৃষ্ঠাটির অ্যাসিঙ্ক ব্লকিং ওয়ার্ক সংস্করণটি আবার চেষ্টা করুন (অথবা আপনার নিজের সংস্করণটি, যদি আপনি গত ধাপে কাজ স্থগিত করার জন্য নিজস্ব কোনো পদ্ধতি তৈরি করে থাকেন):

সম্পূর্ণ কোড দেখুন: timeout_100.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  setTimeout(() => {
    blockFor(1000);
  }, 100);
});

দ্রুত একাধিকবার ক্লিক করলে কী হবে?

পারফরম্যান্স ট্রেস

প্রতিটি ক্লিকের জন্য এক সেকেন্ড দীর্ঘ একটি কাজ সারিতে যুক্ত হয়, যার ফলে মূল থ্রেডটি যথেষ্ট পরিমাণ সময়ের জন্য ব্লক হয়ে থাকে।

প্রধান থ্রেডে একাধিক সেকেন্ড-ব্যাপী টাস্ক চলার কারণে ইন্টারঅ্যাকশন ৮০০ মিলিসেকেন্ডের মতো ধীর হয়ে যায়।

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

কৌশল

আদর্শগতভাবে, আমরা দীর্ঘ কাজগুলো সম্পূর্ণরূপে বাদ দিতে চাই!

  • অপ্রয়োজনীয় কোড সম্পূর্ণরূপে মুছে ফেলুন—বিশেষ করে স্ক্রিপ্ট।
  • দীর্ঘ সময় ধরে চলা কাজ এড়াতে কোড অপ্টিমাইজ করুন।
  • নতুন ইন্টারঅ্যাকশন এলে পুরনো কাজ বন্ধ করুন।

১৬. কৌশল ১: ডিবান্স

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

  • ব্যয়বহুল কাজ শুরু করতে বিলম্ব করার জন্য, একটি টাইমার ব্যবহার করে setTimeout প্রয়োগ করুন, যা সম্ভবত ৫০০ থেকে ১০০০ মিলিসেকেন্ড পর্যন্ত হতে পারে।
  • এটি করার সময় টাইমার আইডিটি সংরক্ষণ করুন।
  • নতুন কোনো ইন্টারঅ্যাকশন এলে, clearTimeout ব্যবহার করে আগের টাইমারটি বাতিল করুন।

সম্পূর্ণ কোড দেখুন: debounce.html

let timer;
button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  if (timer) {
    clearTimeout(timer);
  }
  timer = setTimeout(() => {
    blockFor(1000);
  }, 1000);
});

পারফরম্যান্স ট্রেস

একাধিক মিথস্ক্রিয়া, কিন্তু সেগুলোর সবকিছুর ফলস্বরূপ একটিই দীর্ঘ কর্মসম্পাদন।

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

১৭. কৌশল ২: দীর্ঘক্ষণ ধরে চলা কাজে বাধা দেওয়া

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

আদর্শগতভাবে, আমাদের কাজের মাঝখানে কোনো আলাপ এলে আমরা আমাদের ব্যস্ততাপূর্ণ কাজটি থামিয়ে দিতে চাই, যাতে নতুন আলাপগুলো সঙ্গে সঙ্গেই সামলে নেওয়া যায়। আমরা কীভাবে তা করতে পারি?

isInputPending মতো কিছু API আছে, কিন্তু সাধারণত দীর্ঘ কাজগুলোকে ছোট ছোট অংশে ভাগ করে নেওয়াই ভালো

অনেক setTimeout

প্রথম চেষ্টা: সহজ কিছু করুন।

সম্পূর্ণ কোড দেখুন: small_tasks.html

button.addEventListener('click', () => {
  score.incrementAndUpdateUI();

  requestAnimationFrame(() => {
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
    setTimeout(() => blockFor(100), 0);
  });
});

এটি ব্রাউজারকে প্রতিটি কাজ আলাদাভাবে নির্ধারণ করার সুযোগ দিয়ে কাজ করে, এবং ইনপুট উচ্চতর অগ্রাধিকার পেতে পারে!

একাধিকবার যোগাযোগ করা হয়েছে, কিন্তু সমস্ত নির্ধারিত কাজকে অনেকগুলো ছোট ছোট কাজে ভাগ করা হয়েছে।

আমরা আবার পাঁচটি ক্লিকের জন্য পুরো পাঁচ সেকেন্ডের কাজে ফিরে এসেছি, কিন্তু প্রতি ক্লিকের এক সেকেন্ডের কাজটিকে দশটি ১০০ মিলিসেকেন্ডের কাজে ভাগ করা হয়েছে। ফলে—এমনকি একাধিক ইন্টারঅ্যাকশন সেই কাজগুলোর সাথে ওভারল্যাপ করলেও—কোনো ইন্টারঅ্যাকশনেরই ১০০ মিলিসেকেন্ডের বেশি ইনপুট ডিলে থাকে না! ব্রাউজার setTimeout কাজের চেয়ে আগত ইভেন্ট লিসেনারগুলোকে অগ্রাধিকার দেয়, এবং ইন্টারঅ্যাকশনগুলো রেসপন্সিভ থাকে।

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

তবে, নিবিড়ভাবে সংযুক্ত কোড, যেমন শেয়ার্ড স্টেট ব্যবহারকারী একটি for লুপকে, আলাদা করার ক্ষেত্রে এই কৌশলটি ততটা কার্যকর নয়।

এখন yield() দিয়ে

তবে, আমরা আধুনিক async এবং await ব্যবহার করে যেকোনো জাভাস্ক্রিপ্ট ফাংশনে সহজেই 'yield points' যোগ করতে পারি।

উদাহরণস্বরূপ:

সম্পূর্ণ কোড দেখুন: yieldy.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldy(ms) {
  const ms_per_part = 10;
  const parts = ms / ms_per_part;
  for (let i = 0; i < parts; i++) {
    await schedulerDotYield();

    blockFor(ms_per_part);
  }
}

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();
  await blockInPiecesYieldy(1000);
});

আগের মতোই, একটি নির্দিষ্ট পরিমাণ কাজ শেষ হওয়ার পর মূল থ্রেডটি বন্ধ হয়ে যায় এবং ব্রাউজার যেকোনো আগত ইন্টারঅ্যাকশনে সাড়া দিতে পারে, কিন্তু এখন আলাদা setTimeout এর পরিবর্তে শুধু একটি await schedulerDotYield() ব্যবহার করাই যথেষ্ট, ফলে এটি একটি for লুপের মাঝখানেও ব্যবহারের জন্য যথেষ্ট সুবিধাজনক।

এখন AbortContoller() এর সাথে

সেটা কাজ করেছিল, কিন্তু প্রতিটি আলাপচারিতা আরও কাজের চাপ বাড়ায়, এমনকি যদি নতুন আলাপচারিতা এসে থাকে এবং করণীয় কাজের ধরন বদলে দিয়ে থাকে।

ডিবাউন্সিং কৌশলের মাধ্যমে, আমরা প্রতিটি নতুন ইন্টারঅ্যাকশনের সাথে পূর্ববর্তী টাইমআউটটি বাতিল করে দিয়েছিলাম। আমরা কি এখানেও একই রকম কিছু করতে পারি? এটি করার একটি উপায় হলো AbortController() ব্যবহার করা:

সম্পূর্ণ কোড দেখুন: aborty.html

// Polyfill for scheduler.yield()
async function schedulerDotYield() {
  return new Promise(resolve => {
    setTimeout(resolve, 0);
  });
}

async function blockInPiecesYieldyAborty(ms, signal) {
  const parts = ms / 10;
  for (let i = 0; i < parts; i++) {
    // If AbortController has been asked to stop, abandon the current loop.
    if (signal.aborted) return;

    await schedulerDotYield();

    blockFor(10);
  }
}

let abortController = new AbortController();

button.addEventListener('click', async () => {
  score.incrementAndUpdateUI();

  abortController.abort();
  abortController = new AbortController();

  await blockInPiecesYieldyAborty(1000, abortController.signal);
});

যখন কোনো ক্লিক আসে, তখন এটি blockInPiecesYieldyAborty for loop-টি চালু করে প্রয়োজনীয় কাজ করতে থাকে এবং পর্যায়ক্রমে main thread-কে yield করে, যাতে ব্রাউজারটি নতুন ইন্টারঅ্যাকশনের জন্য সাড়া দিতে পারে।

যখন দ্বিতীয়বার ক্লিক করা হয়, তখন AbortController ব্যবহার করে প্রথম লুপটিকে বাতিল হিসেবে চিহ্নিত করা হয় এবং একটি নতুন blockInPiecesYieldyAborty লুপ শুরু হয়—পরের বার যখন প্রথম লুপটি আবার চলার জন্য নির্ধারিত হয়, তখন এটি লক্ষ্য করে যে signal.aborted এখন true এবং আর কোনো কাজ না করেই অবিলম্বে ফিরে আসে।

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

১৮. উপসংহার

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

মনে রাখবেন

  • INP সমস্ত মিথস্ক্রিয়া পরিমাপ করে।
  • প্রতিটি মিথস্ক্রিয়াকে ইনপুট থেকে পরবর্তী রঙ করা পর্যন্ত পরিমাপ করা হয়—এভাবেই ব্যবহারকারী প্রতিক্রিয়াশীলতা উপলব্ধি করেন
  • ইনপুট বিলম্ব, ইভেন্ট প্রক্রিয়াকরণের সময়কাল এবং উপস্থাপনা বিলম্ব— এই সবগুলোই মিথস্ক্রিয়ার প্রতিক্রিয়াশীলতাকে প্রভাবিত করে।
  • আপনি DevTools-এর সাহায্যে সহজেই INP এবং ইন্টারঅ্যাকশন ব্রেকডাউন পরিমাপ করতে পারেন!

কৌশল

  • আপনার পেজগুলোতে দীর্ঘক্ষণ ধরে চলা কোড (দীর্ঘ কাজ) রাখবেন না।
  • পরবর্তী পেইন্টের পর পর্যন্ত অপ্রয়োজনীয় কোড ইভেন্ট লিসেনার থেকে সরিয়ে রাখুন।
  • নিশ্চিত করুন যে রেন্ডারিং আপডেটটি ব্রাউজারের জন্য কার্যকর।

আরও জানুন