-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Description
1. Read the FAQs ✅
2. Describe the bug
Since ~v12.30, motion uses the native ScrollTimeline API when window.ScrollTimeline !== undefined. This causes scroll-linked opacity animations (via useScroll + useTransform) to break in both Safari and Firefox, while scale and rotate transforms work correctly.
- Safari (macOS): opacity stays faded/intermediate even after scrolling past the animation endpoint — never reaches full opacity
- Firefox: opacity jumps to full immediately at the start — no gradual fade-in at all
Both browsers work correctly with motion 12.29.x (before ScrollTimeline adoption). Chrome behavior is untested.
The root cause appears to be the feature detection in motion:
const supportsScrollTimeline = memo(() => window.ScrollTimeline !== undefined);Both Safari and Firefox expose ScrollTimeline on window, but their implementations don't correctly resolve opacity values for scroll-linked animations. This is a classic "feature exists but is buggy" situation — the check should be more robust than simple existence detection.
3. Reproduction
Minimal pattern that reproduces the issue:
function Card() {
const ref = useRef(null);
const { scrollYProgress } = useScroll({
target: ref,
offset: ['start 100%', 'start 33%'],
});
const opacity = useTransform(scrollYProgress, [0, 1], [0, 1]);
const cardScale = useTransform(scrollYProgress, [0, 1], [0.8, 1]);
return (
<motion.div
ref={ref}
style={{ opacity, scale: cardScale }}
>
<p>Scroll to reveal</p>
</motion.div>
);
}scale animates correctly in all browsers. opacity does not.
4. Steps to reproduce
- Use motion
>=12.30.0(tested with12.34.0) - Create a scroll-linked opacity animation using
useScroll+useTransformas shown above - Open in Safari macOS → opacity stays faded, never reaches 1
- Open in Firefox → opacity is 1 from the start, no animation
- Downgrade to motion
12.29.x→ both browsers work correctly
5. Expected behavior
Opacity should smoothly animate from 0 to 1 as the element scrolls into view, matching the behavior of scale and rotate transforms in the same style prop.
6. Environment details
- macOS Sequoia 26.2
- Safari 18.x, Firefox latest
- motion 12.34.0 (broken) vs 12.29.3 (working)
- Next.js 16, React 19
7. Suggested fix
The supportsScrollTimeline check should go beyond simple existence detection. Options:
- Add browser-specific guards (Safari/Firefox) until their
ScrollTimelineimplementations mature - Run a runtime validation that
ScrollTimelinecorrectly resolves values before committing to it - Provide an opt-out escape hatch (e.g. a
useNativeScroll: falseoption)