Skip to content

Commit 4573dc7

Browse files
committed
Start on vt examples
1 parent bf093e9 commit 4573dc7

File tree

1 file changed

+304
-15
lines changed

1 file changed

+304
-15
lines changed

src/content/reference/react/ViewTransition.md

Lines changed: 304 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -157,30 +157,108 @@ In the future, CSS libraries may add built-in animations using View Transition C
157157

158158
Enter/Exit Transitions trigger when a `<ViewTransition>` is added or removed by a component in a transition:
159159

160-
```js [[1, 10, "startTransition"], [2, 2, "<ViewTransition>"], [2, 2, "</ViewTransition>"]]
160+
```js
161161
function Child() {
162-
return <ViewTransition><div>Hello</div></ViewTransition>;
162+
return <ViewTransition>Hi</ViewTransition>
163163
}
164164

165165
function Parent() {
166-
const [show, setShow] = useState(false);
166+
const [show, setShow] = useState();
167+
if (show) {
168+
return <Child />;
169+
}
170+
return null;
171+
}
172+
```
173+
174+
When `setShow` is called, `show` switched to `true` and the `Child` component is rendered. Since `setShow` is called inside <CodeStep step={1}>startTransition</CodeStep>, and `Child` renders a <CodeStep step={2}>ViewTransition</CodeStep> before any other DOM nodes, an `enter` animation is triggered.
175+
176+
When `show` is switched back to `false`, an `exit` animation is triggered.
177+
178+
<Sandpack>
179+
180+
```js
181+
import {
182+
unstable_ViewTransition as ViewTransition,
183+
useState,
184+
startTransition
185+
} from 'react';
186+
187+
function Item() {
167188
return (
168-
<>
169-
<button onClick={() => {
170-
startTransition(() => {
171-
setShow(true);
172-
});
173-
}}>Show</button>
189+
<ViewTransition>
190+
<div className="item">Hello world</div>
191+
</ViewTransition>
192+
);
193+
}
174194

175-
{show && <Component />}
195+
export default function Component() {
196+
const [showItem, setShowItem] = useState(false);
197+
return (
198+
<>
199+
<button
200+
onClick={() => {
201+
startTransition(() => {
202+
setShowItem((prev) => !prev);
203+
});
204+
}}
205+
>{showItem ? '' : ''}</button>
206+
207+
{showItem ? <Item /> : null}
176208
</>
177209
);
178210
}
179211
```
180212

181-
When `setShow` is called, `show` switched to `true` and the `Child` component is rendered. Since `setShow` is called inside <CodeStep step={1}>startTransition</CodeStep>, and `Child` renders a <CodeStep step={2}>ViewTransition</CodeStep> before any other DOM nodes, an `enter` animation is triggered.
213+
```css
214+
#root {
215+
display: flex;
216+
flex-direction: column;
217+
align-items: center;
218+
min-height: 150px;
219+
}
220+
button {
221+
border: none;
222+
border-radius: 50%;
223+
width: 50px;
224+
height: 50px;
225+
display: flex;
226+
justify-content: center;
227+
align-items: center;
228+
background-color: #f0f8ff;
229+
color: white;
230+
font-size: 20px;
231+
cursor: pointer;
232+
transition: background-color 0.3s, border 0.3s;
233+
}
234+
button:hover {
235+
border: 2px solid #ccc;
236+
background-color: #e0e8ff;
237+
}
238+
.item {
239+
width: 100%;
240+
padding: 10px 20px;
241+
margin: 10px 0;
242+
border-radius: 50px;
243+
background-color: #f0f8ff;
244+
color: #333;
245+
font-size: 16px;
246+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
247+
height: 40px;
248+
}
249+
```
182250

183-
When `show` is switched back to `false`, an `exit` animation is triggered.
251+
```json package.json hidden
252+
{
253+
"dependencies": {
254+
"react": "experimental",
255+
"react-dom": "experimental",
256+
"react-scripts": "latest"
257+
}
258+
}
259+
```
260+
261+
</Sandpack>
184262

185263
<Pitfall>
186264

@@ -203,16 +281,110 @@ function Component() {
203281

204282
Normally, we don't recommend assigning a name to a `<ViewTransition>` and instead let React assign it an automatic name. The reason you might want to assign a name is to animate between completely different components when one tree unmounts and another tree mounts at the same time. To preserve continuity.
205283

284+
```js
285+
<ViewTransition name={UNIQUE_NAME}>
286+
<Child />
287+
</ViewTransition>
288+
```
289+
206290
When one tree unmounts and another mounts, if there's a pair where the same name exists in the unmounting tree and the mounting tree, they trigger the "share" animation on both. It animates from the unmounting side to the mounting side.
207291

208292
Unlike an exit/enter animation this can be deeply inside the deleted/mounted tree. If a `<ViewTransition>` would also be eligible for exit/enter, then the "share" animation takes precedence.
209293

210-
If Transition first unmounts one side an then leads to a `<Suspense>` fallback being shown before eventually the new name being mounted, then no shared element transition happens.
294+
If Transition first unmounts one side and then leads to a `<Suspense>` fallback being shown before eventually the new name being mounted, then no shared element transition happens.
295+
296+
<Sandpack>
297+
298+
```js
299+
import {
300+
unstable_ViewTransition as ViewTransition,
301+
useState,
302+
startTransition
303+
} from 'react';
304+
305+
export default function Component() {
306+
const [position, setPosition] = useState(true);
307+
return (
308+
<>
309+
<button
310+
onClick={() => {
311+
startTransition(() => {
312+
setPosition((prev) => !prev);
313+
});
314+
}}
315+
>{position ? "⬇️" : "⬆️"}</button>
316+
<div className="item">
317+
{position && (
318+
<ViewTransition name="hello_world"><div>Hello</div></ViewTransition>
319+
)}
320+
</div>
321+
<div className="item">
322+
{!position && (
323+
<ViewTransition name="hello_world"><div>World</div></ViewTransition>
324+
)}
325+
</div>
326+
</>
327+
);
328+
}
329+
330+
```
331+
332+
```css
333+
#root {
334+
display: flex;
335+
flex-direction: column;
336+
align-items: center;
337+
min-height: 150px;
338+
}
339+
button {
340+
border: none;
341+
border-radius: 50%;
342+
width: 50px;
343+
height: 50px;
344+
display: flex;
345+
justify-content: center;
346+
align-items: center;
347+
background-color: #f0f8ff;
348+
color: white;
349+
font-size: 20px;
350+
cursor: pointer;
351+
transition: background-color 0.3s, border 0.3s;
352+
}
353+
button:hover {
354+
border: 2px solid #ccc;
355+
background-color: #e0e8ff;
356+
}
357+
.item {
358+
width: 100%;
359+
padding: 10px 20px;
360+
margin: 10px 0;
361+
border-radius: 50px;
362+
background-color: #f0f8ff;
363+
color: #333;
364+
font-size: 16px;
365+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
366+
height: 40px;
367+
}
368+
```
369+
370+
371+
```json package.json hidden
372+
{
373+
"dependencies": {
374+
"react": "experimental",
375+
"react-dom": "experimental",
376+
"react-scripts": "latest"
377+
}
378+
}
379+
```
380+
381+
</Sandpack>
211382

212-
If either the mounted or unmounted side of a pair is outside the viewport, then no pair is formed. This ensures that it doesn't fly in or out of the viewport when something is scrolled. Instead it's treated as a regular enter/exit by itself.
213383

214384
<Note>
215385

386+
If either the mounted or unmounted side of a pair is outside the viewport, then no pair is formed. This ensures that it doesn't fly in or out of the viewport when something is scrolled. Instead it's treated as a regular enter/exit by itself.
387+
216388
This does not happen if the same Component instance changes position, which triggers an "update". Those animate regardless if one position is outside the viewport.
217389

218390
There's currently a quirk where if a deeply nested unmounted `<ViewTransition>` is inside the viewport but the mounted side is not within the viewport, then the unmounted side animates as its own "exit" animation even if it's deeply nested instead of as part of the parent animation.
@@ -261,11 +433,128 @@ function Component() {
261433
```
262434
Instead, any parent `<ViewTransition>` would cross-fade. If there is no parent `<ViewTransition>` then there's no animation in that case.
263435

264-
This means you might want to avoid a wrapper elements in lists where you want to allow the Component to control its own reorder animation:
436+
<Sandpack>
437+
438+
```js
439+
import {
440+
unstable_ViewTransition as ViewTransition,
441+
useState,
442+
startTransition
443+
} from 'react';
444+
445+
function Item({color}) {
446+
return <div className="item" style={{backgroundColor: color[1]}}>{color[0]}</div>;
447+
}
448+
449+
function List({animateEach, items}) {
450+
return items.map(item => {
451+
if (animateEach) {
452+
return <ViewTransition key={item}><Item color={item} /></ViewTransition>
453+
}
454+
return <Item color={item} key={item} />
455+
})
456+
}
457+
458+
export default function Component() {
459+
const [items, setItems] = useState([
460+
['Yellow', 'yellow'],
461+
['Green', 'lightgreen'],
462+
['Blue', 'lightblue'],
463+
['Orange', 'orange'],
464+
]);
465+
466+
const reorder = () => {
467+
startTransition(() => {
468+
setItems((prev) => {
469+
return [...prev.sort(() => Math.random() - 0.5)];
470+
});
471+
});
472+
};
473+
474+
return (
475+
<>
476+
<button onClick={reorder}>🎲</button>
477+
<div className="listContainer">
478+
<div>
479+
<p>Animates each item</p>
480+
<List animateEach={true} items={items} />
481+
</div>
482+
<div>
483+
<p>Animates the whole list</p>
484+
<ViewTransition>
485+
<List animateEach={false} items={items} />
486+
</ViewTransition>
487+
</div>
488+
</div>
489+
</>
490+
);
491+
}
492+
```
493+
494+
```css
495+
#root {
496+
display: flex;
497+
flex-direction: column;
498+
align-items: center;
499+
min-height: 150px;
500+
}
501+
button {
502+
border: none;
503+
border-radius: 50%;
504+
width: 50px;
505+
height: 50px;
506+
display: flex;
507+
justify-content: center;
508+
align-items: center;
509+
background-color: #f0f8ff;
510+
color: white;
511+
font-size: 20px;
512+
cursor: pointer;
513+
transition: background-color 0.3s, border 0.3s;
514+
}
515+
button:hover {
516+
border: 2px solid #ccc;
517+
background-color: #e0e8ff;
518+
}
519+
.item {
520+
width: 100%;
521+
padding: 10px 20px;
522+
margin: 10px 0;
523+
border-radius: 50px;
524+
background-color: #f0f8ff;
525+
color: #333;
526+
font-size: 16px;
527+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
528+
height: 40px;
529+
}
530+
.listContainer {
531+
display: flex;
532+
width: 100%;
533+
}
534+
.listContainer > div {
535+
width: 100%;
536+
margin: 20px;
537+
}
538+
```
539+
540+
```json package.json hidden
541+
{
542+
"dependencies": {
543+
"react": "experimental",
544+
"react-dom": "experimental",
545+
"react-scripts": "latest"
546+
}
547+
}
548+
```
549+
550+
</Sandpack>
551+
552+
This means you might want to avoid wrapper elements in lists where you want to allow the Component to control its own reorder animation:
265553

266554
```
267555
items.map(item => <div><Component key={item.id} item={item} /></div>)
268556
```
557+
269558
The above rule also applies if one of the items updates to resize, which then causes the siblings to resize, it'll also animate its sibling `<ViewTransition>` but only if they're immediate siblings.
270559

271560
This means that during an update, which causes a lot of re-layout, it doesn't individually animate every `<ViewTransition>` on the page. That would lead to a lot of noisy animations which distracts from the actual change. Therefore React is more conservative about when an individual animation triggers.

0 commit comments

Comments
 (0)