আধুনিক ওয়েবসাইটগুলো আরও জাঁকজমকপূর্ণ ভিজ্যুয়াল দিয়ে মানুষের মনোযোগ আকর্ষণের চেষ্টা করে। প্যারালাক্স হিরো (Parallax heroes) ভিউপোর্টের গভীরে বিস্তৃত থাকে। ক্যারোসেল (Carousels) অবিরাম স্ক্রল করতে থাকে। সাইনআপ ফর্মের পেছনে ব্যাকগ্রাউন্ড ভিডিও স্বয়ংক্রিয়ভাবে চলতে থাকে। ডিজাইনের পোর্টফোলিওতে এই প্যাটার্নগুলো মার্জিত এবং আকর্ষণীয় মনে হয়। কিন্তু আপনার অডিয়েন্সের একটি অংশের জন্য এগুলো শারীরিক অস্বস্তির কারণ হতে পারে।
যারা ওয়েস্টিবুলার ডিসঅর্ডার (vestibular disorders) নিয়ে বেঁচে আছেন, তারা ক্রমাগত চলমান বড় কোনো দৃশ্য দেখলে বমি ভাব, মাথা ঘোরা বা মাইগ্রেনের সমস্যায় পড়েন। তাদের সুরক্ষার জন্য, প্রতিটি প্রধান অপারেটিং সিস্টেমে এখন একটি "Reduce Motion" অ্যাক্সেসিবিলিটি সেটিংস রয়েছে। ওয়েব ব্রাউজারগুলো prefers-reduced-motion মিডিয়া কুয়েরির মাধ্যমে সেই পছন্দটি প্রকাশ করে। যেসব React ডেভেলপার এটি সঠিকভাবে অনুসরণ করতে চান, তারা @reactuses/core থেকে useReducedMotion হুকটি ব্যবহার করতে পারেন। এটি অপারেটিং সিস্টেমের পছন্দটিকে একটি বুলিয়ান (boolean) হিসেবে পড়ে নেয়, ব্যবহারকারী পরিবর্তন করলে তা তাৎক্ষণিকভাবে আপডেট করে এবং সার্ভার-সাইড রেন্ডারিংয়ের সময় ক্র্যাশ না করে কাজ করতে পারে।
কেন হাতে তৈরি সমাধানগুলো কাজ করে না
আপনি নিজেই window.matchMedia('(prefers-reduced-motion: reduce)') ব্যবহার করে এই পছন্দটি যাচাই করতে পারেন। অনেক ডেভেলপার এটি করেন, এবং এর ফলে অনেক সময় সূক্ষ্ম বাগ (bugs) তৈরি হয়।
প্রথমত, আপনি সহজেই 'change listener' ভুলে যেতে পারেন। কম্পোনেন্ট মাউন্ট হওয়ার সময় প্রাথমিক কুয়েরিটি একবার চলে। যদি একজন ব্যবহারকারী আপনার অ্যাপটি খোলেন এবং কোনো ক্যারোসেল দেখে অসুস্থ বোধ করার কারণে সিস্টেম সেটিংস থেকে 'Reduce Motion' অন করেন, তবে আপনার কম্পোনেন্ট সেই আপডেটটি জানতে পারবে না। মিডিয়া কুয়েরি অবজেক্ট addEventListener সাপোর্ট করে, কিন্তু এটিকে সঠিকভাবে কানেক্ট করা, আনমাউন্ট করার সময় রিমুভ করা এবং পুরনো addListener সিনট্যাক্সের জন্য পলিফিল (polyfill) করা এমন একটি জটিল কাজ যা কপি-পেস্ট ভুলের সম্ভাবনা বাড়িয়ে দেয়।
দ্বিতীয়ত, SSR-এর সময় window বিদ্যমান থাকে না। যদি আপনার অ্যাপ সার্ভারে রেন্ডার হয়, তবে একটি সাধারণ matchMedia কল 'reference error' তৈরি করে রেন্ডারিং বন্ধ করে দেয়। ফলে আপনাকে typeof window !== 'undefined' গার্ড দিয়ে চেকটি র্যাপ করতে হয়, ডিফল্ট ভ্যালু নিয়ে অনুমান করতে হয় এবং ক্লায়েন্ট হাইড্রেশন (client hydrate) যেন মিলে যায় সেই আশা করতে হয়।
তৃতীয়ত, প্রতিটি অ্যানিমেটেড কম্পোনেন্টে এই লজিক বারবার ব্যবহার করা মেইনটেন্যান্সের বোঝা (maintenance debt) বাড়িয়ে দেয়। একজন টিমমেট লিসেনারটি একভাবে লিখবেন, অন্যজন এটি পুরোপুরি বাদ দেবেন, আর তৃতীয়জন মোশন বা গতির সুবিধা দেয় এমন একটি ডিফল্ট ভ্যালু হার্ড-কোড করবেন। একটি হুক এই বিশৃঙ্খলাকে কেন্দ্রীভূত করে।
useReducedMotion এই তিনটি সমস্যারই সমাধান দেয়। এটি একটি সাধারণ বুলিয়ান রিটার্ন করে। এটি সার্ভারে নিরাপদে 'no-op' হিসেবে কাজ করে। এটি স্বয়ংক্রিয়ভাবে লিসেনার যুক্ত করে এবং ক্লিনআপ করে।
এটি কার্যকর করার তিনটি উপায়
একবার যখন আপনি বুলিয়ানটি পেয়ে যাবেন, তখন এটি ব্যবহার করার জন্য একটি কৌশল প্রয়োজন। এখানে তিনটি প্যাটার্ন দেওয়া হলো যা একটি একক কম্পোনেন্ট থেকে শুরু করে পুরো অ্যাপ্লিকেশনে ব্যবহার করা সম্ভব।
১. অপ্রয়োজনীয় কাজ এড়াতে CSS ক্লাস নিয়ন্ত্রণ করুন
সবচেয়ে সরাসরি পদ্ধতি হলো ভারী JavaScript অ্যানিমেশন লুপ শুরু হওয়া থেকে বিরত রাখা। কল্পনা করুন আপনার একটি প্যারালাক্স ইমেজ কম্পোনেন্ট আছে যা সাধারণত স্ক্রল ইভেন্টগুলো শোনে এবং একটি requestAnimationFrame লুপের ভেতরে লেয়ারগুলোকে মুভ করে। সেই লজিকটি চালিয়ে তারপর ভিজ্যুয়াল ফলাফলটি চেপে ধরার পরিবর্তে, প্রথমে useReducedMotion চেক করুন।
যদি হুকটি true রিটার্ন করে, তবে একটি স্ট্যাটিক ভ্যারিয়েন্ট ক্লাস দিয়ে কম্পোনেন্টটি রেন্ডার করুন এবং স্ক্রল লিসেনার বাইন্ড করার useEffect স্কিপ করুন। এতে ব্রাউজারকে কম কাজ করতে হয়। যাদের মোশন সেনসিটিভিটি আছে তারা একটি স্থির ছবি দেখতে পাবেন। বাকি সবাই মুভিং লেয়ার দেখতে পাবেন। যেহেতু অ্যানিমেশন কোডটি কখনোই ইনিশিয়ালাইজ হয় না, তাই আপনি এর মাধ্যমে CPU এবং ব্যাটারি সাশ্রয় করতে পারেন।
২. সরাসরি অ্যানিমেশন লাইব্রেরিতে ব্যবহার করুন
আপনি যদি Framer Motion-এর মতো কোনো লাইব্রেরি ব্যবহার করেন, তবে এই হুকটি সরাসরি আপনার প্রপ (prop) ডেফিনিশনে যুক্ত করা যায়। মোশন কম্পোনেন্টগুলো ভ্যারিয়েন্ট, ট্রানজিশন এবং জেসচারের জন্য কনফিগারেশন অবজেক্ট গ্রহণ করে। আপনি useReducedMotion থেকে পাওয়া বুলিয়ানটি ব্যবহার করে ট্রানজিশনে শর্তসাপেক্ষে duration: 0 সেট করতে পারেন, অথবা
