جدید ویب سائٹس پہلے سے کہیں زیادہ شاندار بصری اثرات (visuals) کے ذریعے توجہ حاصل کرنے کی دوڑ میں ہیں۔ Parallax heroes ویو پورٹ (viewport) میں گہرائی تک پھیلے ہوتے ہیں۔ Carousels لامتناہی طور پر اسکرول ہوتے رہتے ہیں۔ سائن اپ فارمز کے پیچھے بیک گراؤنڈ ویڈیوز خود بخود چلتی ہیں۔ ڈیزائن پورٹ فولیو میں یہ پیٹرنز نکھرے ہوئے اور دلکش نظر آتے ہیں۔ لیکن آپ کے سامعین کے ایک حصے کے لیے، یہ ایک جسمانی محرک (physical trigger) کا باعث بنتے ہیں۔

وہ لوگ جو vestibular disorders کے ساتھ رہتے ہیں، مسلسل حرکت والے بڑے حصوں کا سامنا کرنے پر متلی، چکر یا مائیگرین (migraines) محسوس کرتے ہیں۔ ان کی حفاظت کے لیے، اب ہر بڑے آپریٹنگ سسٹم میں "Reduce Motion" کی رسائی (accessibility) سیٹنگ شامل ہے۔ ویب براؤزرز اس ترجیح کو prefers-reduced-motion میڈیا کوئری کے ذریعے ظاہر کرتے ہیں۔ React ڈویلپرز جو اسے بہتر طریقے سے نافذ کرنا چاہتے ہیں، وہ @reactuses/core سے useReducedMotion ہک (hook) کا استعمال کر سکتے ہیں۔ یہ OS کی ترجیح کو ایک boolean میں پڑھتا ہے، اگر صارف اپنی رائے بدل لے تو اسے لائیو اپ ڈیٹ کرتا ہے، اور کریش ہوئے بغیر سرور سائیڈ رینڈرنگ (server-side rendering) کو سنبھالتا ہے۔

ہاتھ سے بنائے گئے حل کیوں ناکام ہو جاتے ہیں

آپ خود window.matchMedia('(prefers-reduced-motion: reduce)') کے ذریعے اس ترجیح کو معلوم کر سکتے ہیں۔ بہت سے ڈویلپرز ایسا ہی کرتے ہیں، اور بہت سے اس کے نتیجے میں معمولی بگ (bugs) پیدا کر دیتے ہیں۔

پہلا یہ کہ، آپ آسانی سے 'change listener' کو بھول سکتے ہیں۔ ابتدائی کوئری تب چلتی ہے جب کمپوننٹ ماؤنٹ (mount) ہوتا ہے۔ اگر کوئی صارف آپ کی ایپ کھولتا ہے، اور پھر سسٹم سیٹنگز میں Reduce Motion کو آن کر دیتا ہے کیونکہ carousel انہیں بیمار کر رہا ہے، تو آپ کا کمپوننٹ اس اپ ڈیٹ کو کبھی نہیں جان پائے گا۔ میڈیا کوئری آبجیکٹ addEventListener کو سپورٹ کرتا ہے، لیکن اسے درست طریقے سے جوڑنا، ان ماؤنٹ (unmount) پر اسے ہٹانا، اور پرانی addListener سنٹیکس کو پولی فل (polyfill) کرنا ایسا بوائلر پلیٹ (boilerplate) کوڈ ہے جو کاپی پیسٹ کی غلطیوں کا باعث بنتا ہے۔

دوسرا یہ کہ، SSR کے دوران window موجود نہیں ہوتا۔ اگر آپ کی ایپ سرور پر رینڈر ہوتی ہے، تو ایک سادہ matchMedia کال ریفرنس ایرر (reference error) پیدا کرتی ہے اور رینڈرنگ کو روک دیتی ہے۔ آپ آخر کار چیک کو typeof window !== 'undefined' گارڈز میں لپیٹ دیتے ہیں، ڈیفالٹ کا اندازہ لگاتے ہیں، اور اس امید پر رہتے ہیں کہ کلائنٹ ہائیڈریشن (client hydration) اس سے مطابقت رکھے گی۔

تیسرا یہ کہ، ہر اینیمیٹڈ کمپوننٹ میں اس لاجک کو دہرانا مینٹیننس ڈیٹ (maintenance debt) بن جاتا ہے۔ ایک ساتھی لسنر کو ایک طریقے سے لکھتا ہے، دوسرا اسے مکمل طور پر چھوڑ دیتا ہے، اور تیسرا ایک ایسا ڈیفالٹ ہارڈ کوڈ کر دیتا ہے جو حرکت (motion) کو ترجیح دیتا ہے۔ ایک ہک اس تمام الجھن کو ایک جگہ مرکوز کر دیتا ہے۔

useReducedMotion ان تینوں مسائل کو حل کرتا ہے۔ یہ ایک سادہ boolean واپس کرتا ہے۔ یہ سرور پر محفوظ طریقے سے 'no-op' کرتا ہے۔ یہ خود بخود لسنر کو جوڑتا اور صاف کرتا ہے۔

اسے استعمال کرنے کے تین طریقے

ایک بار جب آپ کے پاس boolean آ جائے، تو آپ کو اس پر عمل کرنے کے لیے ایک حکمت عملی کی ضرورت ہوتی ہے۔ یہاں تین پیٹرنز ہیں جو ایک سنگل کمپوننٹ سے لے کر پوری ایپلی کیشن تک پھیل سکتے ہیں۔

1. فضول کام سے بچنے کے لیے CSS کلاسز کو کنٹرول کریں

سب سے براہ راست طریقہ یہ ہے کہ بھاری JavaScript اینیمیشن لوپس کو شروع ہونے سے ہی روک دیا جائے۔ تصور کریں کہ آپ کے پاس ایک parallax امیج کمپوننٹ ہے جو عام طور پر اسکرول ایونٹس کو سنتا ہے اور requestAnimationFrame لوپ کے اندر تہوں (layers) کو منتقل کرتا ہے۔ اس لاجک کو چلانے اور پھر بصری نتیجے کو روکنے کے بجائے، پہلے useReducedMotion کو چیک کریں۔

اگر ہک true واپس کرتا ہے، تو کمپوننٹ کو ایک اسٹیٹک ویریئنٹ کلاس (static variant class) کے ساتھ رینڈر کریں اور اس useEffect کو چھوڑ دیں جو اسکرول لسنرز کو جوڑتا ہے۔ براؤزر کم کام کرتا ہے۔ حرکت کے حوالے سے حساس صارفین کو ایک ساکن تصویر نظر آتی ہے۔ باقی سب کو متحرک تہیں نظر آتی ہیں۔ چونکہ اینیمیشن کوڈ کبھی شروع ہی نہیں ہوتا، اس لیے آپ اس دوران CPU اور بیٹری بچاتے ہیں۔

2. اسے براہ راست اینیمیشن لائبریریز میں استعمال کریں

اگر آپ Framer Motion جیسی لائبریری استعمال کرتے ہیں، تو یہ ہک براہ راست آپ کی prop تعریفوں میں شامل ہو جاتا ہے۔ Motion کمپوننٹس ویریئنٹ، ٹرانزیشنز اور جیسچرز (gestures) کے لیے کنفیگریشن آبجیکٹس قبول کرتے ہیں۔ آپ useReducedMotion سے حاصل کردہ boolean کو ٹرانزیشنز پر شرط کے مطابق duration: 0 سیٹ کرنے کے لیے استعمال کر سکتے ہیں، یا