被忽略的细节:Amicro use-reduced-motion无障碍动效适配最佳实践
【免费下载链接】Amicro--Micro-transitions-项目地址: https://gitcode.com/gh_mirrors/am/Amicro--Micro-transitions-
Amicro 是一个基于 Motion 的 React 微交互与过渡动效组件库,而 use-reduced-motion 是其中容易被新手忽略的一枚"细节利器":它通过读取系统的prefers-reduced-motion媒体查询,让所有动画自动尊重用户的"减少动态效果"无障碍设置。对普通用户而言,这 20 行的小 Hook 决定了网站是对他们友好,还是让人头晕目眩。
什么是"减少动态效果"?为什么它常被忽略
现代操作系统都内置了一个名为prefers-reduced-motion(减少动态效果)的无障碍开关,位于"辅助功能"或"显示"设置中。开启它后,系统会告知网站:"请少用或不用动画"。
这个设置背后是真实的需求:
- 🌀前庭敏感用户:强烈的视差、缩放、滚动动画可能引发眩晕甚至恶心
- 💡认知障碍用户:动态内容会分散注意力,影响阅读
- ⚡性能优先用户:关掉动画能显著降低低端设备的渲染开销
遗憾的是,很多动效组件库只关心"动得多炫",却从不检查这个开关——这正是 Amicro 选择内置use-reduced-motionHook 的出发点。
读懂 Amicro 的 use-reduced-motion:三个关键设计
整个 Hook 只有一张"小卡片"大的代码,核心逻辑在 registry/hooks/use-reduced-motion.ts 中,它做了三件看似简单却缺一不可的事:
// 1️⃣ 首次渲染时立即读取系统设置 const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)'); setShouldReduceMotion(mediaQuery.matches); // 2️⃣ 监听变化:用户随时可能切换该开关 mediaQuery.addEventListener('change', handleMediaChange); // 3️⃣ 组件卸载时清理监听,避免内存泄漏 return () => { mediaQuery.removeEventListener('change', handleMediaChange); };它最终返回一个布尔值shouldReduceMotion——true表示"用户要求减少动效,请走静态路径"。
这个 Hook 还以 shadcn 注册表条目 registry/hooks/use-reduced-motion.json 的形式发布,并登记在 registry/registry.json 中,意味着它可以像 UI 组件一样被一键添加到任何 React 项目里。
项目里的真实用法:动画让路,内容不缺席
在 Amicro 的 Dither 图表组件中,这个 Hook 的使用方式堪称教科书,值得新手逐帧拆解:
场景一:弹簧动画"跳帧"直达终点
在 DitherBarChart.tsx 中,数字本应通过弹簧物理缓缓滚动到目标值。但代码做了一个优雅的分叉:
const prefersReducedMotion = useReducedMotion(); if (prefersReducedMotion) spring.jump(value); // 瞬间到位 else spring.set(value); // 弹性滚动jump与set只隔一个词,体验却天差地别:动效被关闭,但数据照常显示——这就是无障碍适配的黄金原则。
场景二:Canvas 手绘动画直接呈现最终帧
Canvas 绘图不受 React 控制,需要手动查询设置。ChartCard.tsx 展示了这种写法:
const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches; if (reducedMotion) t = 1; // 过渡进度直接置满,跳过逐帧绘制场景三:同一模式在整个组件家族中复用
从 DitherDonutChart.tsx 到 DitherGrowthChart.tsx,几乎所有数据可视化组件都遵循同一套"检测 → 降级"模式,保证体验一致性。
无障碍动效适配最佳实践清单 📋
结合 Amicro 的实现,给新手整理一份可直接落地的清单:
- 进入页面就先检测:首帧之前就要读取
matches,避免"先动一下、再停下来"的闪烁感 - 监听 change 事件:用户可能在浏览中切换系统设置,静态读取一次是不够的
- 卸载时移除监听器:这是新手最常漏掉的一步,
removeEventListener要写在useEffect的返回值里 - 降级 ≠ 消失:减少动效时,内容和状态必须完整呈现(如上文的
jump直达终值) - Canvas / WebAssembly 等特殊渲染也要覆盖:它们不会自动响应媒体查询,需手动
matchMedia - 把降级路径写得足够简单:通常就是"直接设为最终状态",越简单越不易出 Bug
新手上手:三步为项目加上 reduced motion 适配
🚀第一步:拿到 Hook将 registry/hooks/use-reduced-motion.ts 复制到项目的 hooks 目录,或参考 registry/registry.json 了解 Amicro 注册表的组件分发方式。
🎯第二步:在动效组件中做分叉调用useReducedMotion()拿到布尔值,在渲染逻辑中判断:为true时跳过过渡动画、直接输出最终样式;为false时走完整动画。
🧪第三步:自我验证在系统设置中开启"减少动态效果"(Windows 在辅助功能 → 视觉效果,macOS 在辅助功能 → 显示 → 减弱动态效果),刷新页面确认:页面内容完整、无闪烁、无持续运动。再切换回"全开"状态,确认动画恢复——change监听生效的标志就是无需刷新也能实时响应。
写在最后
动效是界面的语言,而尊重听众的偏好,才是高级的表达。use-reduced-motion用不到 20 行代码,换取的却是对真实用户的体察。把它加入你的组件库,就像给一个热闹的舞会留出一间安静的休息室——大多数人永远不会需要它,但需要的人,会因此记住你。
【免费下载链接】Amicro--Micro-transitions-项目地址: https://gitcode.com/gh_mirrors/am/Amicro--Micro-transitions-
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考