ভূমিকা
আপনি চান যে অ্যানিমেশন, ট্রানজিশন এবং অন্যান্য ছোট UI এফেক্ট করার সময় আপনার ওয়েব অ্যাপটি রেসপন্সিভ এবং স্মুথ হোক। এইসব এফেক্ট যাতে জ্যাঙ্ক-ফ্রি হয় তা নিশ্চিত করার অর্থ হল "নেটিভ" অনুভূতি বা একটি ক্লামজি, আনপলিশড অনুভূতির মধ্যে পার্থক্য।
ব্রাউজারে রেন্ডারিং পারফর্ম্যান্স অপ্টিমাইজেশন কভার করা নিবন্ধের সিরিজের মধ্যে এটি প্রথম। শুরু করার জন্য, আমরা আলোচনা করব কেন স্মুথ অ্যানিমেশন কঠিন এবং এটি অর্জন করার জন্য কী করা দরকার, সেইসাথে কিছু সহজ পেশাদার পদ্ধতি। এইসব আইডিয়ার বেশিরভাগই প্রথমে "Jank Busters"-এ উপস্থাপন করা হয়েছিল, এটি একটি টক শো যেখানে ন্যাট ডুকা এবং আমি এই বছর Google I/O টক শোতে (ভিডিও) বক্তব্য রেখেছিলাম।
V-sync চালু করা হচ্ছে
পিসি (PC) গেমাররা এই শব্দটির সাথে পরিচিত হতে পারেন, তবে এটি ওয়েবে অস্বাভাবিক: v-sync কী?
আপনার ফোনের ডিসপ্লের কথা ভাবুন: এটি নিয়মিত বিরতিতে রিফ্রেশ হয়, সাধারণত (তবে সবসময় নয়!) প্রতি সেকেন্ডে প্রায় ৬০ বার। V-সিঙ্ক (বা ভার্টিক্যাল সিঙ্ক্রোনাইজেশন) বলতে শুধুমাত্র স্ক্রিন রিফ্রেশের মধ্যে নতুন ফ্রেম তৈরি করার পদ্ধতিকে বোঝায়। আপনি এটিকে স্ক্রিন বাফারে ডেটা লেখার প্রসেস এবং ডিসপ্লেতে দেখানোর জন্য অপারেটিং সিস্টেমের সেই ডেটা পড়ার মধ্যে একটি রেস কন্ডিশন হিসেবে ভাবতে পারেন। আমরা চাই যে বাফার করা ফ্রেম কন্টেন্ট যেন এই রিফ্রেশের মাঝে পরিবর্তন হয়, রিফ্রেশ চলাকালীন নয়; অন্যথায় মনিটর একটি ফ্রেমের অর্ধেক এবং অন্যটির অর্ধেক দেখাবে, যার ফলে "টিয়ারিং" হবে।
স্মুথ অ্যানিমেশন পেতে, স্ক্রিন রিফ্রেশ হওয়ার সময় প্রতিবার নতুন ফ্রেম রেডি থাকতে হবে। এর দুটি বড় প্রভাব আছে: ফ্রেম টাইমিং (অর্থাৎ, ফ্রেম কখন রেডি করতে হবে) এবং ফ্রেম বাজেট (অর্থাৎ, ফ্রেম তৈরি করতে ব্রাউজারের কত সময় লাগবে)। একটি ফ্রেম সম্পূর্ণ করার জন্য আপনার কাছে স্ক্রিন রিফ্রেশ করার মধ্যবর্তী সময়টুকুই থাকে (৬০ হার্টজ স্ক্রিনে ~১৬ মিলি সেকেন্ড) এবং স্ক্রিনে শেষ ফ্রেমটি দেখানোর সাথে সাথেই আপনি পরবর্তী ফ্রেম তৈরি করা শুরু করতে চান।
টাইমিংই হল সবকিছু: requestAnimationFrame
অনেক ওয়েব ডেভেলপার অ্যানিমেশন তৈরি করতে প্রতি ১৬ মিলিসেকেন্ডে setInterval বা setTimeout ব্যবহার করেন। এটি বিভিন্ন কারণে সমস্যাজনক (এবং আমরা এক মিনিটের মধ্যে আরও আলোচনা করব), তবে বিশেষ উদ্বেগের বিষয় হল:
- JavaScript থেকে টাইমার রেজোলিউশন শুধুমাত্র কয়েক মিলিসেকেন্ডের অর্ডারে থাকে
- বিভিন্ন ডিভাইসের রিফ্রেশ রেট আলাদা হয়
উপরে উল্লেখ করা ফ্রেম টাইমিং সংক্রান্ত সমস্যাটি মনে করুন: পরবর্তী স্ক্রিন রিফ্রেশ হওয়ার আগে আপনাকে একটি সম্পূর্ণ অ্যানিমেশন ফ্রেম রেডি করতে হবে, যা যেকোনও JavaScript, DOM ম্যানিপুলেশন, লেআউট, পেইন্টিং ইত্যাদির মাধ্যমে সম্পূর্ণ করা হয়েছে। টাইমার রেজোলিউশন কম হলে, পরবর্তী স্ক্রিন রিফ্রেশ হওয়ার আগে অ্যানিমেশন ফ্রেম সম্পূর্ণ করা কঠিন হতে পারে, তবে স্ক্রিন রিফ্রেশ রেটের তারতম্যের কারণে নির্দিষ্ট টাইমার থাকলে এটি করা অসম্ভব। টাইমার ইন্টারভ্যাল যাই হোক না কেন, আপনি ধীরে ধীরে ফ্রেমের টাইমিং উইন্ডো থেকে সরে যাবেন এবং শেষ পর্যন্ত একটি ফ্রেম ড্রপ করবেন। এমনকি টাইমার মিলিসেকেন্ডের নির্ভুলতার সাথে ফায়ার করলেও এটি ঘটবে, যা এটি করবে না (যেহেতু ডেভেলপাররা আবিষ্কার করেছেন) -- মেশিনটি ব্যাটারিতে চলছে নাকি প্লাগ ইন করা আছে তার উপর নির্ভর করে টাইমার রেজোলিউশন পরিবর্তিত হয়, ব্যাকগ্রাউন্ড ট্যাব হগিং রিসোর্স ইত্যাদির দ্বারা প্রভাবিত হতে পারে। এমনকি এটি বিরল হলেও (ধরা যাক, প্রতি ১৬টি ফ্রেম কারণ আপনি এক মিলিসেকেন্ডের জন্য বন্ধ ছিলেন) আপনি লক্ষ্য করবেন: আপনি এক সেকেন্ডে বেশ কয়েকটি ফ্রেম ড্রপ করবেন। এছাড়াও, আপনি এমন ফ্রেম তৈরি করার কাজ করবেন যা কখনই দেখানো হয় না, যার ফলে পাওয়ার এবং সিপিইউ টাইম নষ্ট হয় যা আপনি আপনার অ্যাপ্লিকেশনে অন্যান্য কাজ করার জন্য ব্যয় করতে পারেন।
বিভিন্ন ডিসপ্লের রিফ্রেশ রেট আলাদা হয়: 60Hz সাধারণ, তবে কিছু ফোনের 59Hz, কিছু ল্যাপটপ কম-পাওয়ার মোডে 50Hz-এ নেমে যায়, কিছু ডেস্কটপ মনিটর 70Hz হয়।
রেন্ডারিং পারফর্ম্যান্স নিয়ে আলোচনা করার সময় আমরা ফ্রেম প্রতি সেকেন্ড (FPS)-এর উপর ফোকাস করি, তবে ভ্যারিয়েন্স আরও বড় সমস্যা হতে পারে। অ্যানিমেশনের সময় ঠিক না থাকলে, অ্যানিমেশনে যে ছোট ছোট, অনিয়মিত বাধা তৈরি হয় তা আমাদের চোখে পড়ে।
সঠিক সময়ে অ্যানিমেশন ফ্রেম পাওয়ার উপায় হল requestAnimationFrame। আপনি এই API ব্যবহার করলে, ব্রাউজারকে অ্যানিমেশন ফ্রেমের জন্য অনুরোধ করছেন। ব্রাউজার শীঘ্রই নতুন ফ্রেম তৈরি করতে চলেছে, এমন সময় আপনার কলব্যাক কল করা হয়। রিফ্রেশ রেট যাই হোক না কেন, এটি ঘটে।
requestAnimationFrame-এর অন্যান্য ভালো প্রপার্টিও আছে:
- ব্যাকগ্রাউন্ড ট্যাবে অ্যানিমেশন পজ হয়ে যায়, সিস্টেম রিসোর্স ও ব্যাটারির আয়ু বাঁচায়।
- সিস্টেম যদি স্ক্রিনের রিফ্রেশ রেটে রেন্ডারিং ম্যানেজ করতে না পারে, তাহলে এটি অ্যানিমেশন থ্রটল করতে পারে এবং কম ঘন ঘন কলব্যাক তৈরি করতে পারে (যেমন, ৬০ হার্টজ স্ক্রিনে সেকেন্ডে ৩০ বার)। এটি ফ্রেমরেট অর্ধেক করে দিলেও, অ্যানিমেশনকে সামঞ্জস্যপূর্ণ রাখে -- এবং উপরে যেমন বলা হয়েছে, আমাদের চোখ ফ্রেমরেটের চেয়ে বৈচিত্র্যের সাথে অনেক বেশি মানিয়ে নেয়। প্রতি সেকেন্ডে কয়েকটি ফ্রেম মিস করা ৬০ হার্জের চেয়ে স্থির ৩০ হার্জের পারফর্ম্যান্স অনেক ভালো।
requestAnimationFrame নিয়ে ইতিমধ্যেই অনেক আলোচনা হয়েছে, তাই এই বিষয়ে আরও জানতে ক্রিয়েটিভ JS-এর এই নিবন্ধ-এর মতো নিবন্ধগুলি দেখুন, তবে স্মুথ অ্যানিমেশনের জন্য এটি একটি গুরুত্বপূর্ণ প্রথম ধাপ।
ফ্রেম বাজেট
যেহেতু আমরা চাই যে স্ক্রিন রিফ্রেশ করার সময় নতুন ফ্রেম রেডি থাকুক, তাই রিফ্রেশ করার মধ্যবর্তী সময়েই নতুন ফ্রেম তৈরি করার সব কাজ করতে হয়। ৬০Hz ডিসপ্লেতে, এর অর্থ হল আমাদের কাছে সমস্ত জাভাস্ক্রিপ্ট রান করতে, লেআউট পারফর্ম করতে, পেইন্ট করতে এবং ফ্রেমটি বের করার জন্য ব্রাউজারকে যা করতে হবে তার জন্য প্রায় ১৬ms সময় আছে। এর অর্থ হল, আপনার requestAnimationFrame কলব্যাকের মধ্যে থাকা জাভাস্ক্রিপ্ট রান করতে ১৬ মিলিসেকেন্ডের বেশি সময় নিলে, v-sync-এর জন্য সময়মতো ফ্রেম তৈরি করার কোনও আশা নেই!
১৬ মিলি সেকেন্ড খুব বেশি সময় নয়। ভাগ্যক্রমে, Chrome-এর ডেভেলপার টুল আপনাকে ট্র্যাক করতে সাহায্য করতে পারে যে আপনি requestAnimationFrame কলব্যাকের সময় আপনার ফ্রেম বাজেট শেষ করে ফেলছেন কিনা।
Dev Tools টাইমলাইন খুলে এবং এই অ্যানিমেশনের রেকর্ডিং অ্যাকশনে নিলে দ্রুত দেখা যায় যে অ্যানিমেট করার সময় আমরা বাজেটের অনেক বাইরে চলে গেছি। টাইমলাইনে "ফ্রেম" বিকল্পে পাল্টে নিন এবং দেখুন:
সেইসব requestAnimationFrame (rAF) কলব্যাক >২০০ মিলি সেকেন্ড সময় নিচ্ছে। প্রতি ১৬ মিলিসেকেন্ডে একটি ফ্রেম টিক আউট করার জন্য এটি অনেক বেশি সময়! এইসব দীর্ঘ rAF কলব্যাকের মধ্যে একটি খুললে, ভিতরে কী চলছে তা জানা যায়: এই ক্ষেত্রে, অনেক লেআউট।
পল তার ভিডিওতে রিলেআউটের নির্দিষ্ট কারণ (এটি scrollTop রিড করছে) এবং কীভাবে এটি এড়ানো যায় সে সম্পর্কে আরও বিস্তারিত আলোচনা করেছেন। তবে, এখানে মূল বিষয় হল, আপনি কলব্যাক আরও ভালভাবে বুঝতে পারবেন এবং কেন এত সময় লাগছে তা খতিয়ে দেখতে পারবেন।
১৬ মিলি সেকেন্ডের ফ্রেম টাইম লক্ষ্য করুন। ফ্রেমের মধ্যে থাকা খালি জায়গা হল হেডরুম, যেখানে আপনাকে আরও কাজ করতে হবে (অথবা ব্রাউজারকে ব্যাকগ্রাউন্ডে প্রয়োজনীয় কাজ করতে দিতে হবে)। সেই ফাঁকা জায়গাটি একটি ভালো জিনিস।
জ্যাঙ্কের অন্যান্য সোর্স
জাভাস্ক্রিপ্ট-চালিত অ্যানিমেশন চালানোর চেষ্টা করার সময় সমস্যার সবচেয়ে বড় কারণ হল যে অন্যান্য জিনিস আপনার rAF কলব্যাকের পথে আসতে পারে এবং এমনকি এটি একেবারেই চলতে বাধা দিতে পারে। আপনার rAF কলব্যাক লিন এবং মাত্র কয়েক মিলিসেকেন্ডে রান করলেও, অন্যান্য অ্যাক্টিভিটি (যেমন, এইমাত্র আসা XHR প্রসেস করা, ইনপুট ইভেন্ট হ্যান্ডলার রান করা বা টাইমারের উপর শিডিউল করা আপডেট রান করা) হঠাৎ করে আসতে পারে এবং কোনও ফলাফল ছাড়াই যেকোনও সময় ধরে রান করতে পারে। মোবাইল ডিভাইসে কখনও কখনও এইসব ইভেন্ট প্রসেস করতে কয়েকশো মিলিসেকেন্ড সময় লাগতে পারে, যার ফলে আপনার অ্যানিমেশন সম্পূর্ণভাবে থেমে যাবে। আমরা অ্যানিমেশনের এই ধরনের সমস্যাকে জ্যাঙ্ক বলি।
এইসব পরিস্থিতি এড়ানোর কোনও ম্যাজিক বুলেট নেই, তবে সাফল্য পাওয়ার জন্য নিজেকে সেট-আপ করতে কিছু আর্কিটেকচারাল পেশাদার পদ্ধতি আছে:
- ইনপুট হ্যান্ডলারে অনেক প্রসেসিং করবেন না! অনেক JS করা বা স্ক্রল হ্যান্ডলারের মতো কোনও কিছুর মাধ্যমে পুরো পৃষ্ঠাটি আবার সাজানোর চেষ্টা করা হল খুব সাধারণ কারণে হওয়া ভয়ঙ্কর জ্যাঙ্কিনেস।
- আপনার rAF কলব্যাক বা ওয়েব ওয়ার্কার-এ যতটা সম্ভব প্রসেসিং (পড়ুন: রান করতে অনেক সময় লাগবে এমন যেকোনও কিছু) পুশ করুন।
- আপনি rAF কলব্যাকে কাজ পুশ করলে, সেটি চাঙ্ক করার চেষ্টা করুন যাতে আপনি প্রতিটি ফ্রেমের সামান্য অংশ প্রসেস করেন অথবা কোনও গুরুত্বপূর্ণ অ্যানিমেশন শেষ না হওয়া পর্যন্ত সেটি দেরি করেন -- এইভাবে আপনি ছোট rAF কলব্যাক চালানো এবং মসৃণভাবে অ্যানিমেট করা চালিয়ে যেতে পারবেন।
ইনপুট হ্যান্ডলারের পরিবর্তে কীভাবে প্রসেসিংকে requestAnimationFrame কলব্যাকে পুশ করতে হয় সেই বিষয়ে একটি দুর্দান্ত টিউটোরিয়ালের জন্য পল লুইসের লেখা Leaner, Meaner, Faster Animations with requestAnimationFrame নিবন্ধটি দেখুন।
CSS অ্যানিমেশন
আপনার ইভেন্ট এবং rAF কলব্যাকে লাইটওয়েট JS-এর চেয়ে ভালো আর কী হতে পারে? JS নেই।
আমরা আগেই বলেছি যে rAF কলব্যাক বিঘ্নিত হওয়া এড়ানোর কোনও সহজ উপায় নেই, তবে CSS অ্যানিমেশন ব্যবহার করে আপনি rAF কলব্যাকের প্রয়োজনীয়তা সম্পূর্ণভাবে এড়াতে পারবেন। বিশেষ করে Android-এর জন্য Chrome-এ (এবং অন্যান্য ব্রাউজার একই ধরনের ফিচারে কাজ করছে), CSS অ্যানিমেশনে খুব পছন্দসই প্রপার্টি আছে যা জাভাস্ক্রিপ্ট চললেও ব্রাউজার প্রায়ই চালাতে পারে।
জ্যাঙ্ক সম্পর্কে উপরের বিভাগে একটি অন্তর্নিহিত বিবৃতি রয়েছে: ব্রাউজার একবারে শুধুমাত্র একটি কাজ করতে পারে। এটি পুরোপুরি সত্য নয়, তবে এটি একটি ভালো ওয়ার্কিং অনুমান: যেকোনও সময়ে ব্রাউজার JS রান করতে, লেআউট পারফর্ম করতে বা পেইন্ট করতে পারে, তবে একবারে একটিই করতে পারে। Dev Tools-এর টাইমলাইন ভিউতে এটি যাচাই করা যেতে পারে। এই নিয়মের একটি ব্যতিক্রম হল Android-এর জন্য Chrome-এ CSS অ্যানিমেশন (এবং শীঘ্রই ডেস্কটপ Chrome-এ, যদিও এখনও নয়)।
সম্ভব হলে, CSS অ্যানিমেশন ব্যবহার করলে আপনার অ্যাপ্লিকেশন সহজ হয় এবং জাভাস্ক্রিপ্ট চললেও অ্যানিমেশন মসৃণভাবে চলে।
// see http://paulirish.com/2011/requestanimationframe-for-smart-animating/ for info on rAF polyfills
rAF = window.requestAnimationFrame;
var degrees = 0;
function update(timestamp) {
document.querySelector('#foo').style.webkitTransform = "rotate(" + degrees + "deg)";
console.log('updated to degrees ' + degrees);
degrees = degrees + 1;
rAF(update);
}
rAF(update);
আপনি বোতামে ক্লিক করলে জাভাস্ক্রিপ্ট ১৮০ মিলি সেকেন্ডের জন্য রান করে, যার ফলে জ্যাঙ্ক হয়। কিন্তু, আমরা যদি CSS অ্যানিমেশনের মাধ্যমে সেই অ্যানিমেশনটি চালাই, তাহলে আর জ্যাঙ্ক হয় না।
(মনে রাখবেন, এই লেখাটি লেখার সময়, CSS অ্যানিমেশন শুধুমাত্র Android-এর জন্য Chrome-এ জ্যাঙ্ক-ফ্রি, ডেস্কটপ Chrome-এ নয়।)
/* tools like Modernizr (http://modernizr.com/) can help with CSS polyfills */
#foo {
+animation-duration: 3s;
+animation-timing-function: linear;
+animation-animation-iteration-count: infinite;
+animation-animation-name: rotate;
}
@+keyframes: rotate; {
from {
+transform: rotate(0deg);
}
to {
+transform: rotate(360deg);
}
}
CSS অ্যানিমেশন ব্যবহার করার ব্যাপারে আরও তথ্যের জন্য, MDN-এ এই নিবন্ধের মতো নিবন্ধ দেখুন।
গুটিয়ে নেওয়া
সংক্ষেপে বলতে গেলে:
- অ্যানিমেট করার সময়, প্রতিটি স্ক্রিন রিফ্রেশের জন্য ফ্রেম তৈরি করা গুরুত্বপূর্ণ। Vsync'd অ্যানিমেশন অ্যাপের অনুভূতিতে একটি বিশাল ইতিবাচক প্রভাব ফেলে।
- Chrome এবং অন্যান্য আধুনিক ব্রাউজারে vsync'd অ্যানিমেশন পাওয়ার সবচেয়ে ভালো উপায় হল CSS অ্যানিমেশন ব্যবহার করা। CSS অ্যানিমেশন থেকে আপনি যখন আরও বেশি নমনীয়তা চান, তখন সেরা টেকনিক হল requestAnimationFrame-ভিত্তিক অ্যানিমেশন।
- rAF অ্যানিমেশন ঠিকভাবে কাজ করতে, অন্যান্য ইভেন্ট হ্যান্ডলার যাতে rAF কলব্যাক রানিংয়ের পথে বাধা না হয়ে দাঁড়ায় তা নিশ্চিত করুন এবং rAF কলব্যাক ছোট রাখুন (<১৫ms)।
সবশেষে, vsync'd অ্যানিমেশন শুধু সাধারণ UI অ্যানিমেশনের ক্ষেত্রেই প্রযোজ্য নয় -- এটি Canvas2D অ্যানিমেশন, WebGL অ্যানিমেশন এবং এমনকি স্ট্যাটিক পৃষ্ঠায় স্ক্রল করার ক্ষেত্রেও প্রযোজ্য। এই সিরিজের পরবর্তী নিবন্ধে, আমরা এই ধারণাগুলি মাথায় রেখে স্ক্রোলিং পারফর্ম্যান্স সম্পর্কে বিস্তারিত আলোচনা করব।
অ্যানিমেট করার মজা নিন!