238 lines
5.6 KiB
Markdown
238 lines
5.6 KiB
Markdown
---
|
|
title: View Transitions
|
|
---
|
|
|
|
# View Transitions
|
|
|
|
[MODES: framework, data]
|
|
|
|
<br/>
|
|
<br/>
|
|
|
|
Enable smooth animations between page transitions in your React Router applications using the [View Transitions API][view-transitions-api]. This feature allows you to create seamless visual transitions during client-side navigation.
|
|
|
|
## Basic View Transition
|
|
|
|
### 1. Enable view transitions on navigation
|
|
|
|
The simplest way to enable view transitions is by adding the `viewTransition` prop to your `Link`, `NavLink`, or `Form` components. This automatically wraps the navigation update in `document.startViewTransition()`.
|
|
|
|
```tsx
|
|
<Link to="/about" viewTransition>
|
|
About
|
|
</Link>
|
|
```
|
|
|
|
Without any additional CSS, this provides a basic cross-fade animation between pages.
|
|
|
|
### 2. Enable view transitions with programmatic navigation
|
|
|
|
When using programmatic navigation with the `useNavigate` hook, you can enable view transitions by passing the `viewTransition: true` option:
|
|
|
|
```tsx
|
|
import { useNavigate } from "react-router";
|
|
|
|
function NavigationButton() {
|
|
const navigate = useNavigate();
|
|
|
|
return (
|
|
<button
|
|
onClick={() =>
|
|
navigate("/about", { viewTransition: true })
|
|
}
|
|
>
|
|
About
|
|
</button>
|
|
);
|
|
}
|
|
```
|
|
|
|
This provides the same cross-fade animation as using the `viewTransition` prop on Link components.
|
|
|
|
For more information on using the View Transitions API, please refer to the ["Smooth transitions with the View Transition API" guide][view-transitions-guide] from the Google Chrome team.
|
|
|
|
## Image Gallery Example
|
|
|
|
Let's build an image gallery that demonstrates how to trigger and use view transitions. We'll create a list of images that expand into a detail view with smooth animations.
|
|
|
|
### 1. Create the image gallery route
|
|
|
|
```tsx filename=routes/image-gallery.tsx
|
|
import { NavLink } from "react-router";
|
|
|
|
export const images = [
|
|
"https://remix.run/blog-images/headers/the-future-is-now.jpg",
|
|
"https://remix.run/blog-images/headers/waterfall.jpg",
|
|
"https://remix.run/blog-images/headers/webpack.png",
|
|
// ... more images ...
|
|
];
|
|
|
|
export default function ImageGalleryRoute() {
|
|
return (
|
|
<div className="image-list">
|
|
<h1>Image List</h1>
|
|
<div>
|
|
{images.map((src, idx) => (
|
|
<NavLink
|
|
key={src}
|
|
to={`/image/${idx}`}
|
|
viewTransition // Enable view transitions for this link
|
|
>
|
|
<p>Image Number {idx}</p>
|
|
<img
|
|
className="max-w-full contain-layout"
|
|
src={src}
|
|
/>
|
|
</NavLink>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 2. Add transition styles
|
|
|
|
Define view transition names and animations for elements that should transition smoothly between routes.
|
|
|
|
```css filename=app.css
|
|
/* Layout styles for the image grid */
|
|
.image-list > div {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
column-gap: 10px;
|
|
}
|
|
|
|
.image-list h1 {
|
|
font-size: 2rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.image-list img {
|
|
max-width: 100%;
|
|
contain: layout;
|
|
}
|
|
|
|
.image-list p {
|
|
width: fit-content;
|
|
}
|
|
|
|
/* Assign transition names to elements during navigation */
|
|
.image-list a.transitioning img {
|
|
view-transition-name: image-expand;
|
|
}
|
|
|
|
.image-list a.transitioning p {
|
|
view-transition-name: image-title;
|
|
}
|
|
```
|
|
|
|
### 3. Create the image detail route
|
|
|
|
The detail view needs to use the same view transition names to create a seamless animation.
|
|
|
|
```tsx filename=routes/image-details.tsx
|
|
import { Link } from "react-router";
|
|
import { images } from "./home";
|
|
import type { Route } from "./+types/image-details";
|
|
|
|
export default function ImageDetailsRoute({
|
|
params,
|
|
}: Route.ComponentProps) {
|
|
return (
|
|
<div className="image-detail">
|
|
<Link to="/" viewTransition>
|
|
Back
|
|
</Link>
|
|
<h1>Image Number {params.id}</h1>
|
|
<img src={images[Number(params.id)]} />
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
### 4. Add matching transition styles for the detail view
|
|
|
|
```css filename=app.css
|
|
/* Match transition names from the list view */
|
|
.image-detail h1 {
|
|
font-size: 2rem;
|
|
font-weight: 600;
|
|
width: fit-content;
|
|
view-transition-name: image-title;
|
|
}
|
|
|
|
.image-detail img {
|
|
max-width: 100%;
|
|
contain: layout;
|
|
view-transition-name: image-expand;
|
|
}
|
|
```
|
|
|
|
## Advanced Usage
|
|
|
|
You can control view transitions more precisely using either render props or the `useViewTransitionState` hook.
|
|
|
|
### 1. Using render props
|
|
|
|
```tsx filename=routes/image-gallery.tsx
|
|
<NavLink to={`/image/${idx}`} viewTransition>
|
|
{({ isTransitioning }) => (
|
|
<>
|
|
<p
|
|
style={{
|
|
viewTransitionName: isTransitioning
|
|
? "image-title"
|
|
: "none",
|
|
}}
|
|
>
|
|
Image Number {idx}
|
|
</p>
|
|
<img
|
|
src={src}
|
|
style={{
|
|
viewTransitionName: isTransitioning
|
|
? "image-expand"
|
|
: "none",
|
|
}}
|
|
/>
|
|
</>
|
|
)}
|
|
</NavLink>
|
|
```
|
|
|
|
### 2. Using the `useViewTransitionState` hook
|
|
|
|
```tsx filename=routes/image-gallery.tsx
|
|
function NavImage(props: { src: string; idx: number }) {
|
|
const href = `/image/${props.idx}`;
|
|
// Hook provides transition state for specific route
|
|
const isTransitioning = useViewTransitionState(href);
|
|
|
|
return (
|
|
<Link to={href} viewTransition>
|
|
<p
|
|
style={{
|
|
viewTransitionName: isTransitioning
|
|
? "image-title"
|
|
: "none",
|
|
}}
|
|
>
|
|
Image Number {props.idx}
|
|
</p>
|
|
<img
|
|
src={props.src}
|
|
style={{
|
|
viewTransitionName: isTransitioning
|
|
? "image-expand"
|
|
: "none",
|
|
}}
|
|
/>
|
|
</Link>
|
|
);
|
|
}
|
|
```
|
|
|
|
[view-transitions-api]: https://developer.mozilla.org/en-US/docs/Web/API/ViewTransition
|
|
[view-transitions-guide]: https://developer.chrome.com/docs/web-platform/view-transitions
|