Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,9 @@ export function AnimatedBackground() {
connectParticles()
}

// Animation loop.
let animationId: number
// Animation loop and reduced-motion handling.
let animationId: number | undefined
let isAnimating = false

// Target position for smooth following.
let targetX = canvas.width * 0.2
Expand Down Expand Up @@ -265,14 +266,56 @@ export function AnimatedBackground() {
drawGrid()
}

animate()
const startAnimation = () => {
if (isAnimating) return
isAnimating = true
window.addEventListener("mousemove", handleMouseMove)
animate()
}

const stopAnimation = () => {
if (!isAnimating) return
isAnimating = false
window.removeEventListener("mousemove", handleMouseMove)
if (animationId !== undefined) cancelAnimationFrame(animationId)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Minor: after cancelAnimationFrame, reset animationId to undefined to avoid accidental double-cancel or stale ids after toggling animation state.

Suggested change
if (animationId !== undefined) cancelAnimationFrame(animationId)
if (animationId !== undefined) {
cancelAnimationFrame(animationId)
animationId = undefined
}

}

// Respect user preference for reduced motion
const mediaQuery =
typeof window !== "undefined" && "matchMedia" in window
? window.matchMedia("(prefers-reduced-motion: reduce)")
: null

const onMotionPreferenceChange = (e: MediaQueryListEvent) => {
if (e.matches) {
// User prefers reduced motion now
stopAnimation()
// Render a single static frame
drawGrid()
} else {
// User allows motion
startAnimation()
}
}

window.addEventListener("mousemove", handleMouseMove)
if (mediaQuery) {
if (mediaQuery.matches) {
// Reduced motion: render one static frame
drawGrid()
} else {
startAnimation()
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Older Safari uses the legacy addListener API for MediaQueryList. Add a runtime fallback so preference changes are handled across browsers.

Suggested change
}
if ("addEventListener" in mediaQuery) {
mediaQuery.addEventListener("change", onMotionPreferenceChange)
} else {
// Safari < 14
// @ts-expect-error legacy API
mediaQuery.addListener(onMotionPreferenceChange)
}

mediaQuery.addEventListener("change", onMotionPreferenceChange)
} else {
// Fallback if matchMedia is unavailable
startAnimation()
}

return () => {
window.removeEventListener("resize", resizeCanvas)
window.removeEventListener("mousemove", handleMouseMove)
cancelAnimationFrame(animationId)
// Ensure animation and listeners are stopped
stopAnimation()
if (mediaQuery) mediaQuery.removeEventListener("change", onMotionPreferenceChange)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Mirror the addListener fallback on cleanup to ensure the change listener is properly removed in older Safari.

Suggested change
if (mediaQuery) mediaQuery.removeEventListener("change", onMotionPreferenceChange)
if ("removeEventListener" in mediaQuery) {
mediaQuery.removeEventListener("change", onMotionPreferenceChange)
} else {
// @ts-expect-error legacy API
mediaQuery.removeListener(onMotionPreferenceChange)
}

}
}, [])

Expand Down
Loading