-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Add scroll timeline acceleration via useScroll + useTransform #3549
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Thread scroll timeline metadata through MotionValue so that when a scroll-derived value is bound to an acceleratable CSS property (opacity, clipPath, filter), a native WAAPI animation is created and attached to the scroll timeline directly, bypassing JS per-frame updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pull Request Review: Scroll Timeline AccelerationSummaryThis PR adds native WAAPI scroll timeline acceleration for scroll-driven animations, bypassing the JavaScript per-frame pipeline for supported properties (opacity, clipPath, filter). The implementation is well-designed and follows a clean architecture pattern. ✅ Strengths
🔍 Issues & Concerns1. Type Mismatch in use-scroll.ts (Critical)Lines 36-61: Type incompatibility between // Current code
values.scrollXProgress.accelerate = {
factory: (animation: AnimationPlaybackControls) => // ❌ Wrong type
scroll(animation, { ... }),
// ...
}The factory: (animation: AnimationPlaybackControlsWithThen) => VoidFunctionBut Recommendation: Update the import and type annotation: import { AnimationPlaybackControlsWithThen, motionValue } from "motion-dom"
values.scrollXProgress.accelerate = {
factory: (animation: AnimationPlaybackControlsWithThen) =>
scroll(animation, { ... }),
// ...
}2. Potential Memory Leak (High Priority)VisualElement.ts:561-564: The cleanup function properly cancels animations, but there's a concern about the subscription management flow. const cleanup = factory(animation)
this.valueSubscriptions.set(key, () => {
cleanup()
animation.cancel()
})Questions:
Recommendation: Add defensive checks: const cleanup = factory(animation)
this.valueSubscriptions.set(key, () => {
cleanup?.()
animation.cancel()
})3. Missing Validation in use-transform.ts (Medium Priority)Lines 220-233: The acceleration propagation logic has several unchecked assumptions: if (
!Array.isArray(input) &&
typeof inputRangeOrTransformer !== "function" &&
Array.isArray(outputRangeOrMap) &&
(input as MotionValue).accelerate &&
options?.clamp !== false
) {
result.accelerate = {
...(input as MotionValue).accelerate!,
times: inputRangeOrTransformer as number[], // ❌ Unsafe cast
keyframes: outputRangeOrMap,
...(options?.ease ? { ease: options.ease } : {}),
}
}Concerns:
Recommendation: Add runtime validation: if (
!Array.isArray(input) &&
typeof inputRangeOrTransformer !== "function" &&
Array.isArray(outputRangeOrMap) &&
Array.isArray(inputRangeOrTransformer) && // ✅ Proper check
(input as MotionValue).accelerate &&
options?.clamp !== false
) {
if (inputRangeOrTransformer.length !== outputRangeOrMap.length) {
// Handle error or skip acceleration
return result
}
result.accelerate = {
...(input as MotionValue).accelerate!,
times: inputRangeOrTransformer,
keyframes: outputRangeOrMap,
...(options?.ease ? { ease: options.ease } : {}),
}
}4. Test Coverage Gap (Medium Priority)The test plan mentions manual verification but lacks automated tests: Missing tests:
Recommendation: Add tests similar to: describe("scroll timeline acceleration", () => {
it("should propagate accelerate config through useTransform", () => {
const scrollProgress = motionValue(0)
scrollProgress.accelerate = { /* config */ }
const opacity = useTransform(scrollProgress, [0, 1], [0, 1])
expect(opacity.accelerate).toBeDefined()
expect(opacity.accelerate?.times).toEqual([0, 1])
})
it("should create WAAPI animation when binding accelerated value", () => {
// Test NativeAnimation creation in VisualElement
})
})5. Documentation & Error Handling (Low Priority)Missing documentation:
Edge cases not handled:
Recommendation: Add documentation and consider development-mode warnings: if (value.accelerate) {
if (!acceleratedValues.has(key)) {
warnOnce(false, `Property "${key}" has accelerate metadata but is not acceleratable`)
}
if (!(this.current instanceof HTMLElement)) {
warnOnce(false, `Accelerate requires HTMLElement, got ${typeof this.current}`)
}
}🎯 Performance ConsiderationsPositive:
Potential concerns:
🔒 Security ConsiderationsNo security concerns identified. The implementation doesn't introduce XSS vectors or allow arbitrary code execution. 📋 Code Style & Best PracticesFollows CLAUDE.md guidelines:
Minor style notes:
🎬 Recommended ActionsMust Fix (Blocking)
Should Fix (High Priority)
Nice to Have
SummaryThis is a solid feature implementation with good architecture. The main concerns are type safety issues that could cause runtime errors and missing test coverage. Once the type mismatch and validation issues are addressed, this will be production-ready. Overall Assessment: Approve with required changes ✅ Great work on the compositor-thread optimization! This will significantly improve scroll animation performance. |
…cceleration - Expand CLAUDE.md testing section: document test types, Cypress E2E patterns, and scroll timeline acceleration test requirements - Fix chained useTransform bug: only propagate accelerate when input is a direct scroll progress value (times [0,1], keyframes [0,1]). Prevents incorrect WAAPI animations when transforms are chained. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Pull Request Review: Scroll Timeline AccelerationSummaryThis PR adds native WAAPI scroll timeline acceleration for Code Quality & ArchitectureStrengths ✅
Critical Issues 🚨1. Missing Tests (Blocking)IMPORTANT: According to CLAUDE.md and the PR's own test plan checklist, tests are mandatory for every feature. This PR currently has NO tests to verify:
Required Action: Before merging, you MUST add Cypress E2E tests following the pattern in CLAUDE.md. Tests should verify WAAPI animations exist using 2. Transform Property Ambiguity in
|
…uard Replace verbose identity check (times/keyframes === [0,1]) with an isTransformed flag on AccelerateConfig. The first useTransform from scroll progress propagates acceleration with isTransformed: true; subsequent chained transforms see the flag and skip acceleration, falling back to the JS onChange path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR Review: Scroll Timeline Acceleration via useScroll + useTransformThis is an impressive performance optimization that enables scroll-driven animations to run entirely on the compositor thread via WAAPI ScrollTimeline. The implementation is well-architected and follows the codebase conventions outlined in CLAUDE.md. Strengths1. Architecture & Design
2. Code Quality
3. Test Coverage
Potential Issues & Suggestions1. Type Safety Concern (High Priority) In 2. Memory Leak Risk (Medium Priority) In 3. Edge Cases to Test
4. Documentation Gap (Low Priority) Consider adding a comment in the test file explaining why backgroundColor is expected to not accelerate (Chromium bug referenced in accelerated-values.ts). 5. Performance Consideration In the test page (scroll-accelerate.tsx:20-31), 6. Type Annotation Consistency Line 65 in Security AssessmentNo security concerns identified. The implementation properly validates acceleratable properties and handles user input safely. Performance ImpactPositive:
Considerations:
Overall AssessmentThis is a high-quality PR that delivers significant performance wins. The architecture is sound, the implementation follows best practices, and test coverage demonstrates thorough consideration of edge cases. Recommendation: Approve with minor fixes Priority fixes before merge:
Nice-to-have improvements: Code Quality: 5/5 Great work on this optimization! |
The previous approach used a single requestAnimationFrame inside useEffect to report animation counts as text content. This was unreliable in CI because the RAF could fire before the VisualElement had created the WAAPI animation (e.g. due to StrictMode double-mount timing). Now Cypress checks element.getAnimations() directly in .should() callbacks, which retry automatically until the assertion passes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PR Review: Scroll Timeline WAAPI AccelerationThis is an excellent performance optimization that offloads scroll-driven animations to the compositor thread. The implementation is well-designed overall. Here's my detailed feedback: ✅ Strengths
🐛 Potential Issues1. Memory leak in
|
PR Review: Scroll Timeline Acceleration via useScroll + useTransformSummaryThis PR implements native WAAPI scroll timeline acceleration for scroll-driven animations, bypassing the JavaScript per-frame pipeline for supported CSS properties (opacity, clipPath, filter). Great performance optimization! ✅ Strengths
🐛 Issues FoundCritical: Debug Code Left InLocation: console.log(name, keyframes, times, ease)Issue: Debug Impact: High - affects all users of scroll acceleration Medium: Potential undefined reference in console.logLocation: Same line 550 console.log(name, keyframes, times, ease)Issue: Variable 🤔 Code Quality Concerns1. Mutation of motion values in hookLocation: values.scrollXProgress.accelerate = {
factory: (animation: AnimationPlaybackControls) => ...
}Concern: Mutating Question: Should this be inside Suggestion: const values = useConstant(() => {
const vals = createScrollMotionValues()
vals.scrollXProgress.accelerate = { /* config */ }
vals.scrollYProgress.accelerate = { /* config */ }
return vals
})However, this won't work because 2. Closure captures stale refsLocation: factory: (animation: AnimationPlaybackControls) =>
scroll(animation, {
...options,
axis: "x",
container: container?.current || undefined,
target: target?.current || undefined,
}),Concern: The closure captures Question: What happens if someone passes a different ref object on re-render? The old 3. Type safety: AccelerateConfig interfaceLocation: export interface AccelerateConfig {
factory: (animation: AnimationPlaybackControlsWithThen) => VoidFunction
times: number[]
keyframes: any[] // ⚠️
ease?: EasingFunction | EasingFunction[]
duration: number
isTransformed?: boolean
}Issue: 4. Missing null check safetyLocation: const { factory, keyframes, times, ease, duration } = value.accelerateConcern: Destructuring assumes all properties exist. If 🎯 Test Coverage AnalysisGood:
Missing:
Suggestion: Add a test that actually scrolls and verifies opacity changes without JS execution (using Performance API or checking frame callbacks aren't called). 🔒 Security & PerformancePerformance:✅ Excellent - Main goal achieved: moves scroll animations to compositor thread Memory:
Security:✅ No security concerns identified 📋 RecommendationsMust fix before merge:
Should fix:
Consider:
🎓 Learning & Best PracticesAccording to CLAUDE.md:
VerdictOverall: Good implementation with one critical fix needed The core implementation is solid and achieves the performance goals. The main blocker is the debug Estimated risk: Low (after removing console.log) 🤖 Review generated by Claude Code |
PR Review: Scroll Timeline Acceleration via useScroll + useTransformThis PR implements a sophisticated optimization that accelerates scroll-driven animations by leveraging WAAPI ScrollTimeline when possible, bypassing the JavaScript per-frame pipeline for supported CSS properties. ✅ Code Quality & ImplementationStrong Points:
|
PR Review: Scroll Timeline Acceleration via useScroll + useTransformSummaryThis PR adds WAAPI scroll timeline acceleration for scroll-driven animations, allowing supported CSS properties to animate on the compositor thread instead of via JavaScript per-frame updates. This is a significant performance optimization for scroll-linked animations. Code Quality & Architecture ✅Strengths:
Potential Issues & Concerns1. Memory Management
|
Summary
AccelerateConfigtype andaccelerateproperty toMotionValueto carry scroll timeline metadataaccelerateonscrollXProgress/scrollYProgressinuseScrollwith a factory closure that defers ref resolution to mount timeacceleratethrough range-mappinguseTransformcalls, mapping input/output ranges to WAAPItimes/keyframesbindToMotionValue, create aNativeAnimationattached to the scroll timeline when the value hasacceleratemetadata and the CSS property is acceleratable (opacity, clipPath, filter)This bypasses the JS per-frame pipeline (motionValue.set → onChange → build → renderInstance) for scroll-driven animations on supported properties, running them entirely on the compositor thread via WAAPI ScrollTimeline.
Test plan
cd packages/framer-motion && yarn test-client(88 suites, 739 tests, 0 failures)🤖 Generated with Claude Code