|
1 | 1 | # Route transition animations |
2 | 2 |
|
3 | | -Routing enables users to navigate between different routes in an application. |
| 3 | +When a user navigates from one route to another, the Angular Router maps the URL path to the relevant component and displays its view. Animating this route transition can greatly enhance the user experience. The Router has support for the View Transitions API when navigating between routes in Chrome/Chromium browsers. |
4 | 4 |
|
5 | | -## Enable routing transition animation |
| 5 | +HELPFUL: The Router's native View Transitions integraiton is currently in [developer preview](/reference/releases#developer-preview). Native View Transitions are also a relatively new feature so there may be limited support in some browsers. |
6 | 6 |
|
7 | | -When a user navigates from one route to another, the Angular router maps the URL path to a relevant component and displays its view. |
8 | | -Animating this route transition can greatly enhance the user experience. |
| 7 | +## How View Transitions work |
9 | 8 |
|
10 | | -The Angular router comes with high-level animation functions that let you animate the transitions between views when a route changes. |
11 | | -To produce an animation sequence when switching between routes, you need to define nested animation sequences. |
12 | | -Start with the top-level component that hosts the view, and nest animations in the components that host the embedded views. |
| 9 | +The native browser method that’s used for view transitions is `document.startViewTransition`. When `startViewTransition()` is called, the browser captures the current state of the page which includes taking a screenshot. The method takes a callback that updates the DOM and this function can be asynchronous. The new state is captured and the transition begins in the next animation frame when the promise returned by the callback resolves. |
13 | 10 |
|
14 | | -To enable routing transition animation, do the following: |
15 | | - |
16 | | -1. Create a routing configuration that defines the possible routes. For NgModule based applications, this will include creating a `RouterModule` and adding it to the main `AppModule`. |
17 | | -1. Add a router outlet to tell the Angular router where to place the activated components in the DOM. |
18 | | -1. Define the animation. |
19 | | - |
20 | | -Illustrate a router transition animation by navigating between two routes, *Home* and *About* associated with the `HomeComponent` and `AboutComponent` views respectively. |
21 | | -Both of these component views are children of the top-most view, hosted by `AppComponent`. |
22 | | -Implement a router transition animation that slides in the new view to the right and slides out the old view when navigating between the two routes. |
23 | | - |
24 | | -<img alt="Animations in action" width="440" src="assets/images/guide/animations/route-animation.gif"> |
25 | | - |
26 | | -## Route configuration |
27 | | - |
28 | | -To begin, configure a set of routes. This route configuration tells the router how to navigate. |
29 | | - |
30 | | -Create a `Routes` array to define a set of routes. Add the routes to the `provideRouter` function in the providers array of the `bootstrapApplication` function call in `main.ts`. |
| 11 | +Here’s an example of the startViewTransition api: |
31 | 12 |
|
32 | 13 | ```ts |
33 | | -bootstrapApplication(AppComponent, { |
34 | | - providers: [ |
35 | | - provideRouter(appRoutes), |
36 | | - ] |
| 14 | +document.startViewTransition(async () => { |
| 15 | + await updateTheDOMSomehow(); |
37 | 16 | }); |
38 | 17 | ``` |
39 | 18 |
|
40 | | -Note: For `NgModule` based applications: |
41 | | -Use the `RouterModule.forRoot` method to define a set of routes. |
42 | | -Also, add `RouterModule` to the `imports` array of the main module, `AppModule`. |
43 | | - |
44 | | -Use the `RouterModule.forRoot` method in the root module, `AppModule`, to register top-level application routes and providers. |
45 | | -For feature modules, call the `RouterModule.forChild` method instead. |
| 19 | +If you’re curious to read more about the details of the browser API, the [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions) is an invaluable resource. |
46 | 20 |
|
47 | | -The following configuration defines the possible routes for the application. |
| 21 | +## How the Router uses view transitions |
48 | 22 |
|
49 | | -<docs-code header="src/app/app.routes.ts" path="adev/src/content/examples/animations/src/app/app.routes.ts" visibleRegion="route-animation-data"/> |
| 23 | +Several things happen after navigation starts in the router: route matching, loading lazy routes and components, executing guards and resolvers to name a few. Once these have completed successfully, the new routes are ready to be activated. This route activation is the DOM update that we want to perform as part of the view transition. |
50 | 24 |
|
51 | | -The `home` and `about` paths are associated with the `HomeComponent` and `AboutComponent` views. |
52 | | -The route configuration tells the Angular router to instantiate the `HomeComponent` and `AboutComponent` views when the navigation matches the corresponding path. |
| 25 | +When the view transition feature is enabled, navigation “pauses” and a call is made to the browser’s `startViewTransition` method. Once the `startViewTransition` callback executes (this happens asynchronously, as outlined in the spec here), navigation “resumes”. The remaining steps for the router navigation include updating the browser URL and activating or deactivating the matched routes (the DOM update). |
53 | 26 |
|
54 | | -The `data` property of each route defines the key animation-specific configuration associated with a route. |
55 | | -The `data` property value is passed into `AppComponent` when the route changes. |
| 27 | +Finally, the callback passed to `startViewTransition` returns a Promise that resolves once Angular has finished rendering. As described above, this indicates to the browser that the new DOM state should be captured and the transition should begin. |
56 | 28 |
|
57 | | -HELPFUL: The `data` property names that you use can be arbitrary. |
58 | | -For example, the name *animation* used in the preceding example is an arbitrary choice. |
| 29 | +View transitions are a [progressive enhancement](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). If the browser does not support the API, the Router will perform the DOM updates without calling `startViewTransition` and the navigation will not be animated. |
59 | 30 |
|
60 | | -## Router outlet |
| 31 | +## Enabling View Transitions in the Router |
61 | 32 |
|
62 | | -After configuring the routes, add a `<router-outlet>` inside the root `AppComponent` template. |
63 | | -The `<router-outlet>` directive tells the Angular router where to render the views when matched with a route. |
| 33 | +To enable this feature, simply add `withViewTransitions` to the `provideRouter` or set `enableViewTransitions: true` in `RouterModule.forRoot`: |
64 | 34 |
|
65 | | -The `ChildrenOutletContexts` holds information about outlets and activated routes. |
66 | | -The `data` property of each `Route` can be used to animate routing transitions. |
67 | | - |
68 | | -<docs-code header="src/app/app.component.html" path="adev/src/content/examples/animations/src/app/app.component.html" visibleRegion="route-animations-outlet"/> |
| 35 | +```ts |
| 36 | +// Standalone bootstrap |
| 37 | +bootstrapApplication(MyApp, {providers: [ |
| 38 | + provideRouter(ROUTES, withViewTransitions()), |
| 39 | +]}); |
| 40 | + |
| 41 | +// NgModule bootstrap |
| 42 | +@NgModule({ |
| 43 | + imports: [RouterModule.forRoot(routes, {enableViewTransitions: true})] |
| 44 | +}) |
| 45 | +export class AppRouting {} |
| 46 | +``` |
69 | 47 |
|
70 | | -`AppComponent` defines a method that can detect when a view changes. |
71 | | -The method assigns an animation state value to the animation trigger \(`@routeAnimation`\) based on the route configuration `data` property value. |
72 | | -Here's an example of an `AppComponent` method that detects when a route change happens. |
| 48 | +[Try the “count” example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-2dnvtm?file=src%2Fmain.ts) |
| 49 | + |
| 50 | +This example uses the counter application from the Chrome explainer and replaces the direct call to startViewTransition when the counter increments with a router navigation. |
| 51 | + |
| 52 | +## Using CSS to customize transitions |
| 53 | + |
| 54 | +View transitions can be customized with CSS. We can also instruct the browser to create separate elements for the transition by setting a view-transition-name. We can expand the first example by adding view-transition-name: count to the .count style in the Counter component. Then, in the global styles, we can define a custom animation for this view transition: |
| 55 | + |
| 56 | +```css |
| 57 | +/* Custom transition */ |
| 58 | +@keyframes rotate-out { |
| 59 | + to { |
| 60 | + transform: rotate(90deg); |
| 61 | + } |
| 62 | +} |
| 63 | +@keyframes rotate-in { |
| 64 | + from { |
| 65 | + transform: rotate(-90deg); |
| 66 | + } |
| 67 | +} |
| 68 | +::view-transition-old(count), |
| 69 | +::view-transition-new(count) { |
| 70 | + animation-duration: 200ms; |
| 71 | + animation-name: -ua-view-transition-fade-in, rotate-in; |
| 72 | +} |
| 73 | +::view-transition-old(count) { |
| 74 | + animation-name: -ua-view-transition-fade-out, rotate-out; |
| 75 | +} |
| 76 | +``` |
73 | 77 |
|
74 | | -<docs-code header="src/app/app.component.ts" path="adev/src/content/examples/animations/src/app/app.component.ts" visibleRegion="get-route-animations-data"/> |
| 78 | +It is important that the view transition animations are defined in a global style file. They cannot be defined in the component styles because the default view encapsulation will scope the styles to the component. |
75 | 79 |
|
76 | | -The `getRouteAnimationData()` method takes the value of the outlet. It returns a string that represents the state of the animation based on the custom data of the current active route. |
77 | | -Use this data to control which transition to run for each route. |
| 80 | +[Try the updated “count” example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-fwn4i7?file=src%2Fmain.ts) |
78 | 81 |
|
79 | | -## Animation definition |
| 82 | +## Controlling transitions with onViewTransitionCreated |
80 | 83 |
|
81 | | -Animations can be defined directly inside your components. |
82 | | -For this example you are defining the animations in a separate file, which allows re-use of animations. |
| 84 | +The `withViewTransitions` router feature can also be called with an options object that includes an `onViewTransitionCreated` callback. This callback is run in an [injection context](/guide/di/dependency-injection-context#run-within-an-injection-context) and receives a [ViewTransitionInfo](/api/router/ViewTransitionInfo) object that includes the `ViewTransition` returned from `startViewTransition`, as well as the `ActivatedRouteSnapshot` that the navigation is transitioning from and the new one that it is transitioning to. |
83 | 85 |
|
84 | | -The following code snippet defines a reusable animation named `slideInAnimation`. |
| 86 | +This callback can be used for any number of customizations. For example, you might want to skip transitions under certain conditions. We use this on the new angular.dev docs site: |
85 | 87 |
|
86 | | -<docs-code header="src/app/animations.ts" path="adev/src/content/examples/animations/src/app/animations.ts" visibleRegion="route-animations"/> |
| 88 | +```ts |
| 89 | +withViewTransitions({ |
| 90 | + onViewTransitionCreated: ({transition}) => { |
| 91 | + const router = inject(Router); |
| 92 | + const targetUrl = router.getCurrentNavigation()!.finalUrl!; |
| 93 | + // Skip the transition if the only thing |
| 94 | + // changing is the fragment and queryParams |
| 95 | + const config = { |
| 96 | + paths: 'exact', |
| 97 | + matrixParams: 'exact', |
| 98 | + fragment: 'ignored', |
| 99 | + queryParams: 'ignored', |
| 100 | + }; |
| 101 | + |
| 102 | + if (router.isActive(targetUrl, config)) { |
| 103 | + transition.skipTransition(); |
| 104 | + } |
| 105 | + }, |
| 106 | +}), |
| 107 | +``` |
87 | 108 |
|
88 | | -The animation definition performs the following tasks: |
| 109 | +In this code snippet, we create a `UrlTree` from the `ActivatedRouteSnapshot` the navigation is going to. We then check with the Router to see if this `UrlTree` is already active, ignoring any differences in the fragment or query parameters. If it is already active, we call skipTransition which will skip the animation portion of the view transition. This is the case when clicking on an anchor link that will only scroll to another location in the same document. |
89 | 110 |
|
90 | | -* Defines two transitions \(a single `trigger` can define multiple states and transitions\) |
91 | | -* Adjusts the styles of the host and child views to control their relative positions during the transition |
92 | | -* Uses `query()` to determine which child view is entering and which is leaving the host view |
| 111 | +## Examples from the Chrome explainer adapted to Angular |
93 | 112 |
|
94 | | -A route change activates the animation trigger, and a transition matching the state change is applied. |
| 113 | +We’ve recreated some of the great examples from the Chrome Team in Angular for you to explore. |
95 | 114 |
|
96 | | -HELPFUL: The transition states must match the `data` property value defined in the route configuration. |
| 115 | +### Transitioning elements don’t need to be the same DOM element |
97 | 116 |
|
98 | | -Make the animation definition available in your application by adding the reusable animation \(`slideInAnimation`\) to the `animations` metadata of the `AppComponent`. |
| 117 | +* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#transitioning_elements_dont_need_to_be_the_same_dom_element) |
| 118 | +* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-dh8npr?file=src%2Fmain.ts) |
99 | 119 |
|
100 | | -<docs-code header="src/app/app.component.ts" path="adev/src/content/examples/animations/src/app/app.component.ts" visibleRegion="define"/> |
| 120 | +### Custom entry and exit animations |
101 | 121 |
|
102 | | -### Style the host and child components |
| 122 | +* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#custom_entry_and_exit_transitions) |
| 123 | +* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-8kly3o) |
103 | 124 |
|
104 | | -During a transition, a new view is inserted directly after the old one and both elements appear on screen at the same time. |
105 | | -To prevent this behavior, update the host view to use relative positioning. |
106 | | -Then, update the removed and inserted child views to use absolute positioning. |
107 | | -Adding these styles to the views animates the containers in place and prevents one view from affecting the position of the other on the page. |
| 125 | +### Async DOM updates and waiting for content |
108 | 126 |
|
109 | | -<docs-code header="src/app/animations.ts (excerpt)" path="adev/src/content/examples/animations/src/app/animations.ts" visibleRegion="style-view"/> |
| 127 | +* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#async_dom_updates_and_waiting_for_content) |
110 | 128 |
|
111 | | -### Query the view containers |
| 129 | +> During this time, the page is frozen, so delays here should be kept to a minimum…in some cases it’s better to avoid the delay altogether, and use the content you already have. |
112 | 130 |
|
113 | | -Use the `query()` method to find and animate elements within the current host component. |
114 | | -The `query(":enter")` statement returns the view that is being inserted, and `query(":leave")` returns the view that is being removed. |
| 131 | +The view transition feature in the Angular router does not provide a way to delay the animation. For the moment, our stance is that it’s always better to use the content you have rather than making the page non-interactive for any additional amount of time. |
115 | 132 |
|
116 | | -Assume that you are routing from the *Home => About*. |
| 133 | +### Handle multiple view transition styles with view transition types |
117 | 134 |
|
118 | | -<docs-code header="src/app/animations.ts (excerpt)" path="adev/src/content/examples/animations/src/app/animations.ts" visibleRegion="query"/> |
| 135 | +* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#view-transition-types) |
| 136 | +* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-vxzcam) |
119 | 137 |
|
120 | | -The animation code does the following after styling the views: |
| 138 | +### Handle multiple view transition styles with a class name on the view transition root (deprecated) |
121 | 139 |
|
122 | | -1. `query(':enter', style({ left: '-100%' }))` matches the view that is added and hides the newly added view by positioning it to the far left. |
123 | | -1. Calls `animateChild()` on the view that is leaving, to run its child animations. |
124 | | -1. Uses [`group()`](api/animations/group) function to make the inner animations run in parallel. |
125 | | -1. Within the [`group()`](api/animations/group) function: |
126 | | - 1. Queries the view that is removed and animates it to slide far to the right. |
127 | | - 1. Slides in the new view by animating the view with an easing function and duration. |
| 140 | +* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#changing-on-navigation-type) |
| 141 | +* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-nmnzzg?file=src%2Fmain.ts) |
128 | 142 |
|
129 | | - This animation results in the `about` view sliding in from the left. |
| 143 | +### Transitioning without freezing other animations |
130 | 144 |
|
131 | | -1. Calls the `animateChild()` method on the new view to run its child animations after the main animation completes. |
| 145 | +* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#transitioning-without-freezing) |
| 146 | +* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-76kgww) |
132 | 147 |
|
133 | | -You now have a basic routable animation that animates routing from one view to another. |
| 148 | +### Animating with Javascript |
134 | 149 |
|
135 | | -## More on Angular animations |
| 150 | +* [Chrome Explainer](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#animating-with-javascript) |
| 151 | +* [Angular Example on StackBlitz](https://stackblitz.com/edit/stackblitz-starters-cklnkm) |
136 | 152 |
|
137 | | -You might also be interested in the following: |
| 153 | +## Native View Transitions Alternative |
138 | 154 |
|
139 | | -<docs-pill-row> |
140 | | - <docs-pill href="guide/animations" title="Introduction to Angular animations"/> |
141 | | - <docs-pill href="guide/animations/transition-and-triggers" title="Transition and triggers"/> |
142 | | - <docs-pill href="guide/animations/complex-sequences" title="Complex animation sequences"/> |
143 | | - <docs-pill href="guide/animations/reusable-animations" title="Reusable animations"/> |
144 | | -</docs-pill-row> |
| 155 | +Animating the transition between routes can also be done with the `@angular/animations` package. |
| 156 | +The animation [triggers and transitions](/guide/animations/transition-and-triggers) |
| 157 | +can be derived from the router state, such as the current URL or `ActivatedRoute`. |
0 commit comments