Interaction to Next Paint (INP) সম্পর্কে বুঝে নেওয়া

১. ভূমিকা

Interaction to Next Paint (INP) সম্পর্কে জানার জন্য একটি ইন্টার‍্যাক্টিভ ডেমো ও কোডল্যাব।

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

পূর্বশর্ত

  • HTML ও JavaScript ডেভেলপমেন্ট সম্পর্কে জ্ঞান।
  • সাজেস্ট করা হয়েছে: INP ডকুমেন্টেশন পড়ুন।

আপনি কী শিখবেন

  • ব্যবহারকারীর ইন্টার‍্যাকশন এবং সেই ইন্টার‍্যাকশন আপনি কীভাবে ম্যানেজ করছেন, এই দুটির পারস্পরিক প্রভাব পৃষ্ঠার রেসপন্সিভনেসকে কীভাবে প্রভাবিত করে।
  • ব্যবহারকারীকে নির্বিঘ্ন অভিজ্ঞতা প্রদান করতে কীভাবে দেরি কমানো ও দূর করা যায়।

আপনার যা প্রয়োজন

  • GitHub থেকে কোড ক্লোন করা এবং npm কমান্ড রান করার ক্ষমতা সহ একটি কম্পিউটার।
  • একটি টেক্সট এডিটর।
  • সব ইন্টার‍্যাকশন পরিমাপ করার জন্য Chrome-এর সাম্প্রতিক ভার্সন।

২. সেট-আপ করুন

কোড পান ও রান করুন

কোডটি 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 পরিমাপ করে।

৩. Chrome DevTools-এর মাধ্যমে ইন্টার‍্যাকশন পরিমাপ করা

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

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

অ্যাপের পাশাপাশি DevTools পারফর্ম্যান্স প্যানেলের স্ক্রিনশট

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

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

ফলাফল হিসেবে দেখানো টাইমলাইনে, আপনি ইন্টার‍্যাকশন ট্র্যাক দেখতে পাবেন। বাঁদিকে দেখানো ত্রিভুজে ক্লিক করে এটি বড় করুন।

DevTools পারফর্ম্যান্স প্যানেল ব্যবহার করে কোনও ইন্ট্যার‌্যাকশন রেকর্ড করার অ্যানিমেটেড ডেমোস্ট্রেশন

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

DevTools পারফর্ম্যান্স প্যানেলের স্ক্রিনশট, প্যানেলে ইন্টার‍্যাকশনের উপর কার্সর ঘোরাফেরা করছে এবং ইন্টার‍্যাকশনের সংক্ষিপ্ত সময় তালিকাভুক্ত করে একটি টুলটিপ

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

৪. অনেকক্ষণ ধরে চলা ইভেন্ট লিসনার

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

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

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

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

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

ইভেন্ট লিসনারের জন্য ইভেন্টের ধরন পরিবর্তন করলে কী হবে? যেমন, 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 ইভেন্টের ফলে দুটি ভিন্ন ফাংশন কল করা হচ্ছে।

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

এই উদাহরণে এক সেকেন্ড দীর্ঘ ইন্টার‍্যাকশনের একটি জুম-ইন ভিউ, যা দেখায় যে প্রথম ফাংশন কলটি সম্পূর্ণ হতে এক মিলিসেকেন্ডের কম সময় নেয়

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

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

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

এই ফলাফলগুলি খুব একই রকম। ইন্ট্যার‍্যাকশন এখনও পুরো এক সেকেন্ড; একমাত্র পার্থক্য হল যে ছোট UI-আপডেট-অনলি click লিসেনার এখন ব্লকিং pointerup লিসেনারের পরে রান করে।

এই উদাহরণে এক সেকেন্ড দীর্ঘ ইন্টার‍্যাকশনের জুম-ইন করা ভিউ, যা দেখায় যে পয়েন্টারআপ লিসনারের পরে ক্লিক ইভেন্ট লিসনার সম্পূর্ণ হতে এক মিলিসেকেন্ডের কম সময় নেয়

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

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

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

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

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

পারফর্ম্যান্স প্যানেল রেকর্ডিং নিলে, ইন্টার‍্যাকশনটি আগের ইন্টার‍্যাকশনের মতোই দেখায়।

পারফর্ম্যান্স প্যানেলে এখনও এক সেকেন্ড দীর্ঘ ইন্টার‍্যাকশন

টেক-আ্যাওয়ে

যেকোনও ইভেন্ট লিসনারে রান করা যেকোনও কোড ইন্টার‍্যাকশনে দেরি করবে।

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

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

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

৭. এক্সপেরিমেন্ট: ইনপুট দিতে দেরি

ইভেন্ট লিসনারের বাইরে দীর্ঘ সময় ধরে রান করা কোডের ক্ষেত্রে কী হবে? যেমন:

  • আপনার যদি দেরিতে লোড হওয়া <script> থাকে যা লোড হওয়ার সময় পৃষ্ঠাটিকে এলোমেলোভাবে ব্লক করে দেয়।
  • setInterval-এর মতো কোনও API কল, যা মাঝে মাঝে পৃষ্ঠা ব্লক করে?

ইভেন্ট লিসনার থেকে 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 পারফর্ম্যান্স প্যানেল এক সেকেন্ডের ব্লকিং টাস্ক, সেই টাস্কের মাধ্যমে আংশিকভাবে আসা ইন্টার‍্যাকশন এবং ৬৪২ মিলিসেকেন্ডের ইন্টার‍্যাকশন দেখায়, যা মূলত ইনপুট বিলম্বের কারণে হয়

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

এগুলি ট্র্যাক করার একটি উপায় হল বড় টাস্ক (বা লং অ্যানিমেশন ফ্রেম) এবং Total Blocking Time পরিমাপ করা।

৯. স্লো প্রেজেন্টেশন

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

ঠিক আছে, দামি এফেক্ট দিয়ে পৃষ্ঠা আপডেট করা হচ্ছে!

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

মূল থ্রেডে:

  • স্ট্যাটাস পরিবর্তনের পরে আপডেট রেন্ডার করতে হবে এমন UI ফ্রেমওয়ার্ক
  • DOM পরিবর্তন বা অনেক বেশি CSS কোয়েরি সিলেক্টর টগল করলে অনেক স্টাইল, লেআউট ও পেইন্ট ট্রিগার হতে পারে।

মূল থ্রেডের বাইরে:

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

ওয়েবে রেন্ডারিংয়ের বিভিন্ন এলিমেন্টের স্কেচ

RenderingNG

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

  • একটি 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 সহ রেসপন্সিভনেস অত্যন্ত ভিজ্যুয়াল...তবে গড় পৃষ্ঠা টেস্ট করার সময় এটি আরও সূক্ষ্ম হয়।

ইন্টার‍্যাকশন দীর্ঘ সময় ধরে চললে, কোন কারণে তা হচ্ছে তা সবসময় বোঝা যায় না। এটি কি:

  • ইনপুট দিতে দেরি?
  • ইভেন্ট প্রসেস করার সময়সীমা?
  • প্রেজেন্টেশনে দেরি?

আপনি যেকোনও পৃষ্ঠায় রেসপন্সিভনেস পরিমাপ করতে DevTools ব্যবহার করতে পারবেন। অভ্যাস করতে, এই ফ্লো ব্যবহার করে দেখুন:

  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 নিজে থেকে (যা পরবর্তী পেইন্টের আগে রান করার চেষ্টা করবে এবং সাধারণত এখনও একটি ধীর ইন্টার‍্যাকশন তৈরি করবে) করার পরিবর্তে, requestAnimationFrame + setTimeout পরবর্তী পেইন্টের পরে কলব্যাক রান করে 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 কাজের চেয়ে ইনকামিং ইভেন্ট লিসেনারকে অগ্রাধিকার দেয় এবং ইন্টার‍্যাকশন রেসপন্সিভ থাকে।

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

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

এখন yield()-এর সাথে

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

যেমন:

সম্পূর্ণ কোড দেখুন: 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 লুপ শুরু করে যা যা কাজ করা দরকার তা করে এবং একই সাথে পর্যায়ক্রমে মূল থ্রেডটি তৈরি করে যাতে ব্রাউজার নতুন ইন্টার‍্যাকশনের জন্য প্রতিক্রিয়াশীল থাকে।

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

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

১৮. উপসংহার

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

মনে রাখবেন

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

কৌশল

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

আরও জানুন