Skip to content

Commit cd07bac

Browse files
committed
fix(web): respect prefers-reduced-motion in AnimatedBackground
Ensure the animated canvas renders a single static frame and never starts requestAnimationFrame when prefers-reduced-motion: reduce is enabled. Adds runtime listener to stop animations if the user toggles the setting. Mousemove listener is only attached when animating.
1 parent 5e218fe commit cd07bac

File tree

1 file changed

+49
-6
lines changed

1 file changed

+49
-6
lines changed

apps/web-roo-code/src/components/homepage/animated-background.tsx

Lines changed: 49 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,9 @@ export function AnimatedBackground() {
225225
connectParticles()
226226
}
227227

228-
// Animation loop.
229-
let animationId: number
228+
// Animation loop and reduced-motion handling.
229+
let animationId: number | undefined
230+
let isAnimating = false
230231

231232
// Target position for smooth following.
232233
let targetX = canvas.width * 0.2
@@ -265,14 +266,56 @@ export function AnimatedBackground() {
265266
drawGrid()
266267
}
267268

268-
animate()
269+
const startAnimation = () => {
270+
if (isAnimating) return
271+
isAnimating = true
272+
window.addEventListener("mousemove", handleMouseMove)
273+
animate()
274+
}
275+
276+
const stopAnimation = () => {
277+
if (!isAnimating) return
278+
isAnimating = false
279+
window.removeEventListener("mousemove", handleMouseMove)
280+
if (animationId !== undefined) cancelAnimationFrame(animationId)
281+
}
282+
283+
// Respect user preference for reduced motion
284+
const mediaQuery =
285+
typeof window !== "undefined" && "matchMedia" in window
286+
? window.matchMedia("(prefers-reduced-motion: reduce)")
287+
: null
288+
289+
const onMotionPreferenceChange = (e: MediaQueryListEvent) => {
290+
if (e.matches) {
291+
// User prefers reduced motion now
292+
stopAnimation()
293+
// Render a single static frame
294+
drawGrid()
295+
} else {
296+
// User allows motion
297+
startAnimation()
298+
}
299+
}
269300

270-
window.addEventListener("mousemove", handleMouseMove)
301+
if (mediaQuery) {
302+
if (mediaQuery.matches) {
303+
// Reduced motion: render one static frame
304+
drawGrid()
305+
} else {
306+
startAnimation()
307+
}
308+
mediaQuery.addEventListener("change", onMotionPreferenceChange)
309+
} else {
310+
// Fallback if matchMedia is unavailable
311+
startAnimation()
312+
}
271313

272314
return () => {
273315
window.removeEventListener("resize", resizeCanvas)
274-
window.removeEventListener("mousemove", handleMouseMove)
275-
cancelAnimationFrame(animationId)
316+
// Ensure animation and listeners are stopped
317+
stopAnimation()
318+
if (mediaQuery) mediaQuery.removeEventListener("change", onMotionPreferenceChange)
276319
}
277320
}, [])
278321

0 commit comments

Comments
 (0)