Fix shared element animations when animate() called before animateLayout()#3511
Fix shared element animations when animate() called before animateLayout()#3511mattgperry merged 1 commit intomainfrom
Conversation
…out() When animate() is called on an element before animateLayout(), the VisualElement gets mounted but the projection does not. This causes registerSharedNode() to never be called, breaking shared element animations. The fix explicitly mounts the projection when the VisualElement is already mounted but the projection isn't. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Pull Request Review - PR #3511SummaryThis PR fixes a critical bug where calling Code Quality: ✅ ExcellentStrengths:
Code Style:
Test Coverage: ✅ StrongStrengths:
Minor Suggestion:The Playwright tests use Potential Bugs: ✅ None IdentifiedThe fix is sound. I verified:
Performance Considerations: ✅ Good
Security Concerns: ✅ NoneThis is animation infrastructure code with no user input handling or external data processing. No security concerns identified. Architecture Considerations: 💡 SuggestionThe fix correctly handles the edge case, but it highlights an interesting architectural question: Should the creation and mounting of projections be more tightly coupled to prevent this class of bugs? Currently there are two places where we check
This duplication suggests the logic could potentially be encapsulated, though given the different contexts (React mounting vs. layout animation), the current approach is reasonable. Specific Code ReviewLayoutAnimationBuilder.ts:333-337} else if (!visualElement.projection.instance) {
// Mount projection if VisualElement is already mounted but projection isn't
// This happens when animate() was called before animateLayout()
visualElement.projection.mount(element as HTMLElement)
}✅ Correct logic flow Test File: modal-open-after-animate.htmlLine 156-157: const cardContent = document.querySelector('.card-content')
animate(cardContent, { borderRadius: 16 }, { type: false })✅ Excellent reproduction of the bug scenario - calling Lines 186-188: const modalContent = expandedCardContainer.querySelector('.card-content')
animate(modalContent, { borderRadius: 16 }, { type: false })✅ Tests the scenario on both source and target elements Recommendations
Final Verdict: ✅ APPROVEDThis is high-quality work that fixes a real bug with minimal code changes, excellent test coverage, and clear documentation. The fix is architecturally sound and follows the repository's patterns and conventions. Great job identifying and fixing this edge case! 🎯 |
Summary
animate(element, { borderRadius: 20 }, { type: false })beforeanimateLayout()breaks shared element animationsanimate()is called first, it creates and mounts a VisualElement, but whenanimateLayout()later creates a projection,projection.mount()was never called becausevisualElement.currentwas already setregisterSharedNode()was never called, so the element wasn't in the shared node stackTest plan
modal-open-after-animate.htmlpasses (callsanimate()beforeanimateLayout())modal-open.htmltest still passes🤖 Generated with Claude Code