Skip to content

Commit d20c280

Browse files
authored
Adjust range start/end based on the duration and delay of the animation (#32790)
When different animations in a View Transition have different durations, we shouldn't stretch them out to run the full range of swipe. Because then they wouldn't line up the same way as when played using plain time. This adjusts the range start/end to be what it would've been when played by time. Except since we are playing animations in reverse, the animation-delay is actually applied from the range end and then the duration from there to get closer to the start. Reverse the range if the original animation was reversed. Interestingly, the range it takes can be adjusted by what is in the viewport since if a long duration animation is excluded then everything else adjusts too. I left some todos too. We really should also handle if the original animation has multiple iterations. Currently we only play those once.
1 parent 0a7cf20 commit d20c280

File tree

1 file changed

+45
-10
lines changed

1 file changed

+45
-10
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1880,6 +1880,7 @@ function animateGesture(
18801880
// keyframe. Otherwise it applies to every keyframe.
18811881
moveOldFrameIntoViewport(keyframes[0]);
18821882
}
1883+
// TODO: Reverse the reverse if the original direction is reverse.
18831884
const reverse = rangeStart > rangeEnd;
18841885
targetElement.animate(keyframes, {
18851886
pseudoElement: pseudoElement,
@@ -1890,7 +1891,7 @@ function animateGesture(
18901891
// from scroll bouncing.
18911892
easing: 'linear',
18921893
// We fill in both direction for overscroll.
1893-
fill: 'both',
1894+
fill: 'both', // TODO: Should we preserve the fill instead?
18941895
// We play all gestures in reverse, except if we're in reverse direction
18951896
// in which case we need to play it in reverse of the reverse.
18961897
direction: reverse ? 'normal' : 'reverse',
@@ -1935,18 +1936,33 @@ export function startGestureTransition(
19351936
// up if they exist later.
19361937
const foundGroups: Set<string> = new Set();
19371938
const foundNews: Set<string> = new Set();
1939+
// Collect the longest duration of any view-transition animation including delay.
1940+
let longestDuration = 0;
19381941
for (let i = 0; i < animations.length; i++) {
1942+
const effect: KeyframeEffect = (animations[i].effect: any);
19391943
// $FlowFixMe
1940-
const pseudoElement: ?string = animations[i].effect.pseudoElement;
1944+
const pseudoElement: ?string = effect.pseudoElement;
19411945
if (pseudoElement == null) {
1942-
} else if (pseudoElement.startsWith('::view-transition-group')) {
1943-
foundGroups.add(pseudoElement.slice(23));
1944-
} else if (pseudoElement.startsWith('::view-transition-new')) {
1945-
// TODO: This is not really a sufficient detection because if the new
1946-
// pseudo element might exist but have animations disabled on it.
1947-
foundNews.add(pseudoElement.slice(21));
1946+
} else if (pseudoElement.startsWith('::view-transition')) {
1947+
const timing = effect.getTiming();
1948+
const duration =
1949+
typeof timing.duration === 'number' ? timing.duration : 0;
1950+
// TODO: Consider interation count higher than 1.
1951+
const durationWithDelay = timing.delay + duration;
1952+
if (durationWithDelay > longestDuration) {
1953+
longestDuration = durationWithDelay;
1954+
}
1955+
if (pseudoElement.startsWith('::view-transition-group')) {
1956+
foundGroups.add(pseudoElement.slice(23));
1957+
} else if (pseudoElement.startsWith('::view-transition-new')) {
1958+
// TODO: This is not really a sufficient detection because if the new
1959+
// pseudo element might exist but have animations disabled on it.
1960+
foundNews.add(pseudoElement.slice(21));
1961+
}
19481962
}
19491963
}
1964+
const durationToRangeMultipler =
1965+
(rangeEnd - rangeStart) / longestDuration;
19501966
for (let i = 0; i < animations.length; i++) {
19511967
const anim = animations[i];
19521968
if (anim.playState !== 'running') {
@@ -1986,14 +2002,33 @@ export function startGestureTransition(
19862002
}
19872003
// TODO: If this has only an old state and no new state,
19882004
}
2005+
// Adjust the range based on how long the animation would've ran as time based.
2006+
// Since we're running animations in reverse from how they normally would run,
2007+
// therefore the timing is from the rangeEnd to the start.
2008+
const timing = effect.getTiming();
2009+
const duration =
2010+
typeof timing.duration === 'number' ? timing.duration : 0;
2011+
let adjustedRangeStart =
2012+
rangeEnd - (duration + timing.delay) * durationToRangeMultipler;
2013+
let adjustedRangeEnd =
2014+
rangeEnd - timing.delay * durationToRangeMultipler;
2015+
if (
2016+
timing.direction === 'reverse' ||
2017+
timing.direction === 'alternate-reverse'
2018+
) {
2019+
// This animation was originally in reverse so we have to play it in flipped range.
2020+
const temp = adjustedRangeStart;
2021+
adjustedRangeStart = adjustedRangeEnd;
2022+
adjustedRangeEnd = temp;
2023+
}
19892024
animateGesture(
19902025
effect.getKeyframes(),
19912026
// $FlowFixMe: Always documentElement atm.
19922027
effect.target,
19932028
pseudoElement,
19942029
timeline,
1995-
rangeStart,
1996-
rangeEnd,
2030+
adjustedRangeStart,
2031+
adjustedRangeEnd,
19972032
isGeneratedGroupAnim,
19982033
isExitGroupAnim,
19992034
);

0 commit comments

Comments
 (0)