From 3aed93711c0118ab68fa09f4d3ac2635cb3344b8 Mon Sep 17 00:00:00 2001 From: Echo Date: Wed, 11 Jun 2025 18:51:55 +0200 Subject: [PATCH] Make React Spring respect animation preferences (#35018) --- app/javascript/mastodon/components/poll.tsx | 2 -- .../mastodon/features/alt_text_modal/index.tsx | 4 ++-- app/javascript/mastodon/features/audio/index.tsx | 10 +--------- .../features/compose/components/upload_progress.tsx | 3 +-- .../getting_started/components/announcements.jsx | 1 - .../mastodon/features/ui/components/upload_area.tsx | 4 ---- app/javascript/mastodon/features/video/index.tsx | 10 +--------- app/javascript/mastodon/main.tsx | 10 +++++++++- 8 files changed, 14 insertions(+), 30 deletions(-) diff --git a/app/javascript/mastodon/components/poll.tsx b/app/javascript/mastodon/components/poll.tsx index 6692f674d4..e9b3b2b672 100644 --- a/app/javascript/mastodon/components/poll.tsx +++ b/app/javascript/mastodon/components/poll.tsx @@ -14,7 +14,6 @@ import { fetchPoll, vote } from 'mastodon/actions/polls'; import { Icon } from 'mastodon/components/icon'; import emojify from 'mastodon/features/emoji/emoji'; import { useIdentity } from 'mastodon/identity_context'; -import { reduceMotion } from 'mastodon/initial_state'; import { makeEmojiMap } from 'mastodon/models/custom_emoji'; import type * as Model from 'mastodon/models/poll'; import type { Status } from 'mastodon/models/status'; @@ -265,7 +264,6 @@ const PollOption: React.FC = (props) => { to: { width: `${percent}%`, }, - immediate: reduceMotion, }); return ( diff --git a/app/javascript/mastodon/features/alt_text_modal/index.tsx b/app/javascript/mastodon/features/alt_text_modal/index.tsx index f24a3b6f70..8a91e14e31 100644 --- a/app/javascript/mastodon/features/alt_text_modal/index.tsx +++ b/app/javascript/mastodon/features/alt_text_modal/index.tsx @@ -27,7 +27,7 @@ import { Audio } from 'mastodon/features/audio'; import { CharacterCounter } from 'mastodon/features/compose/components/character_counter'; import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components'; import { Video, getPointerPosition } from 'mastodon/features/video'; -import { me, reduceMotion } from 'mastodon/initial_state'; +import { me } from 'mastodon/initial_state'; import type { MediaAttachment } from 'mastodon/models/media_attachment'; import { useAppSelector, useAppDispatch } from 'mastodon/store'; import { assetHost } from 'mastodon/utils/config'; @@ -110,7 +110,7 @@ const Preview: React.FC<{ left: `${x * 100}%`, top: `${y * 100}%`, }, - immediate: reduceMotion || draggingRef.current, + immediate: draggingRef.current, }); const media = useAppSelector((state) => ( diff --git a/app/javascript/mastodon/features/audio/index.tsx b/app/javascript/mastodon/features/audio/index.tsx index dd6fef07d9..a6a131c0d4 100644 --- a/app/javascript/mastodon/features/audio/index.tsx +++ b/app/javascript/mastodon/features/audio/index.tsx @@ -19,11 +19,7 @@ import { SpoilerButton } from 'mastodon/components/spoiler_button'; import { formatTime, getPointerPosition } from 'mastodon/features/video'; import { useAudioContext } from 'mastodon/hooks/useAudioContext'; import { useAudioVisualizer } from 'mastodon/hooks/useAudioVisualizer'; -import { - displayMedia, - useBlurhash, - reduceMotion, -} from 'mastodon/initial_state'; +import { displayMedia, useBlurhash } from 'mastodon/initial_state'; import { playerSettings } from 'mastodon/settings'; const messages = defineMessages({ @@ -163,7 +159,6 @@ export const Audio: React.FC<{ } void spring.start({ volume: `${audioRef.current.volume * 100}%`, - immediate: reduceMotion, }); } }, @@ -217,7 +212,6 @@ export const Audio: React.FC<{ if (audioRef.current && audioRef.current.duration > 0) { void spring.start({ progress: `${(audioRef.current.currentTime / audioRef.current.duration) * 100}%`, - immediate: reduceMotion, config: config.stiff, }); } @@ -263,7 +257,6 @@ export const Audio: React.FC<{ if (lastTimeRange > -1) { void spring.start({ buffer: `${Math.ceil(audioRef.current.buffered.end(lastTimeRange) / audioRef.current.duration) * 100}%`, - immediate: reduceMotion, }); } }, [spring]); @@ -278,7 +271,6 @@ export const Audio: React.FC<{ void spring.start({ volume: `${audioRef.current.muted ? 0 : audioRef.current.volume * 100}%`, - immediate: reduceMotion, }); persistVolume(audioRef.current.volume, audioRef.current.muted); diff --git a/app/javascript/mastodon/features/compose/components/upload_progress.tsx b/app/javascript/mastodon/features/compose/components/upload_progress.tsx index be15917784..e12f58d17f 100644 --- a/app/javascript/mastodon/features/compose/components/upload_progress.tsx +++ b/app/javascript/mastodon/features/compose/components/upload_progress.tsx @@ -4,7 +4,6 @@ import { animated, useSpring } from '@react-spring/web'; import UploadFileIcon from '@/material-icons/400-24px/upload_file.svg?react'; import { Icon } from 'mastodon/components/icon'; -import { reduceMotion } from 'mastodon/initial_state'; interface UploadProgressProps { active: boolean; @@ -20,7 +19,7 @@ export const UploadProgress: React.FC = ({ const styles = useSpring({ from: { width: '0%' }, to: { width: `${progress}%` }, - immediate: reduceMotion || !active, // If this is not active, update the UI immediately. + immediate: !active, // If this is not active, update the UI immediately. }); if (!active) { return null; diff --git a/app/javascript/mastodon/features/getting_started/components/announcements.jsx b/app/javascript/mastodon/features/getting_started/components/announcements.jsx index f5f593860f..87d7e2a3be 100644 --- a/app/javascript/mastodon/features/getting_started/components/announcements.jsx +++ b/app/javascript/mastodon/features/getting_started/components/announcements.jsx @@ -270,7 +270,6 @@ const ReactionsBar = ({ leave: { scale: 0, }, - immediate: reduceMotion, keys: visibleReactions.map(x => x.get('name')), }); diff --git a/app/javascript/mastodon/features/ui/components/upload_area.tsx b/app/javascript/mastodon/features/ui/components/upload_area.tsx index 87ac090e7e..1919a30df3 100644 --- a/app/javascript/mastodon/features/ui/components/upload_area.tsx +++ b/app/javascript/mastodon/features/ui/components/upload_area.tsx @@ -4,8 +4,6 @@ import { FormattedMessage } from 'react-intl'; import { animated, config, useSpring } from '@react-spring/web'; -import { reduceMotion } from 'mastodon/initial_state'; - interface UploadAreaProps { active?: boolean; onClose: () => void; @@ -39,7 +37,6 @@ export const UploadArea: React.FC = ({ active, onClose }) => { opacity: 1, }, reverse: !active, - immediate: reduceMotion, }); const backgroundAnimStyles = useSpring({ from: { @@ -50,7 +47,6 @@ export const UploadArea: React.FC = ({ active, onClose }) => { }, reverse: !active, config: config.wobbly, - immediate: reduceMotion, }); return ( diff --git a/app/javascript/mastodon/features/video/index.tsx b/app/javascript/mastodon/features/video/index.tsx index e9c3cdefb6..65f26cedad 100644 --- a/app/javascript/mastodon/features/video/index.tsx +++ b/app/javascript/mastodon/features/video/index.tsx @@ -27,11 +27,7 @@ import { attachFullscreenListener, detachFullscreenListener, } from 'mastodon/features/ui/util/fullscreen'; -import { - displayMedia, - useBlurhash, - reduceMotion, -} from 'mastodon/initial_state'; +import { displayMedia, useBlurhash } from 'mastodon/initial_state'; import { playerSettings } from 'mastodon/settings'; import { HotkeyIndicator } from './components/hotkey_indicator'; @@ -260,7 +256,6 @@ export const Video: React.FC<{ setMuted(videoRef.current.muted); void api.start({ volume: `${videoRef.current.volume * 100}%`, - immediate: reduceMotion, }); } }, @@ -350,7 +345,6 @@ export const Video: React.FC<{ videoRef.current.currentTime / videoRef.current.duration; void api.start({ progress: isNaN(progress) ? '0%' : `${progress * 100}%`, - immediate: reduceMotion, config: config.stiff, }); } @@ -738,7 +732,6 @@ export const Video: React.FC<{ if (lastTimeRange > -1) { void api.start({ buffer: `${Math.ceil(videoRef.current.buffered.end(lastTimeRange) / videoRef.current.duration) * 100}%`, - immediate: reduceMotion, }); } }, [api]); @@ -753,7 +746,6 @@ export const Video: React.FC<{ void api.start({ volume: `${videoRef.current.muted ? 0 : videoRef.current.volume * 100}%`, - immediate: reduceMotion, }); persistVolume(videoRef.current.volume, videoRef.current.muted); diff --git a/app/javascript/mastodon/main.tsx b/app/javascript/mastodon/main.tsx index a9696ac50e..70e6391bee 100644 --- a/app/javascript/mastodon/main.tsx +++ b/app/javascript/mastodon/main.tsx @@ -1,8 +1,10 @@ import { createRoot } from 'react-dom/client'; +import { Globals } from '@react-spring/web'; + import { setupBrowserNotifications } from 'mastodon/actions/notifications'; import Mastodon from 'mastodon/containers/mastodon'; -import { me } from 'mastodon/initial_state'; +import { me, reduceMotion } from 'mastodon/initial_state'; import * as perf from 'mastodon/performance'; import ready from 'mastodon/ready'; import { store } from 'mastodon/store'; @@ -21,6 +23,12 @@ function main() { mountNode.getAttribute('data-props') ?? '{}', ) as Record; + if (reduceMotion) { + Globals.assign({ + skipAnimation: true, + }); + } + const root = createRoot(mountNode); root.render(); store.dispatch(setupBrowserNotifications());