diff --git a/src/content/blog/2025/04/01/react-labs-what-we-have-been-working-on-april-2025.md b/src/content/blog/2025/04/01/react-labs-what-we-have-been-working-on-april-2025.md index 89dff2111..dd1d5ded5 100644 --- a/src/content/blog/2025/04/01/react-labs-what-we-have-been-working-on-april-2025.md +++ b/src/content/blog/2025/04/01/react-labs-what-we-have-been-working-on-april-2025.md @@ -92,7 +92,7 @@ const deferred = useDeferredValue(value); ``` -By default, these animations have the [default CSS animations for View Transitions](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#customizing_your_animations) applied (most are given a default smooth cross-fade). You can use [view transition psuedo-sectors](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#the_view_transition_pseudo-element_tree) to define "how" the animation runs: +By default, these animations have the [default CSS animations for View Transitions](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#customizing_your_animations) applied (most are given a default smooth cross-fade). You can use [view transition psuedo-sectors](https://developer.mozilla.org/en-US/docs/Web/API/View_Transition_API/Using#the_view_transition_pseudo-element_tree) to define "how" the animation runs. For example, using `*` we can change the default for all animations: ``` // ✅ "how" to animate. @@ -115,9 +115,7 @@ We'll start with an app like this: ```js src/App.js active -import TalkDetails from './Details'; -import Home from './Home'; -import {useRouter} from './router'; +import TalkDetails from './Details'; import Home from './Home'; import {useRouter} from './router'; export default function App() { const {url} = useRouter(); @@ -257,7 +255,7 @@ export default function Home() { ``` -```js src/Icons.js hidden +```js src/Icons.js export function ChevronLeft() { return ( { go(url); }); } function navigateBack(url) { + // ✅ Update router state in transition. startTransition(() => { go(url); }); @@ -647,7 +647,6 @@ export function Router({ children }) { return ( - -You may notice in this example there are already animations when you click the "like" button and the Suspense fallback glimmer. +#### View Transitions do not replace CSS and JS driven animations {/*view-transitions-do-not-replace-css-and-js-driven-animations*/} -This is expected! View Transitions are not meant to replace all of the animations in your app. They are meant to be used for the transitions between pages and between states of the same page. Animations driven by CSS and JS are still valid and should be used when appropriate. +View Transitions are meant to be used for UI transitions such as navigation, expanding, opening, or re-ordering. They are not meant to replace all the animations in your app. + +In our example app above, notice that there are already animations when you click the "like" button and the Suspense fallback glimmer. These are good use cases for CSS animations because they are animating a specific element. ### Animating navigations {/*animating-navigations*/} -Since our app includes a Suspense-enabled router, with [page transitions already marked as Transitions](/reference/react/useTransition#building-a-suspense-enabled-router), we can add `` to each page to animate the transition between them: +Our app includes a Suspense-enabled router, with [page transitions already marked as Transitions](/reference/react/useTransition#building-a-suspense-enabled-router), which means navigations are performed with `startTranstion`: + +```js {2} +function navigate(url) { + startTransition(() => { + go(url); + }); +} +``` + +`startTransition` is a View Transition trigger, so we can add `` to animate between pages: ```js {2,4} // "what" to animate - + {url === '/' ? : } ``` -When the `url` changes inside a Transition, the `` around the routes are activated. By default, View Transitions include the browser default cross-fade animation. +When the `url` changes, the `` and new route are rendered. Since the `` was updated inside of `startTransition`, the `` is activated for an animation. -Adding this to our example, we now have a cross-fade whenever we navigate between pages: + +By default, View Transitions include the browser default cross-fade animation. Adding this to our example, we now have a cross-fade whenever we navigate between pages: ```js src/App.js active -import {unstable_ViewTransition as ViewTransition} from 'react'; -import Details from './Details'; -import Home from './Home'; -import {useRouter} from './router'; +import {unstable_ViewTransition as ViewTransition} from 'react'; import Details from './Details'; import Home from './Home'; import {useRouter} from './router'; export default function App() { const {url} = useRouter(); - // ✅ Use ViewTransition to animate between pages + // ✅ Use ViewTransition to animate between pages. + // No additional CSS needed by default. return ( {url === '/' ? :
}
); } - ``` -```js src/Details.js +```js src/Details.js hidden import { fetchVideo, fetchVideoDetails } from "./data"; import { Thumbnail, VideoControls } from "./Videos"; import { useRouter } from "./router"; @@ -1370,7 +1378,7 @@ export default function Details({}) { ``` -```js src/Home.js +```js src/Home.js hidden import { Video } from "./Videos"; import Layout from "./Layout"; import { fetchVideos } from "./data"; @@ -1559,7 +1567,7 @@ export function IconSearch(props) { } ``` -```js src/Layout.js +```js src/Layout.js hidden import { useIsNavPending } from "./router"; export default function Page({ heading, children }) { @@ -1581,7 +1589,7 @@ export default function Page({ heading, children }) { } ``` -```js src/LikeButton.js +```js src/LikeButton.js hidden import {useState} from 'react'; import {Heart} from './Icons'; @@ -1612,7 +1620,7 @@ export default function LikeButton({video}) { } ``` -```js src/Videos.js +```js src/Videos.js hidden import { useState } from "react"; import LikeButton from "./LikeButton"; import { useRouter } from "./router"; @@ -1760,31 +1768,26 @@ export function fetchVideoDetails(id) { ``` ```js src/router.js -import { - useState, - createContext, - use, - useTransition, - useLayoutEffect, - useEffect, -} from "react"; - -const RouterContext = createContext({ url: "/", params: {} }); - -export function useRouter() { - return use(RouterContext); -} - -export function useIsNavPending() { - return use(RouterContext).isPending; -} +import {useState, createContext,use,useTransition,useLayoutEffect,useEffect} from "react"; export function Router({ children }) { + const [isPending, startTransition] = useTransition(); + + function navigate(url) { + // ✅ Update router state in transition. + startTransition(() => { + go(url); + }); + } + + + + const [routerState, setRouterState] = useState({ pendingNav: () => {}, url: document.location.pathname, }); - const [isPending, startTransition] = useTransition(); + function go(url) { setRouterState({ @@ -1794,11 +1797,7 @@ export function Router({ children }) { }, }); } - function navigate(url) { - startTransition(() => { - go(url); - }); - } + function navigateBack(url) { startTransition(() => { @@ -1831,7 +1830,6 @@ export function Router({ children }) { return ( ); } + +const RouterContext = createContext({ url: "/", params: {} }); + +export function useRouter() { + return use(RouterContext); +} + +export function useIsNavPending() { + return use(RouterContext).isPending; +} ``` -```css src/styles.css +```css src/styles.css hidden @font-face { font-family: Optimistic Text; src: url(https://react.dev/fonts/Optimistic_Text_W_Rg.woff2) format("woff2"); @@ -2450,6 +2458,9 @@ root.render(
+Since our router already updates the route using `startTransition`, this one line change to add `` activates with the default cross-fade animation. + +If you're curious how this works, see the docs for [How does `` work?](/reference/react/ViewTransition#how-does-viewtransition-work) ### Customizing animations {/*customizing-animations*/} @@ -2457,27 +2468,27 @@ By default, `` includes the default cross-fade from the browser. To customize animations, you can provide props to the `` component to specify which animations to use, based on how the `` activates (see [the docs](/TODO) for the heuristics React uses to activate different types of animations). -For example, you can specify an enter and exit animation for navigation: +For example, we can slow down the `default` cross fade animation: ```js - + ``` -The animations are specified in CSS using view transition classes: +And define `slow-fade` in CSS using [view transition classes](/reference/react/ViewTransition#view-transition-classes): ```css {1,5} -::view-transition-old(.slide-to-left) { - animation: 300ms ease-out both slide-to-left, 300ms ease-out both fade-out; +::view-transition-old(.slow-fade) { + animation-duration: 500ms; } -::view-transition-new(.slide-from-left) { - animation: 300ms ease-in both slide-from-left, 300ms ease-in both fade-in; +::view-transition-new(.slow-fade) { + animation-duration: 500ms; } ``` -Now, the navigation slides while is cross-fades: +Now, the cross fade is slower: @@ -2490,23 +2501,17 @@ import { useRouter } from "./router"; export default function App() { const { url } = useRouter(); - // ✅ Use ViewTransition to animate between pages - if (url === "/") { - return ( - - - - ); - } + // ✅ Define a default animation of .slow-fade. + // See animations.css for the animation definiton. return ( - -
+ + {url === '/' ? :
}
); } ``` -```js src/Details.js +```js src/Details.js hidden import { fetchVideo, fetchVideoDetails } from "./data"; import { Thumbnail, VideoControls } from "./Videos"; import { useRouter } from "./router"; @@ -2565,7 +2570,7 @@ export default function Details({}) { ``` -```js src/Home.js +```js src/Home.js hidden import { Video } from "./Videos"; import Layout from "./Layout"; import { fetchVideos } from "./data"; @@ -2754,7 +2759,7 @@ export function IconSearch(props) { } ``` -```js src/Layout.js +```js src/Layout.js hidden import { useIsNavPending } from "./router"; export default function Page({ heading, children }) { @@ -2776,7 +2781,7 @@ export default function Page({ heading, children }) { } ``` -```js src/LikeButton.js +```js src/LikeButton.js hidden import {useState} from 'react'; import {Heart} from './Icons'; @@ -2807,7 +2812,7 @@ export default function LikeButton({video}) { } ``` -```js src/Videos.js +```js src/Videos.js hidden import { useState } from "react"; import LikeButton from "./LikeButton"; import { useRouter } from "./router"; @@ -2954,7 +2959,7 @@ export function fetchVideoDetails(id) { } ``` -```js src/router.js +```js src/router.js hidden import { useState, createContext, @@ -2990,12 +2995,14 @@ export function Router({ children }) { }); } function navigate(url) { + // ✅ Update router state in transition. startTransition(() => { go(url); }); } function navigateBack(url) { + // ✅ Update router state in transition. startTransition(() => { go(url); }); @@ -3026,7 +3033,6 @@ export function Router({ children }) { return (
``` -Now, we can remove the slide animation, and just have the video thumbnail animate between the two pages: +Now the video thumbnail animates between the two pages: @@ -3733,9 +3691,11 @@ import { useRouter } from "./router"; export default function App() { const { url } = useRouter(); - // ✅ Default cross-fade animation. + // ✅ Keeping our default slow-fade. + // This allows the content not in the shared + // element transition to cross-fade. return ( - + {url === "/" ? :
}
); @@ -4043,15 +4003,12 @@ export default function LikeButton({video}) { } ``` -```js src/Videos.js -import { useState, unstable_ViewTransition as ViewTransition } from "react"; -import LikeButton from "./LikeButton"; -import { useRouter } from "./router"; -import { PauseIcon, PlayIcon } from "./Icons"; -import { startTransition } from "react"; +```js src/Videos.js active +import { useState, unstable_ViewTransition as ViewTransition } from "react"; import LikeButton from "./LikeButton"; import { useRouter } from "./router"; import { PauseIcon, PlayIcon } from "./Icons"; import { startTransition } from "react"; export function Thumbnail({ video, children }) { - // ✅ Animated with a shared element transition + // ✅ Add a name to animate with a shared element transition. + // This uses the default animation, no additional css needed. return (
{ go(url); }); } function navigateBack(url) { + // ✅ Update router state in transition. startTransition(() => { go(url); }); @@ -4265,7 +4224,6 @@ export function Router({ children }) { return (