Skip to content

[BUG] useScroll + useTransform opacity broken in Safari & Firefox since ScrollTimeline adoption (~12.30+) #3559

@iPurpl3x

Description

@iPurpl3x

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

  1. Use motion >=12.30.0 (tested with 12.34.0)
  2. Create a scroll-linked opacity animation using useScroll + useTransform as shown above
  3. Open in Safari macOS → opacity stays faded, never reaches 1
  4. Open in Firefox → opacity is 1 from the start, no animation
  5. 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 ScrollTimeline implementations mature
  • Run a runtime validation that ScrollTimeline correctly resolves values before committing to it
  • Provide an opt-out escape hatch (e.g. a useNativeScroll: false option)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions