นากแม่น้ำต้นฉบับกำลังปรับสวิตช์ให้ลูกศรเคลื่อนไหวยาวกลายเป็นการเปลี่ยนสถานะสั้นและนิ่งบนโต๊ะสีมิ้นต์

React Native reduced motion: เคารพการตั้งค่าลดการเคลื่อนไหวตั้งแต่เฟรมแรก

พี่โชว์Engineering

แอนิเมชันที่ช่วยบอกว่าการ์ดเลื่อนไปไหน อาจทำให้ผู้ใช้บางคนเวียนหัวหรือเสียสมาธิครับ React Native เปิดค่าระบบผ่าน AccessibilityInfo.isReduceMotionEnabled() และ event reduceMotionChanged เพื่อให้แอปเปลี่ยน motion โดยเคารพการตั้งค่าของผู้ใช้

WHY: ลด motion ไม่ใช่ลบ feedback

ผู้ใช้ไม่ได้ขอให้หน้าจอหยุดสื่อสาร เขาขอให้หลีกเลี่ยงการเคลื่อนที่ที่กระตุ้นอาการ เช่น zoom ใหญ่, parallax หรือวัตถุวิ่งข้ามจอ เราจึงควรแทนด้วย fade สั้น, การเปลี่ยนสี หรือ state แบบทันที โดยยังบอกว่าการกดสำเร็จหรือหน้าเปลี่ยนแล้ว

เหมือนเปลี่ยนบันไดเลื่อนเป็นลิฟต์ครับ ปลายทางเดิม แต่ลดการเคลื่อนไหวที่ผู้โดยสารต้องรับรู้ระหว่างทาง

HOW: อ่านค่าเริ่มต้นและติดตามการเปลี่ยน

ตัวอย่างนี้เป็น fragment สำหรับแอป React Native ต้องตรวจต่อใน simulator/อุปกรณ์จริง เพราะ Scheduled Task ไม่มี mobile runtime

import { useEffect, useState } from "react";
import { AccessibilityInfo } from "react-native";

export function useReducedMotion(): boolean {
  const [reduced, setReduced] = useState(false);

  useEffect(() => {
    let mounted = true;
    AccessibilityInfo.isReduceMotionEnabled().then((value) => {
      if (mounted) setReduced(value);
    });

    const subscription = AccessibilityInfo.addEventListener(
      "reduceMotionChanged",
      setReduced,
    );

    return () => {
      mounted = false;
      subscription.remove();
    };
  }, []);

  return reduced;
}

ค่าเริ่ม false อาจทำให้เฟรมแรกเล่น motion ก่อน Promise ตอบ ถ้าหน้าจอมี transition ใหญ่ ให้เก็บสถานะ unknown | reduce | full แล้วรอค่าก่อนเริ่ม หรือให้ค่าเริ่มต้นเป็น motion ต่ำสำหรับเส้นทางแรก ความเร็วไม่ได้สำคัญกว่าการเคารพ preference

ออกแบบ fallback ให้ตรงสาเหตุ

Motion ปกติเมื่อ reduce motion
slide เต็มหน้าจอcross-fade สั้นหรือเปลี่ยนทันที
parallax ตาม scrollตำแหน่งนิ่ง
scale เด้งหลายรอบเปลี่ยนสี/เส้นขอบหนึ่งครั้ง
progress spinnerprogress text หรือแถบที่ไม่หมุนเมื่อทำได้

ทดสอบทั้งตอนเปิดค่าก่อนเข้าแอปและเปลี่ยนค่าขณะแอปรัน ตรวจ cleanup listener เมื่อ component unmount และอย่าซ่อนเนื้อหาจนกว่า animation จะจบ เพราะผู้ใช้ screen reader อาจเจอ state ที่ภาพกับ accessibility tree ไม่ตรงกัน

Recap

  • อ่าน preference จากระบบ ไม่สร้าง toggle แยกโดยไม่จำเป็น
  • ติดตาม reduceMotionChanged และถอด listener ตอน unmount
  • ลดการเดินทาง/zoom แต่คง feedback ของสถานะ
  • ทดสอบบนอุปกรณ์จริงทั้ง iOS และ Android