Skip to content

Commit 9632c54

Browse files
committed
Block the view transition on suspensey images
Up to 500ms just like the client.
1 parent d742611 commit 9632c54

File tree

3 files changed

+41
-10
lines changed

3 files changed

+41
-10
lines changed

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4860,8 +4860,9 @@ export function writeCompletedSegmentInstruction(
48604860
const completeBoundaryScriptFunctionOnly = stringToPrecomputedChunk(
48614861
completeBoundaryFunction,
48624862
);
4863-
const completeBoundaryUpgradeToViewTransitionsInstruction =
4864-
stringToPrecomputedChunk(upgradeToViewTransitionsInstruction);
4863+
const completeBoundaryUpgradeToViewTransitionsInstruction = stringToChunk(
4864+
upgradeToViewTransitionsInstruction,
4865+
);
48654866
const completeBoundaryScript1Partial = stringToPrecomputedChunk('$RC("');
48664867

48674868
const completeBoundaryWithStylesScript1FullPartial = stringToPrecomputedChunk(

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/react-dom-bindings/src/server/fizz-instruction-set/ReactDOMFizzInstructionSetShared.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ const SUSPENSE_PENDING_START_DATA = '$?';
1313
const SUSPENSE_QUEUED_START_DATA = '$~';
1414
const SUSPENSE_FALLBACK_START_DATA = '$!';
1515

16-
const SUSPENSEY_FONT_TIMEOUT = 500;
16+
const SUSPENSEY_FONT_AND_IMAGE_TIMEOUT = 1000;
1717

1818
// TODO: Symbols that are referenced outside this module use dynamic accessor
1919
// notation instead of dot notation to prevent Closure's advanced compilation
@@ -136,6 +136,7 @@ export function revealCompletedBoundariesWithViewTransitions(
136136
);
137137
}
138138
}
139+
const suspenseyImages = [];
139140
// Next we'll find the nodes that we're going to animate and apply names to them..
140141
for (let i = 0; i < batch.length; i += 2) {
141142
const suspenseIdNode = batch[i];
@@ -248,21 +249,50 @@ export function revealCompletedBoundariesWithViewTransitions(
248249
ancestorElement.nodeType === ELEMENT_NODE &&
249250
ancestorElement.getAttribute('vt-update') !== 'none'
250251
);
252+
253+
// Find the appearing Suspensey Images inside the new content.
254+
const appearingImages = contentNode.querySelectorAll(
255+
'img[src]:not([loading="lazy"])',
256+
);
257+
// TODO: Consider marking shouldStartViewTransition if we found any images.
258+
// But only once we can disable the root animation for that case.
259+
suspenseyImages.push.apply(suspenseyImages, appearingImages);
251260
}
252261
if (shouldStartViewTransition) {
253262
const transition = (document['__reactViewTransition'] = document[
254263
'startViewTransition'
255264
]({
256265
update: () => {
257-
revealBoundaries(
258-
batch,
259-
// Force layout to trigger font loading, we pass the actual value to trick minifiers.
266+
revealBoundaries(batch);
267+
const blockingPromises = [
268+
// Force layout to trigger font loading, we stash the actual value to trick minifiers.
260269
document.documentElement.clientHeight,
261-
);
262-
return Promise.race([
263270
// Block on fonts finishing loading before revealing these boundaries.
264271
document.fonts.ready,
265-
new Promise(resolve => setTimeout(resolve, SUSPENSEY_FONT_TIMEOUT)),
272+
];
273+
for (let i = 0; i < suspenseyImages.length; i++) {
274+
const suspenseyImage = suspenseyImages[i];
275+
if (!suspenseyImage.complete) {
276+
const rect = suspenseyImage.getBoundingClientRect();
277+
const inViewport =
278+
rect.bottom > 0 &&
279+
rect.right > 0 &&
280+
rect.top < window.innerHeight &&
281+
rect.left < window.innerWidth;
282+
if (inViewport) {
283+
const loadingImage = new Promise(resolve => {
284+
suspenseyImage.addEventListener('load', resolve);
285+
suspenseyImage.addEventListener('error', resolve);
286+
});
287+
blockingPromises.push(loadingImage);
288+
}
289+
}
290+
}
291+
return Promise.race([
292+
Promise.all(blockingPromises),
293+
new Promise(resolve =>
294+
setTimeout(resolve, SUSPENSEY_FONT_AND_IMAGE_TIMEOUT),
295+
),
266296
]);
267297
},
268298
types: [], // TODO: Add a hard coded type for Suspense reveals.

0 commit comments

Comments
 (0)