Skip to content

Commit dfa152b

Browse files
authored
[ViewTransition] Add docs for using with Activity (#8320)
1 parent e8ef063 commit dfa152b

File tree

1 file changed

+136
-7
lines changed

1 file changed

+136
-7
lines changed

src/content/reference/react/ViewTransition.md

Lines changed: 136 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -259,10 +259,10 @@ In the future, CSS libraries may add built-in animations using View Transition C
259259

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

262-
```js
262+
```js {3}
263263
function Child() {
264264
return (
265-
<ViewTransition>
265+
<ViewTransition enter="auto" exit="auto" default="none">
266266
<div>Hi</div>
267267
</ViewTransition>
268268
);
@@ -299,7 +299,6 @@ export function Video({video}) {
299299
<div className="video">
300300
<div className="link">
301301
<Thumbnail video={video}></Thumbnail>
302-
303302
<div className="info">
304303
<div className="video-title">{video.title}</div>
305304
<div className="video-description">{video.description}</div>
@@ -317,7 +316,7 @@ import videos from './data';
317316

318317
function Item() {
319318
return (
320-
<ViewTransition>
319+
<ViewTransition enter="auto" exit="auto" default="none">
321320
<Video video={videos[0]} />
322321
</ViewTransition>
323322
);
@@ -448,18 +447,148 @@ button:hover {
448447

449448
<Pitfall>
450449

451-
`<ViewTransition>` only activates if it is placed before any DOM node. If `Child` instead looked like this, no animation would trigger:
450+
#### Only top-level ViewTransitions animate on exit/enter {/*only-top-level-viewtransition-animates-on-exit-enter*/}
451+
452+
`<ViewTransition>` only activates exit/enter if it is placed _before_ any DOM nodes.
453+
454+
If there's a `<div>` above `<ViewTransition>`, no exit/enter animations trigger:
452455

453456
```js [3, 5]
454-
function Component() {
455-
return <ViewTransition>Hi</ViewTransition>;
457+
function Item() {
458+
return (
459+
<div> {/* 🚩<div> above <ViewTransition> breaks exit/enter */}
460+
<ViewTransition enter="auto" exit="auto" default="none">
461+
<Video video={videos[0]} />
462+
</ViewTransition>
463+
</div>
464+
);
456465
}
457466
```
458467

468+
This constraint prevents subtle bugs where too much or too little animates.
469+
459470
</Pitfall>
460471

461472
---
462473

474+
### Animating enter/exit with Activity {/*animating-enter-exit-with-activity*/}
475+
476+
If you want to animate a component in and out while preserving its state, or pre-rendering content for an animation, you can use [`<Activity>`](/reference/react/Activity). When a `<ViewTransition>` inside an `<Activity>` becomes visible, the `enter` animation activates. When it becomes hidden, the `exit` animation activates:
477+
478+
```js
479+
<Activity mode={isVisible ? 'visible' : 'hidden'}>
480+
<ViewTransition enter="auto" exit="auto">
481+
<Counter />
482+
</ViewTransition>
483+
</Activity>
484+
485+
```
486+
487+
In this example, `Counter` has a counter with internal state. Try incrementing the counter, hiding it, then showing it again. The counter's value is preserved while the sidebar animates in and out:
488+
489+
<Sandpack>
490+
491+
```js
492+
import { Activity, ViewTransition, useState, startTransition } from 'react';
493+
494+
export default function App() {
495+
const [show, setShow] = useState(true);
496+
return (
497+
<div className="layout">
498+
<Toggle show={show} setShow={setShow} />
499+
<Activity mode={show ? 'visible' : 'hidden'}>
500+
<ViewTransition enter="auto" exit="auto" default="none">
501+
<Counter />
502+
</ViewTransition>
503+
</Activity>
504+
</div>
505+
);
506+
}
507+
function Toggle({show, setShow}) {
508+
return (
509+
<button
510+
className="toggle"
511+
onClick={() => {
512+
startTransition(() => {
513+
setShow(s => !s);
514+
});
515+
}}>
516+
{show ? 'Hide' : 'Show'}
517+
</button>
518+
)
519+
}
520+
function Counter() {
521+
const [count, setCount] = useState(0);
522+
return (
523+
<div className="counter">
524+
<h2>Counter</h2>
525+
<p>Count: {count}</p>
526+
<button onClick={() => setCount(count + 1)}>
527+
Increment
528+
</button>
529+
</div>
530+
);
531+
}
532+
533+
```
534+
535+
```css
536+
.layout {
537+
display: flex;
538+
flex-direction: column;
539+
align-items: flex-start;
540+
gap: 10px;
541+
min-height: 200px;
542+
}
543+
.counter {
544+
padding: 15px;
545+
background: #f0f4f8;
546+
border-radius: 8px;
547+
width: 200px;
548+
}
549+
.counter h2 {
550+
margin: 0 0 10px 0;
551+
font-size: 16px;
552+
}
553+
.counter p {
554+
margin: 0 0 10px 0;
555+
}
556+
.toggle {
557+
padding: 8px 16px;
558+
border: 1px solid #ccc;
559+
border-radius: 6px;
560+
background: #f0f8ff;
561+
cursor: pointer;
562+
font-size: 14px;
563+
}
564+
.toggle:hover {
565+
background: #e0e8ff;
566+
}
567+
.counter button {
568+
padding: 4px 12px;
569+
border: 1px solid #ccc;
570+
border-radius: 4px;
571+
background: white;
572+
cursor: pointer;
573+
}
574+
```
575+
576+
```json package.json hidden
577+
{
578+
"dependencies": {
579+
"react": "canary",
580+
"react-dom": "canary",
581+
"react-scripts": "latest"
582+
}
583+
}
584+
```
585+
586+
</Sandpack>
587+
588+
Without `<Activity>`, the counter would reset to `0` every time the sidebar reappears.
589+
590+
---
591+
463592
### Animating a shared element {/*animating-a-shared-element*/}
464593

465594
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.

0 commit comments

Comments
 (0)