Modern websites compete for attention with ever-grander visuals. Parallax heroes stretch deep into the viewport. Carousels scroll indefinitely. Background videos auto-play behind signup forms. These patterns read as polished and engaging on a design portfolio. For a slice of your audience, they are a physical trigger.
People living with vestibular disorders experience nausea, dizziness, or migraines when confronted with large areas of continuous motion. To protect them, every major operating system now includes a "Reduce Motion" accessibility setting. Web browsers expose that preference through the prefers-reduced-motion media query. React developers who want to honor it cleanly can reach for the useReducedMotion hook from @reactuses/core. It reads the OS preference into a boolean, updates live if the user changes their mind, and handles server-side rendering without crashing.
چرا راهکارهای دستی از کار میافتند
شما میتوانید خودتان این اولویت را با استفاده از window.matchMedia('(prefers-reduced-motion: reduce)') استعلام بگیرید. بسیاری از توسعهدهندگان این کار را انجام میدهند و بسیاری از آنها با باگهای ظریفی همراه هستند.
اول اینکه، ممکن است بهراحتی فراموش کنید که شنونده (listener) تغییرات را اضافه کنید. استعلام اولیه تنها یک بار هنگام نصب (mount) کامپوننت اجرا میشود. اگر کاربری اپلیکیشن شما را باز کند و سپس به دلیل اینکه یک کاروسل باعث حالت تهوع او شده، گزینه Reduce Motion را در تنظیمات سیستم فعال کند، کامپوننت شما هرگز متوجه این بهروزرسانی نمیشود. شیء media query از addEventListener پشتیبانی میکند، اما اتصال صحیح آن، حذف آن هنگام unmount و پیادهسازی polyfill برای سینتکس قدیمی addListener کدهای تکراری (boilerplate) هستند که احتمال خطاهای کپی-پیست را بالا میبرند.
دوم اینکه، window در طول SSR وجود ندارد. اگر اپلیکیشن شما روی سرور رندر شود، یک فراخوانی سادهی matchMedia باعث خطای reference error شده و رندر را متوقف میکند. در نهایت مجبور میشوید بررسی را در قالب شرطهای typeof window !== 'undefined' قرار دهید، مقدار پیشفرض را حدس بزنید و امیدوار باشید که با hydrate شدن در سمت کلاینت مطابقت داشته باشد.
سوم اینکه، تکرار این منطق در هر کامپوننت متحرک، به بدهی فنی (maintenance debt) تبدیل میشود. یک همکار شنونده را به یک روش مینویسد، دیگری آن را کاملاً نادیده میگیرد و سومی یک مقدار پیشفرض را بهصورت سختافزاری (hard-code) تعریف میکند که حرکت را ترجیح میدهد. یک هوک این آشفتگی را متمرکز میکند.
useReducedMotion هر سه مشکل را حل میکند. این هوک یک مقدار boolean ساده برمیگرداند، در سمت سرور بهطور ایمن عملیات را بیاثر (no-op) میکند و شنونده را بهطور خودکار متصل و پاکسازی میکند.
سه روش برای بهکارگیری آن
پس از اینکه مقدار boolean را دریافت کردید، به استراتژیای برای واکنش نشان دادن به آن نیاز دارید. در اینجا سه الگو آورده شده است که از یک کامپوننت واحد تا یک اپلیکیشن کامل را پوشش میدهند.
۱. کنترل کلاسهای CSS برای جلوگیری از کارهای بیهوده
مستقیمترین رویکرد این است که از شروع حلقههای سنگین انیمیشن جاوااسکریپت جلوگیری کنید. تصور کنید یک کامپوننت تصویر پارالاکس دارید که بهطور معمول به رویدادهای اسکرول گوش میدهد و لایهها را در یک حلقه requestAnimationFrame جابهجا میکند. بهجای اجرای آن منطق و سپس سرکوب کردن نتیجه بصری، ابتدا useReducedMotion را بررسی کنید.
اگر هوک مقدار true را برگرداند، کامپوننت را با یک کلاس واریانت (variant) استاتیک رندر کنید و از اجرای useEffect که شنوندههای اسکرول را متصل میکند، صرفنظر کنید. مرورگر کار کمتری انجام میدهد. کاربرانی که حساسیت به حرکت دارند، یک تصویر ثابت میبینند. بقیه کاربران لایههای متحرک را دریافت میکنند. از آنجایی که کد انیمیشن هرگز مقداردهی اولیه نمیشود، در این مسیر در مصرف CPU و باتری نیز صرفهجویی میکنید.
۲. تزریق مستقیم به کتابخانههای انیمیشن
اگر از کتابخانهای مانند Framer Motion استفاده میکنید، این هوک مستقیماً در تعریف propهای شما قرار میگیرد. کامپوننتهای Motion اشیاء پیکربندی را برای variants، transitions و gestures میپذیرند. میتوانید از مقدار boolean حاصل از useReducedMotion استفاده کنید تا بهصورت شرطی duration: 0 را در transitions تنظیم کنید، یا
