填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

路由轉場動畫

Route transition animations

前提條件

Prerequisites

對下列概念有基本的理解:

A basic understanding of the following concepts:


路由能讓使用者在應用中的不同路由之間導航。當用戶從一個路由導航到另一個路由時,Angular 路由器會把這個 URL 對映到一個相關的元件,並顯示其檢視。為這種路由轉換新增動畫,將極大地提升使用者體驗。

Routing enables users to navigate between different routes in an application. When a user navigates from one route to another, the Angular router maps the URL path to a relevant component and displays its view. Animating this route transition can greatly enhance the user experience.

Angular 路由器天生帶有高階動畫功能,它可以讓你為在路由變化時為檢視之間設定轉場動畫。要想在路由切換時產生動畫序列,你需要首先定義出巢狀的動畫序列。從宿主檢視的最上層元件開始,在這些內嵌檢視的宿主元件中巢狀新增其它動畫。

The Angular router comes with high-level animation functions that let you animate the transitions between views when a route changes. To produce an animation sequence when switching between routes, you need to define nested animation sequences. Start with the top-level component that hosts the view, and nest additional animations in the components that host the embedded views.

要啟用路由轉場動畫,需要做如下步驟:

To enable routing transition animation, do the following:

  1. 為應用匯入路由模組,並建立一個路由配置來定義可能的路由。

    Import the routing module into the application and create a routing configuration that defines the possible routes.

  2. 新增路由器出口,來告訴 Angular 路由器要把啟用的元件放在 DOM 中的什麼位置。

    Add a router outlet to tell the Angular router where to place the activated components in the DOM.

  3. 定義動畫。

    Define the animation.

讓我們以兩個路由之間的導航過程來解釋一下路由轉場動畫,HomeAbout 分別與 HomeComponentAboutComponent 的檢視相關聯。所有這些元件檢視都是最上層檢視的子節點,其宿主是 AppComponent。我們將實現路由器過渡動畫,該動畫會在出現新檢視時向右滑動,並當使用者在兩個路由之間導航時把舊檢視滑出。

Let's illustrate a router transition animation by navigating between two routes, Home and About associated with the HomeComponent and AboutComponent views respectively. Both of these component views are children of the top-most view, hosted by AppComponent. We'll implement a router transition animation that slides in the new view to the right and slides out the old view when the user navigates between the two routes.


路由配置

Route configuration

首先,使用 RouterModule 類別提供的方法來配置一組路由。該路由配置會告訴路由器該如何導航。

To begin, configure a set of routes using methods available in the RouterModule class. This route configuration tells the router how to navigate.

使用 RouterModule.forRoot 方法來定義一組路由。同時,把其返回值匯入到主模組 AppModuleimports 陣列中。

Use the RouterModule.forRoot method to define a set of routes. Also, import this RouterModule to the imports array of the main module, AppModule.

注意:在根模組 AppModule 中使用 RouterModule.forRoot 方法來註冊一些最上層應用路由和提供者。對於特性模組,呼叫 RouterModule.forChild 方法來註冊其它路由。

Note: Use the RouterModule.forRoot method in the root module, AppModule, to register top-level application routes and providers. For feature modules, call the RouterModule.forChild method to register additional routes.

下列配置定義了應用程式中可能的路由。

The following configuration defines the possible routes for the application.

src/app/app.module.ts
      
      import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component';
import { OpenCloseComponent } from './open-close.component';
import { OpenClosePageComponent } from './open-close-page.component';
import { OpenCloseChildComponent } from './open-close.component.4';
import { ToggleAnimationsPageComponent } from './toggle-animations-page.component';
import { StatusSliderComponent } from './status-slider.component';
import { StatusSliderPageComponent } from './status-slider-page.component';
import { HeroListPageComponent } from './hero-list-page.component';
import { HeroListGroupPageComponent } from './hero-list-group-page.component';
import { HeroListGroupsComponent } from './hero-list-groups.component';
import { HeroListEnterLeavePageComponent } from './hero-list-enter-leave-page.component';
import { HeroListEnterLeaveComponent } from './hero-list-enter-leave.component';
import { HeroListAutoCalcPageComponent } from './hero-list-auto-page.component';
import { HeroListAutoComponent } from './hero-list-auto.component';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
import { InsertRemoveComponent } from './insert-remove.component';


@NgModule({
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    RouterModule.forRoot([
      { path: '', pathMatch: 'full', redirectTo: '/enter-leave' },
      { path: 'open-close', component: OpenClosePageComponent },
      { path: 'status', component: StatusSliderPageComponent },
      { path: 'toggle', component: ToggleAnimationsPageComponent },
      { path: 'heroes', component: HeroListPageComponent, data: {animation: 'FilterPage'} },
      { path: 'hero-groups', component: HeroListGroupPageComponent },
      { path: 'enter-leave', component: HeroListEnterLeavePageComponent },
      { path: 'auto', component: HeroListAutoCalcPageComponent },
      { path: 'insert-remove', component: InsertRemoveComponent},
      { path: 'home', component: HomeComponent, data: {animation: 'HomePage'} },
      { path: 'about', component: AboutComponent, data: {animation: 'AboutPage'} },

    ])
  ],
    

homeabout 路徑分別關聯著 HomeComponentAboutComponent 檢視。該路由配置告訴 Angular 路由器當導航匹配了相應的路徑時,就實例化 HomeComponentAboutComponent 檢視。

The home and about paths are associated with the HomeComponent and AboutComponent views. The route configuration tells the Angular router to instantiate the HomeComponent and AboutComponent views when the navigation matches the corresponding path.

除了 pathcomponent 之外,每個路由定義中的 data 屬性也定義了與此路由有關的動畫配置。當路由變化時,data 屬性的值就會傳給 AppComponent。你還可以在路由配置中傳遞其它的值供路由的動畫使用。data 屬性的值必須滿足 routeAnimation 中定義的轉場動畫的要求,稍後我們就會定義它。

In addition to path and component, the data property of each route defines the key animation-specific configuration associated with a route. The data property value is passed into AppComponent when the route changes. You can also pass additional data in route configuration that is consumed within the animation. The data property value has to match the transitions defined in the routeAnimation trigger, which we'll define later.

注意:這個 data 中的屬性名可以是任意的。例如,上面例子中使用的名字 animation 就是隨便起的。

Note: The data property names that you use can be arbitrary. For example, the name animation used in the example above is an arbitrary choice.

路由出口

Router outlet

配置好路由之後,還要告訴 Angular 路由器當路由匹配時,要把檢視渲染到那裡。你可以透過在根元件 AppComponent 的範本中插入一個 <router-outlet> 容器來指定路由出口的位置。

After configuring the routes, tell the Angular router where to render the views when matched with a route. You can set a router outlet by inserting a <router-outlet> container inside the root AppComponent template.

<router-outlet> 容器具有一個屬性型指令,它包含關於活動路由及其狀態的資料,這些資料會基於我們在路由配置中設定的 data 屬性。

The <router-outlet> container has an attribute directive that contains data about active routes and their states, based on the data property that we set in the route configuration.

src/app/app.component.html
      
      <div [@routeAnimations]="prepareRoute(outlet)">
  <router-outlet #outlet="outlet"></router-outlet>
</div>
    

AppComponent 中定義了一個可以檢測檢視何時發生變化的方法,該方法會基於路由配置的 data 屬性值,將動畫狀態值賦值給動畫觸發器(@routeAnimation)。下面就是一個 AppComponent 中的範例方法,用於檢測路由在何時發生了變化。

AppComponent defines a method that can detect when a view changes. The method assigns an animation state value to the animation trigger (@routeAnimation) based on the route configuration data property value. Here's an example of an AppComponent method that detects when a route change happens.

src/app/app.component.ts
      
      prepareRoute(outlet: RouterOutlet) {
  return outlet && outlet.activatedRouteData && outlet.activatedRouteData.animation;
}
    

這裡的 prepareRoute() 方法會獲取這個 outlet 指令的值(透過 #outlet="outlet"),並根據當前活動路由的自訂資料返回一個表示動畫狀態的字串值。你可以使用這個資料來控制各個路由之間該執行哪個轉場。

Here, the prepareRoute() method takes the value of the outlet directive (established through #outlet="outlet") and returns a string value representing the state of the animation based on the custom data of the current active route. You can use this data to control which transition to execute for each route.

動畫定義

Animation definition

動畫可以直接在元件中定義。對於此範例,我們會在獨立的檔案中定義動畫,這讓我們可以複用這些動畫。

Animations can be defined directly inside your components. For this example we are defining the animations in a separate file, which allows us to re-use the animations.

下面的程式碼片段定義了一個名叫 slideInAnimation 的可複用動畫。

The following code snippet defines a reusable animation named slideInAnimation.

src/app/animations.ts
      
      export const slideInAnimation =
  trigger('routeAnimations', [
    transition('HomePage <=> AboutPage', [
      style({ position: 'relative' }),
      query(':enter, :leave', [
        style({
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%'
        })
      ]),
      query(':enter', [
        style({ left: '-100%' })
      ]),
      query(':leave', animateChild()),
      group([
        query(':leave', [
          animate('300ms ease-out', style({ left: '100%' }))
        ]),
        query(':enter', [
          animate('300ms ease-out', style({ left: '0%' }))
        ])
      ]),
      query(':enter', animateChild()),
    ]),
    transition('* <=> FilterPage', [
      style({ position: 'relative' }),
      query(':enter, :leave', [
        style({
          position: 'absolute',
          top: 0,
          left: 0,
          width: '100%'
        })
      ]),
      query(':enter', [
        style({ left: '-100%' })
      ]),
      query(':leave', animateChild()),
      group([
        query(':leave', [
          animate('200ms ease-out', style({ left: '100%' }))
        ]),
        query(':enter', [
          animate('300ms ease-out', style({ left: '0%' }))
        ])
      ]),
      query(':enter', animateChild()),
    ])
  ]);
    

該動畫定義做了如下事情:

The animation definition does several things:

  • 定義兩個轉場。每個觸發器都可以定義多個狀態和多個轉場。

    Defines two transitions. A single trigger can define multiple states and transitions.

  • 調整宿主檢視和子檢視的樣式,以便在轉場期間,控制它們的相對位置。

    Adjusts the styles of the host and child views to control their relative positions during the transition.

  • 使用 query() 來確定哪個子檢視正在進入或離開宿主檢視。

    Uses query() to determine which child view is entering and which is leaving the host view.

路由的變化會啟用這個動畫觸發器,並應用一個與該狀態變更相匹配的轉場。

A route change activates the animation trigger, and a transition matching the state change is applied.

注意:這些轉場狀態必須和路由配置中定義的 data 屬性的值相一致。

Note: The transition states must match the data property value defined in the route configuration.

透過將可複用動畫 slideInAnimation 新增到 AppComponentanimations 元資料中,可以讓此動畫定義能用在你的應用中。

Make the animation definition available in your application by adding the reusable animation (slideInAnimation) to the animations metadata of the AppComponent.

src/app/app.component.ts
      
      @Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css'],
  animations: [
    slideInAnimation
    // animation triggers go here
  ]
})
    

為宿主元件和子元件新增樣式

Styling the host and child components

在轉場期間,新檢視將直接插入在舊檢視後面,並且這兩個元素會同時出現在螢幕上。要防止這種情況,就要為宿主檢視以及要刪除和插入的子檢視指定一些額外的樣式。宿主檢視必須使用相對定位模式,而子檢視則必須使用絕對定位模式。在這些檢視中新增樣式,就可以讓容器就地播放動畫,而不會讓 DOM 移動。

During a transition, a new view is inserted directly after the old one and both elements appear on screen at the same time. To prevent this, apply additional styling to the host view, and to the removed and inserted child views. The host view must use relative positioning, and the child views must use absolute positioning. Adding styling to the views animates the containers in place, without the DOM moving things around.

src/app/animations.ts
      
      trigger('routeAnimations', [
  transition('HomePage <=> AboutPage', [
    style({ position: 'relative' }),
    query(':enter, :leave', [
      style({
        position: 'absolute',
        top: 0,
        left: 0,
        width: '100%'
      })
    ]),
    

查詢檢視的容器

Querying the view containers

使用 query() 方法可以找出當前宿主元件中的動畫元素。query(":enter") 語句會返回已插入的檢視,query(":leave") 語句會返回已移除的檢視。

Use the query() method to find and animate elements within the current host component. The query(":enter") statement returns the view that is being inserted, and query(":leave") returns the view that is being removed.

我們假設正在從 Home 轉場到 AboutHome => About

Let's assume that we are routing from the Home => About.

src/app/animations.ts (Continuation from above)
      
      query(':enter', [
    style({ left: '-100%' })
  ]),
  query(':leave', animateChild()),
  group([
    query(':leave', [
      animate('300ms ease-out', style({ left: '100%' }))
    ]),
    query(':enter', [
      animate('300ms ease-out', style({ left: '0%' }))
    ])
  ]),
  query(':enter', animateChild()),
]),
transition('* <=> FilterPage', [
  style({ position: 'relative' }),
  query(':enter, :leave', [
    style({
      position: 'absolute',
      top: 0,
      left: 0,
      width: '100%'
    })
  ]),
  query(':enter', [
    style({ left: '-100%' })
  ]),
  query(':leave', animateChild()),
  group([
    query(':leave', [
      animate('200ms ease-out', style({ left: '100%' }))
    ]),
    query(':enter', [
      animate('300ms ease-out', style({ left: '0%' }))
    ])
  ]),
  query(':enter', animateChild()),
])
    

在設定了檢視的樣式之後,動畫程式碼會執行如下操作:

The animation code does the following after styling the views:

  • query(':enter style({ left: '-100%'}) 會匹配新增的檢視,並透過將其定位在最左側來隱藏這個新檢視。

    query(':enter', style({ left: '-100%' })) matches the view that is added and hides the newly added view by positioning it to the far left.

  • 在正在離開的檢視上呼叫 animateChild(),來執行其子動畫。

    Calls animateChild() on the view that is leaving, to run its child animations.

  • 使用 group() 函式來並行執行內部動畫。

    Uses group() function to make the inner animations run in parallel.

  • group() 函式中:

    Within the group() function:

    • 查詢已移除的檢視,並讓它從右側滑出。

      Queries the view that is removed and animates it to slide far to the right.

    • 使用緩動函式和持續時間定義的動畫,讓這個新檢視滑入。
      此動畫將導致 about 檢視從左向右滑動。

      Slides in the new view by animating the view with an easing function and duration.
      This animation results in the about view sliding from the left to right.

  • 當主動畫完成之後,在這個新檢視上呼叫 animateChild() 方法,以執行其子動畫。

    Calls the animateChild() method on the new view to run its child animations after the main animation completes.

你現在有了一個基本的路由動畫,可以在從一個檢視路由到另一個檢視時播放動畫。

You now have a basic routable animation that animates routing from one view to another.

關於 Angular 動畫的更多知識

More on Angular animations

你可能還對下列內容感興趣:

You may also be interested in the following: