Skip to content

Clear width/height from Keyframes to Optimize View Transitions #33576

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 107 additions & 1 deletion packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -2133,6 +2133,84 @@ export function startViewTransition(
});
// $FlowFixMe[prop-missing]
ownerDocument.__reactViewTransition = transition;

const readyCallback = () => {
const documentElement: Element = (ownerDocument.documentElement: any);
// Loop through all View Transition Animations.
const animations = documentElement.getAnimations({subtree: true});
for (let i = 0; i < animations.length; i++) {
const animation = animations[i];
const effect: KeyframeEffect = (animation.effect: any);
// $FlowFixMe
const pseudoElement: ?string = effect.pseudoElement;
if (
pseudoElement != null &&
pseudoElement.startsWith('::view-transition')
) {
const keyframes = effect.getKeyframes();
// Next, we're going to try to optimize this animation in case the auto-generated
// width/height keyframes are unnecessary.
let width;
let height;
let unchangedDimensions = true;
for (let j = 0; j < keyframes.length; j++) {
const keyframe = keyframes[j];
const w = keyframe.width;
if (width === undefined) {
width = w;
} else if (width !== w) {
unchangedDimensions = false;
break;
}
const h = keyframe.height;
if (height === undefined) {
height = h;
} else if (height !== h) {
unchangedDimensions = false;
break;
}
// We're clearing the keyframes in case we are going to apply the optimization.
delete keyframe.width;
delete keyframe.height;
if (keyframe.transform === 'none') {
delete keyframe.transform;
}
}
if (
unchangedDimensions &&
width !== undefined &&
height !== undefined
) {
// Replace the keyframes with ones that don't animate the width/height.
// $FlowFixMe
effect.setKeyframes(keyframes);
// Read back the new animation to see what the underlying width/height of the pseudo-element was.
const computedStyle = getComputedStyle(
// $FlowFixMe
effect.target,
// $FlowFixMe
effect.pseudoElement,
);
if (
computedStyle.width !== width ||
computedStyle.height !== height
) {
// Oops. Turns out that the pseudo-element had a different width/height so we need to let it
// be overridden. Add it back.
const first = keyframes[0];
first.width = width;
first.height = height;
const last = keyframes[keyframes.length - 1];
last.width = width;
last.height = height;
// $FlowFixMe
effect.setKeyframes(keyframes);
}
}
}
}
spawnedWorkCallback();
};
const handleError = (error: mixed) => {
try {
error = customizeViewTransitionError(error, false);
Expand All @@ -2150,7 +2228,7 @@ export function startViewTransition(
spawnedWorkCallback();
}
};
transition.ready.then(spawnedWorkCallback, handleError);
transition.ready.then(readyCallback, handleError);
transition.finished.finally(() => {
cancelAllViewTransitionAnimations((ownerDocument.documentElement: any));
// $FlowFixMe[prop-missing]
Expand Down Expand Up @@ -2219,11 +2297,26 @@ function animateGesture(
moveFirstFrameIntoViewport: boolean,
moveAllFramesIntoViewport: boolean,
) {
let width;
let height;
let unchangedDimensions = true;
for (let i = 0; i < keyframes.length; i++) {
const keyframe = keyframes[i];
// Delete any easing since we always apply linear easing to gestures.
delete keyframe.easing;
delete keyframe.computedOffset;
const w = keyframe.width;
if (width === undefined) {
width = w;
} else if (width !== w) {
unchangedDimensions = false;
}
const h = keyframe.height;
if (height === undefined) {
height = h;
} else if (height !== h) {
unchangedDimensions = false;
}
// Chrome returns "auto" for width/height which is not a valid value to
// animate to. Similarly, transform: "none" is actually lack of transform.
if (keyframe.width === 'auto') {
Expand Down Expand Up @@ -2272,6 +2365,19 @@ function animateGesture(
// keyframe. Otherwise it applies to every keyframe.
moveOldFrameIntoViewport(keyframes[0]);
}
if (unchangedDimensions && width !== undefined && height !== undefined) {
// Read the underlying width/height of the pseudo-element. The previous animation
// should have already been cancelled so we should observe the underlying element.
const computedStyle = getComputedStyle(targetElement, pseudoElement);
if (computedStyle.width === width && computedStyle.height === height) {
for (let i = 0; i < keyframes.length; i++) {
const keyframe = keyframes[i];
delete keyframe.width;
delete keyframe.height;
}
}
}

// TODO: Reverse the reverse if the original direction is reverse.
const reverse = rangeStart > rangeEnd;
targetElement.animate(keyframes, {
Expand Down
Loading