![【Bug已解决】[Windows] Pet overlay ignores “Reduce motion: Off“ when OS animation effects are disabled...](http://pic.xiahunao.cn/yaotu/【Bug已解决】[Windows] Pet overlay ignores “Reduce motion: Off“ when OS animation effects are disabled...)
【Bug已解决】[Windows] Pet overlay ignores Reduce motion: Off when OS animation effects are disabled 解决方案原始报错线索[Windows] Pet overlay ignores Reduce motion: Off when OS animation effects are disabledWindows 上一个「桌面宠物」覆盖层在系统关掉动画效果后仍自顾自地播放动画无视用户的无障碍偏好。一、现象长什么样用户为了舒适或性能在系统里关掉了动画Windows「设置 → 辅助功能 → 视觉效果 → 动画效果」关闭macOS「辅助功能 → 显示 → 减少动态效果」开启浏览器/系统prefers-reduced-motion: reduce。 但桌面宠物覆盖层依然一直飘动、弹跳、做入场动画完全没理会系统开关像是「不知道有这个设置」用户关掉系统动画后其他地方都静止了唯独这个浮层还在动对动效敏感的用户因此眩晕或分心体验受损。 根因是应用自己写死了动画从不查询系统的无障碍偏好。二、背景Reduce Motion 是什么2.1 无障碍维度「减少动态效果」是操作系统/浏览器提供的一项无障碍Accessibility偏好表达「用户希望尽量少的移动、闪烁、视差、缩放动画」。它服务于前庭功能敏感 / 易眩晕人群注意力缺陷用户低性能设备或省电场景单纯偏好简洁界面的用户。2.2 三个层级的来源层级来源读取方式WebCSSmedia (prefers-reduced-motion)/ JSmatchMedia标准Windows系统「动画效果」开关SPI_GETCLIENTAREAANIMATIONSystemParametersInfomacOSAXIsReduceMotionEnabled/NSWorkspaceCocoa API应用若想「尊重用户」必须在每一个它运行的层级都去读对应的设置而不是只在一个层级读、或干脆不读。三、为什么覆盖层无视设置根因3.1 动画写死无偏好查询浮层代码里animate()永远执行从没调用过「系统是否要减少动效」的判断。3.2 只读了部分层级比如 Web 版读了matchMedia但桌面原生版Electron 的主进程 / 原生窗口没读 Windows 的SystemParametersInfo于是原生浮层照动不误。3.3 设置变更不监听用户中途改了开关应用只在启动时读了一次没监听变更导致「改了也不生效」。3.4 降级不到位即便读到「要减少」也只是「关掉部分动画」仍保留了会诱发眩晕的视差/缩放降级不彻底。四、最小可运行复现无视偏好的动画下面用 Web 标准 API 演示「不读偏好」与「读偏好」的区别在浏览器控制台 / 任意 HTML 里运行!-- 错误动画永远播放无视系统设置 -- style .pet { animation: bounce 1s infinite; } keyframes bounce { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-20px)} } /style div classpet/div !-- 正确尊重 prefers-reduced-motion -- style .pet2 { animation: bounce 1s infinite; } media (prefers-reduced-motion: reduce) { .pet2 { animation: none; } /* 减少动效时完全静止 */ } /style div classpet2/div仅当页面/应用真正查询了prefers-reduced-motion第二段才会静止。第一段是 Bug 所在。五、解决方案一Web 层用 matchMedia 查询与监听function applyMotionPreference() { const mq window.matchMedia((prefers-reduced-motion: reduce)); const reduce mq.matches; document.body.classList.toggle(reduce-motion, reduce); return reduce; } // 启动时应用一次 applyMotionPreference(); // 监听系统设置变更用户中途改开关也能即时生效 window.matchMedia((prefers-reduced-motion: reduce)) .addEventListener(change, (e) { document.body.classList.toggle(reduce-motion, e.matches); console.log(减少动效:, e.matches); });配合 CSS.reduce-motion .pet { animation: none; transition: none; } .reduce-motion .parallax { transform: none; } /* 视差也关掉 */监听change是关键——用户中途切换开关浮层立刻静止解决根因 3.3。六、解决方案二Windows 原生层读系统设置ctypes桌面原生如 Electron 主进程、Python 工具要用 Windows API 读「动画效果」开关import sys import ctypes def windows_reduce_motion() - bool: 读取 Windows『动画效果』设置。返回 True 表示应减少动效。 if sys.platform ! win32: return False try: # SPI_GETCLIENTAREAANIMATION 0x1042 # 该值非 0 表示『开启动画』故 reduce (val 0) val ctypes.c_int(0) ok ctypes.windll.user32.SystemParametersInfoW( 0x1042, 0, ctypes.byref(val), 0) if not ok: return False return val.value 0 except Exception: return False if __name__ __main__: if windows_reduce_motion(): print(系统要求减少动效 - 关闭宠物动画) else: print(系统允许动画 - 可播放)SystemParametersInfoW(0x1042, ...)是 Windows 真实 API读取「客户端区动画」开关。返回 0 即「动画关闭 应减少动效」。七、解决方案三统一偏好门面跨层级合并应用可能同时跑在 Web 层和原生层应把各来源合并成一个「是否减少动效」的统一判断import sys import ctypes def system_reduce_motion() - bool: 跨平台统一读取系统『减少动态效果』偏好。 plat sys.platform if plat win32: try: val ctypes.c_int(0) ctypes.windll.user32.SystemParametersInfoW(0x1042, 0, ctypes.byref(val), 0) return val.value 0 except Exception: return False if plat darwin: # macOS: 通过 AXIsReduceMotionEnabledCocoa此处用命令行探测示意 import subprocess try: out subprocess.run( [defaults, read, com.apple.AccessibilitySupport, ReduceMotionEnabled], capture_outputTrue, textTrue, timeout3).stdout.strip() return out 1 except Exception: return False # Linux: 常见桌面用 gsettings如 GNOME reduce-animations if plat.startswith(linux): import subprocess try: out subprocess.run( [gsettings, get, org.gnome.desktop.interface, enable-animations], capture_outputTrue, textTrue, timeout3).stdout.strip() return out false except Exception: return False return False if __name__ __main__: print(系统要求减少动效:, system_reduce_motion())统一门面让浮层只需问「system_reduce_motion()吗」而不必关心当前是 Windows / macOS / Linux。八、解决方案四彻底的动效降级读到「要减少」后降级必须彻底——不只关掉animation还要关掉会诱发眩晕的视差、缩放、自动播放/* 减少动效时一切非必要运动都停 */ media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; } .parallax-layer { transform: none !important; } /* 视差关掉 */ .autoplay-video { /* 不自动播放 */ } }要点降级覆盖所有运动类型位移动画、过渡、视差、滚动平滑、自动播放而非只关一类否则仍可能诱发不适。九、排查清单「浮层无视减少动效」按下面排查动画是否被写死有无任何「查询系统偏好」的判断是否读了所有运行层级Web 读matchMedia、Windows 读SystemParametersInfo、macOS/Linux 各有 API是否监听了设置变更只在启动读一次用户中途改开关不生效降级是否彻底视差、缩放、自动播放是否一并关掉是否有统一偏好门面原生层与 Web 层结论应一致是否提供应用内覆盖开关用户想强制开/关动效时能否手动设低性能设备是否也走减少动效以省电可观测是否打印当前 reduce-motion 状态便于测试。十、小结「UI 覆盖层忽略系统减少动态效果」的根因是动画被写死应用从不查询操作系统/浏览器的无障碍偏好或只在一个层级查询、且不监听变更。通用修复Web 层用media (prefers-reduced-motion) JSmatchMedia查询并监听change第五节原生层Windows 用SystemParametersInfoW(0x1042)、macOS/Linux 用各自 API 读取第六节统一门面跨平台合并成system_reduce_motion()浮层只问一个布尔第七节彻底降级关掉动画、过渡、视差、自动播放全部运动类型第八节。 尊重「减少动态效果」不是锦上添花而是无障碍的基本义务——用户的系统级偏好必须成为应用动效的总开关。把偏好查询做成浮层启动与设置变更时的第一道门动效敏感用户才能在一个安静的界面里正常工作。这与第 92 篇终端输入解析、第 96 篇 bracketed-paste 一样都是「应用必须尊重宿主环境的用户设置而非自作主张」这一原则的延伸。