From 68a11e501bd0c7dbf04f97826882a7d3cee28070 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sat, 29 Mar 2025 21:52:55 -0400 Subject: [PATCH 1/2] Adjust range start/end based on the duration and delay of the animation --- .../src/client/ReactFiberConfigDOM.js | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index e6b54a1e5e522..64f615474c4b1 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -1876,6 +1876,7 @@ function animateGesture( // keyframe. Otherwise it applies to every keyframe. moveOldFrameIntoViewport(keyframes[0]); } + // TODO: Reverse the reverse if the original direction is reverse. const reverse = rangeStart > rangeEnd; targetElement.animate(keyframes, { pseudoElement: pseudoElement, @@ -1886,7 +1887,7 @@ function animateGesture( // from scroll bouncing. easing: 'linear', // We fill in both direction for overscroll. - fill: 'both', + fill: 'both', // TODO: Should we preserve the fill instead? // We play all gestures in reverse, except if we're in reverse direction // in which case we need to play it in reverse of the reverse. direction: reverse ? 'normal' : 'reverse', @@ -1931,18 +1932,32 @@ export function startGestureTransition( // up if they exist later. const foundGroups: Set = new Set(); const foundNews: Set = new Set(); + // Collect the longest duration of any view-transition animation including delay. + let longestDuration = 0; for (let i = 0; i < animations.length; i++) { + const effect: KeyframeEffect = (animations[i].effect: any); // $FlowFixMe - const pseudoElement: ?string = animations[i].effect.pseudoElement; + const pseudoElement: ?string = effect.pseudoElement; if (pseudoElement == null) { - } else if (pseudoElement.startsWith('::view-transition-group')) { - foundGroups.add(pseudoElement.slice(23)); - } else if (pseudoElement.startsWith('::view-transition-new')) { - // TODO: This is not really a sufficient detection because if the new - // pseudo element might exist but have animations disabled on it. - foundNews.add(pseudoElement.slice(21)); + } else if (pseudoElement.startsWith('::view-transition')) { + const timing = effect.getTiming(); + const duration = + typeof timing.duration === 'number' ? timing.duration : 0; + const durationWithDelay = timing.delay + duration; + if (durationWithDelay > longestDuration) { + longestDuration = durationWithDelay; + } + if (pseudoElement.startsWith('::view-transition-group')) { + foundGroups.add(pseudoElement.slice(23)); + } else if (pseudoElement.startsWith('::view-transition-new')) { + // TODO: This is not really a sufficient detection because if the new + // pseudo element might exist but have animations disabled on it. + foundNews.add(pseudoElement.slice(21)); + } } } + const durationToRangeMultipler = + (rangeEnd - rangeStart) / longestDuration; for (let i = 0; i < animations.length; i++) { const anim = animations[i]; if (anim.playState !== 'running') { @@ -1982,14 +1997,24 @@ export function startGestureTransition( } // TODO: If this has only an old state and no new state, } + // Adjust the range based on how long the animation would've ran as time based. + // Since we're running animations in reverse from how they normally would run, + // therefore the timing is from the rangeEnd to the start. + const timing = effect.getTiming(); + const duration = + typeof timing.duration === 'number' ? timing.duration : 0; + const adjustedRangeStart = + rangeEnd - (duration + timing.delay) * durationToRangeMultipler; + const adjustedRangeEnd = + rangeEnd - timing.delay * durationToRangeMultipler; animateGesture( effect.getKeyframes(), // $FlowFixMe: Always documentElement atm. effect.target, pseudoElement, timeline, - rangeStart, - rangeEnd, + adjustedRangeStart, + adjustedRangeEnd, isGeneratedGroupAnim, isExitGroupAnim, ); From 4d0def474a96bb52f5d2a72d593d0550381a9c05 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Sat, 29 Mar 2025 22:09:42 -0400 Subject: [PATCH 2/2] Reverse if the original animation was reversed --- .../src/client/ReactFiberConfigDOM.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 64f615474c4b1..c3ecb5da49f6c 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -1943,6 +1943,7 @@ export function startGestureTransition( const timing = effect.getTiming(); const duration = typeof timing.duration === 'number' ? timing.duration : 0; + // TODO: Consider interation count higher than 1. const durationWithDelay = timing.delay + duration; if (durationWithDelay > longestDuration) { longestDuration = durationWithDelay; @@ -2003,10 +2004,19 @@ export function startGestureTransition( const timing = effect.getTiming(); const duration = typeof timing.duration === 'number' ? timing.duration : 0; - const adjustedRangeStart = + let adjustedRangeStart = rangeEnd - (duration + timing.delay) * durationToRangeMultipler; - const adjustedRangeEnd = + let adjustedRangeEnd = rangeEnd - timing.delay * durationToRangeMultipler; + if ( + timing.direction === 'reverse' || + timing.direction === 'alternate-reverse' + ) { + // This animation was originally in reverse so we have to play it in flipped range. + const temp = adjustedRangeStart; + adjustedRangeStart = adjustedRangeEnd; + adjustedRangeEnd = temp; + } animateGesture( effect.getKeyframes(), // $FlowFixMe: Always documentElement atm.