Skip to content

Commit c857135

Browse files
committed
Add CustomTimeline protocol
This basically lets a custom implementation drive the Animation we start on pseudo-elements.
1 parent b067c6f commit c857135

File tree

2 files changed

+61
-22
lines changed

2 files changed

+61
-22
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,7 @@ module.exports = {
622622
ScrollTimeline: 'readonly',
623623
EventListenerOptionsOrUseCapture: 'readonly',
624624
FocusOptions: 'readonly',
625+
OptionalEffectTiming: 'readonly',
625626

626627
spyOnDev: 'readonly',
627628
spyOnDevAndProd: 'readonly',

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

Lines changed: 60 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2213,7 +2213,8 @@ function animateGesture(
22132213
keyframes: any,
22142214
targetElement: Element,
22152215
pseudoElement: string,
2216-
timeline: AnimationTimeline,
2216+
timeline: GestureTimeline,
2217+
customTimelineCleanup: Array<() => void>,
22172218
rangeStart: number,
22182219
rangeEnd: number,
22192220
moveFirstFrameIntoViewport: boolean,
@@ -2274,24 +2275,49 @@ function animateGesture(
22742275
}
22752276
// TODO: Reverse the reverse if the original direction is reverse.
22762277
const reverse = rangeStart > rangeEnd;
2277-
targetElement.animate(keyframes, {
2278-
pseudoElement: pseudoElement,
2279-
// Set the timeline to the current gesture timeline to drive the updates.
2280-
timeline: timeline,
2281-
// We reset all easing functions to linear so that it feels like you
2282-
// have direct impact on the transition and to avoid double bouncing
2283-
// from scroll bouncing.
2284-
easing: 'linear',
2285-
// We fill in both direction for overscroll.
2286-
fill: 'both', // TODO: Should we preserve the fill instead?
2287-
// We play all gestures in reverse, except if we're in reverse direction
2288-
// in which case we need to play it in reverse of the reverse.
2289-
direction: reverse ? 'normal' : 'reverse',
2290-
// Range start needs to be higher than range end. If it goes in reverse
2291-
// we reverse the whole animation below.
2292-
rangeStart: (reverse ? rangeEnd : rangeStart) + '%',
2293-
rangeEnd: (reverse ? rangeStart : rangeEnd) + '%',
2294-
});
2278+
if (timeline instanceof AnimationTimeline) {
2279+
// Native Timeline
2280+
targetElement.animate(keyframes, {
2281+
pseudoElement: pseudoElement,
2282+
// Set the timeline to the current gesture timeline to drive the updates.
2283+
timeline: timeline,
2284+
// We reset all easing functions to linear so that it feels like you
2285+
// have direct impact on the transition and to avoid double bouncing
2286+
// from scroll bouncing.
2287+
easing: 'linear',
2288+
// We fill in both direction for overscroll.
2289+
fill: 'both', // TODO: Should we preserve the fill instead?
2290+
// We play all gestures in reverse, except if we're in reverse direction
2291+
// in which case we need to play it in reverse of the reverse.
2292+
direction: reverse ? 'normal' : 'reverse',
2293+
// Range start needs to be higher than range end. If it goes in reverse
2294+
// we reverse the whole animation below.
2295+
rangeStart: (reverse ? rangeEnd : rangeStart) + '%',
2296+
rangeEnd: (reverse ? rangeStart : rangeEnd) + '%',
2297+
});
2298+
} else {
2299+
// Custom Timeline
2300+
const animation = targetElement.animate(keyframes, {
2301+
pseudoElement: pseudoElement,
2302+
// We reset all easing functions to linear so that it feels like you
2303+
// have direct impact on the transition and to avoid double bouncing
2304+
// from scroll bouncing.
2305+
easing: 'linear',
2306+
// We fill in both direction for overscroll.
2307+
fill: 'both', // TODO: Should we preserve the fill instead?
2308+
// We play all gestures in reverse, except if we're in reverse direction
2309+
// in which case we need to play it in reverse of the reverse.
2310+
direction: reverse ? 'normal' : 'reverse',
2311+
// We set the delay and duration to represent the span of the range.
2312+
delay: reverse ? rangeEnd : rangeStart,
2313+
duration: reverse ? rangeStart - rangeEnd : rangeEnd - rangeStart,
2314+
});
2315+
// Let the custom timeline take control of driving the animation.
2316+
const cleanup = timeline.animate(animation);
2317+
if (cleanup) {
2318+
customTimelineCleanup.push(cleanup);
2319+
}
2320+
}
22952321
}
22962322

22972323
export function startGestureTransition(
@@ -2320,6 +2346,7 @@ export function startGestureTransition(
23202346
});
23212347
// $FlowFixMe[prop-missing]
23222348
ownerDocument.__reactViewTransition = transition;
2349+
const customTimelineCleanup: Array<() => void> = []; // Cleanup Animations started in a CustomTimeline
23232350
const readyCallback = () => {
23242351
const documentElement: Element = (ownerDocument.documentElement: any);
23252352
// Loop through all View Transition Animations.
@@ -2419,6 +2446,7 @@ export function startGestureTransition(
24192446
effect.target,
24202447
pseudoElement,
24212448
timeline,
2449+
customTimelineCleanup,
24222450
adjustedRangeStart,
24232451
adjustedRangeEnd,
24242452
isGeneratedGroupAnim,
@@ -2445,6 +2473,7 @@ export function startGestureTransition(
24452473
effect.target,
24462474
pseudoElementName,
24472475
timeline,
2476+
customTimelineCleanup,
24482477
rangeStart,
24492478
rangeEnd,
24502479
false,
@@ -2494,6 +2523,10 @@ export function startGestureTransition(
24942523
transition.ready.then(readyForAnimations, handleError);
24952524
transition.finished.finally(() => {
24962525
cancelAllViewTransitionAnimations((ownerDocument.documentElement: any));
2526+
for (let i = 0; i < customTimelineCleanup.length; i++) {
2527+
const cleanup = customTimelineCleanup[i];
2528+
cleanup();
2529+
}
24972530
// $FlowFixMe[prop-missing]
24982531
if (ownerDocument.__reactViewTransition === transition) {
24992532
// $FlowFixMe[prop-missing]
@@ -2597,10 +2630,15 @@ export function createViewTransitionInstance(
25972630
};
25982631
}
25992632

2600-
export type GestureTimeline = AnimationTimeline; // TODO: More provider types.
2633+
interface CustomTimeline {
2634+
currentTime: number;
2635+
animate(animation: Animation): void | (() => void);
2636+
}
2637+
2638+
export type GestureTimeline = AnimationTimeline | CustomTimeline;
26012639

2602-
export function getCurrentGestureOffset(provider: GestureTimeline): number {
2603-
const time = provider.currentTime;
2640+
export function getCurrentGestureOffset(timeline: GestureTimeline): number {
2641+
const time = timeline.currentTime;
26042642
if (time === null) {
26052643
throw new Error(
26062644
'Cannot start a gesture with a disconnected AnimationTimeline.',

0 commit comments

Comments
 (0)