Skip to content

Commit 78bc539

Browse files
committed
Apply view transitions
1 parent 91ac1fe commit 78bc539

File tree

3 files changed

+176
-16
lines changed

3 files changed

+176
-16
lines changed

fixtures/view-transition/src/components/Page.js

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -200,21 +200,41 @@ export default function Page({url, navigate}) {
200200
<div>!!</div>
201201
</ViewTransition>
202202
</Activity>
203-
<Suspense fallback="Loading">
203+
<Suspense
204+
fallback={
205+
<ViewTransition>
206+
<div>
207+
<ViewTransition name="shared-reveal">
208+
<h2>█████</h2>
209+
</ViewTransition>
210+
<p>████</p>
211+
<p>███████</p>
212+
<p>████</p>
213+
<p>██</p>
214+
<p>██████</p>
215+
<p>███</p>
216+
<p>████</p>
217+
</div>
218+
</ViewTransition>
219+
}>
204220
<ViewTransition>
205-
<p>these</p>
206-
<p>rows</p>
207-
<p>exist</p>
208-
<p>to</p>
209-
<p>test</p>
210-
<p>scrolling</p>
211-
<p>content</p>
212-
<p>out</p>
213-
<p>of</p>
214-
{portal}
215-
<p>the</p>
216-
<p>viewport</p>
217-
<Suspend />
221+
<div>
222+
<p>these</p>
223+
<p>rows</p>
224+
<ViewTransition name="shared-reveal">
225+
<h2>exist</h2>
226+
</ViewTransition>
227+
<p>to</p>
228+
<p>test</p>
229+
<p>scrolling</p>
230+
<p>content</p>
231+
<p>out</p>
232+
<p>of</p>
233+
{portal}
234+
<p>the</p>
235+
<p>viewport</p>
236+
<Suspend />
237+
</div>
218238
</ViewTransition>
219239
</Suspense>
220240
{show ? <Component /> : null}

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: 141 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Shared implementation and constants between the inline script and external
44
// runtime instruction sets.
55

6+
const ELEMENT_NODE = 1;
67
const COMMENT_NODE = 8;
78
const ACTIVITY_START_DATA = '&';
89
const ACTIVITY_END_DATA = '/&';
@@ -84,14 +85,153 @@ export function revealCompletedBoundariesWithViewTransitions(
8485
revealBoundaries,
8586
batch,
8687
) {
88+
let shouldStartViewTransition = false;
89+
let autoNameIdx = 0;
90+
function applyViewTransitionName(element, classAttributeName) {
91+
const className = element.getAttribute(classAttributeName);
92+
if (!className || className === 'none') {
93+
return;
94+
}
95+
if (className !== 'auto') {
96+
element.style['viewTransitionClass'] = className;
97+
}
98+
let name = element.getAttribute('vt-name');
99+
if (!name) {
100+
// Auto-generate a name for this one.
101+
// TODO: We don't have a prefix to pick from here but maybe we don't need it
102+
// since it's only applicable temporarily during this specific animation.
103+
const idPrefix = '';
104+
name = '\u00AB' + idPrefix + 'T' + autoNameIdx++ + '\u00BB';
105+
}
106+
element.style['viewTransitionName'] = name;
107+
shouldStartViewTransition = true;
108+
}
87109
try {
88110
const existingTransition = document['__reactViewTransition'];
89111
if (existingTransition) {
90112
// Retry after the previous ViewTransition finishes.
91113
existingTransition.finished.finally(window['$RV'].bind(null, batch));
92114
return;
93115
}
94-
const shouldStartViewTransition = window['_useVT']; // TODO: Detect.
116+
// First collect all entering names that might form pairs exiting names.
117+
const appearingViewTransitions = new Map();
118+
for (let i = 1; i < batch.length; i += 2) {
119+
const contentNode = batch[i];
120+
const appearingElements = contentNode.querySelectorAll('[vt-share]');
121+
for (let j = 0; j < appearingElements.length; j++) {
122+
const appearingElement = appearingElements[j];
123+
appearingViewTransitions.set(
124+
appearingElement.getAttribute('vt-name'),
125+
appearingElement,
126+
);
127+
}
128+
}
129+
// Next we'll find the nodes that we're going to animate and apply names to them..
130+
for (let i = 0; i < batch.length; i += 2) {
131+
const suspenseIdNode = batch[i];
132+
const parentInstance = suspenseIdNode.parentNode;
133+
if (!parentInstance) {
134+
// We may have client-rendered this boundary already. Skip it.
135+
continue;
136+
}
137+
const parentRect = parentInstance.getBoundingClientRect();
138+
if (
139+
!parentRect.left &&
140+
!parentRect.top &&
141+
!parentRect.width &&
142+
!parentRect.height
143+
) {
144+
// If the parent instance is display: none then we don't animate this boundary.
145+
// This can happen when this boundary is actually a child of a different boundary that
146+
// isn't yet revealed or is about to be revealed, but in that case that boundary
147+
// should do the exit/enter and not this one. Conveniently this also lets us skip
148+
// this if it's just in a hidden tree in general.
149+
// TODO: Should we skip it if it's out of viewport? It's possible that it gets
150+
// brought into the viewport by changing size.
151+
// TODO: There's a another case where an inner boundary is inside a fallback that
152+
// is about to be deleted. In that case we should not run exit animations on the inner.
153+
continue;
154+
}
155+
156+
// Apply update animations to any parents and siblings that might be affected.
157+
let ancestorElement = parentInstance;
158+
do {
159+
let childElement = ancestorElement.firstElementChild;
160+
while (childElement) {
161+
// TODO: Bail out if we can
162+
// TODO: If we have already handled this element as part of another exit/enter/share, don't override.
163+
applyViewTransitionName(childElement, 'vt-update');
164+
childElement = childElement.nextElementSibling;
165+
}
166+
} while (
167+
(ancestorElement = ancestorElement.parentNode) &&
168+
ancestorElement.nodeType === ELEMENT_NODE &&
169+
ancestorElement.getAttribute('vt-update') !== 'none'
170+
);
171+
172+
// Apply exit animations to the immediate elements inside the fallback.
173+
let node = suspenseIdNode;
174+
let depth = 0;
175+
while (node) {
176+
if (node.nodeType === COMMENT_NODE) {
177+
const data = node.data;
178+
if (data === SUSPENSE_END_DATA) {
179+
if (depth === 0) {
180+
break;
181+
} else {
182+
depth--;
183+
}
184+
} else if (
185+
data === SUSPENSE_START_DATA ||
186+
data === SUSPENSE_PENDING_START_DATA ||
187+
data === SUSPENSE_QUEUED_START_DATA ||
188+
data === SUSPENSE_FALLBACK_START_DATA
189+
) {
190+
depth++;
191+
}
192+
} else if (node.nodeType === ELEMENT_NODE) {
193+
const exitElement = node;
194+
const exitName = exitElement.getAttribute('vt-name');
195+
const pairedElement = appearingViewTransitions.get(exitName);
196+
applyViewTransitionName(
197+
exitElement,
198+
pairedElement ? 'vt-share' : 'vt-exit',
199+
);
200+
if (pairedElement) {
201+
// Activate the other side as well.
202+
applyViewTransitionName(pairedElement, 'vt-share');
203+
appearingViewTransitions.set(exitName, null); // mark claimed
204+
}
205+
// Next we'll look inside this element for pairs to trigger "share".
206+
const disappearingElements =
207+
exitElement.querySelectorAll('[vt-share]');
208+
for (let j = 0; j < disappearingElements.length; j++) {
209+
const disappearingElement = disappearingElements[j];
210+
const name = disappearingElement.getAttribute('vt-name');
211+
const appearingElement = appearingViewTransitions.get(name);
212+
if (appearingElement) {
213+
applyViewTransitionName(disappearingElement, 'vt-share');
214+
applyViewTransitionName(appearingElement, 'vt-share');
215+
appearingViewTransitions.set(name, null); // mark claimed
216+
}
217+
}
218+
}
219+
node = node.nextSibling;
220+
}
221+
222+
// Apply enter animations to the new nodes about to be inserted.
223+
const contentNode = batch[i + 1];
224+
let enterElement = contentNode.firstElementChild;
225+
while (enterElement) {
226+
const paired =
227+
appearingViewTransitions.get(enterElement.getAttribute('vt-name')) ===
228+
null;
229+
if (!paired) {
230+
applyViewTransitionName(enterElement, 'vt-enter');
231+
}
232+
enterElement = enterElement.nextElementSibling;
233+
}
234+
}
95235
if (shouldStartViewTransition) {
96236
const transition = (document['__reactViewTransition'] = document[
97237
'startViewTransition'

0 commit comments

Comments
 (0)