原文:Angular — Supercharge your Router transitions using new animation features (v4.3+)
首先我们看一下效果展示的demo
Basic
Variation
Stagger
Final
样例
为了介绍这个新的动画,我们将用一个只有home和about页面的简单应用来做演示。我们将要用内容向左飞出然后用下图所示的交错进入的效果实现一个很酷的路由页面切换
动画依赖安装
独立引入Angular 4里的动画部分,这样可以使你的项目更轻便,如果你不想用动画的话只要直接拿掉就可以了
首先,我们把下列依赖独立引入你的项目:@angular/animations
和 @angular/platform-browser/animations
接下来在项目中的入口模块的代码引入 BrowserAnimationsModule
\\app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
imports: [ BrowserModule, BrowserAnimationsModule, ... ],
declarations: [ App, ... ],
bootstrap: [ App ]
})
export class AppModule { }
如果你需要使用 不支持原生WEB动画API的浏览器 的话:比如IE、Edge、Safari等等。你必须要引入web-animations依赖
正如我们所见,我们的例子将由顶部导航和主体内容部分组成。各个内容部分将共享同一个导航栏,我们使用router-outlet
元素来通知路由我们需要渲染组件的地方和需要匹配路由的地方
\\app.module.ts
@Component({
selector: 'my-app',
template: `
`
})
export class App { }
我们使用静态路由来创建不同的导航链接。然后我们用routerLinkActive
指令来装饰当前的内容部分。举个例子,如果我们要导航到首页Home,导航链接的标签上将加上active
选择器并根据这个渲染对应部分
Home
添加路由转换
让我们改变默认的路由变换设置。首先我们需要加上动画触发器属性 routerTransition
,然后我们绑定@routerTransition
在主标签元素上,之后,我们可以装饰内部的路由组件并交由Angular的路由来实例化
我们可以用一个div或者其他标签来替换主标签元素,只要它是
router-outlet
的父结点
我们用getState
方法传递外部设置的参数来设置正确的状态。这个方法将返回一个 state
的用于设置路由定义的属性。我们接下来看下一个部分,这个设置将允许我们去控制每个路由将执行哪种切换
提示:我们可以通过使用
#o="outlet"
来获得外部传参,由于在router-outlet definition里使用exportAs使之成为可能。这给了我们一个快速获取底层RouterOutlet选择器类的途径
\\app.module.ts
@Component({
selector: 'my-app',
animations: [ routerTransition ],
template: `
`
})
export class App {
getState(outlet) {
return outlet.activatedRouteData.state;
}
}
设置路由
在Angular里面,路由会去尝试匹配路由定义和当前的url,同一个生效的配置优先级由上到下
\\app.routing.ts
const routes = [
{ path: '', redirectTo: 'home', pathMatch: 'full' },
{ path: 'home', component: Home, data: { state: 'home' } },
{ path: 'about', component: About, data: { state: 'about' } },
{ path: '**', component: NotFound }
];
export const AppRouting = RouterModule.forRoot(routes, {
useHash: true
});
路由设置告诉路由当匹配到Home和About页面的路径时去实例化对应的组件。注意这个data
属性设置每个状态给对应的路由。这些状态将要去匹配我们在routerTransition
中设置的切换
我们也需要设置两个为了通常展示的特殊路由。这个空路径的路由将提供一个默重定向到主页的路由以防导航去了一个非我们需要的路径。其他的路由或者未定义的路由展示一个用户友好的404页面就好
为了这个例子,因为Plunker的原因我们使用一个哈希定位的策略。如果我们需要后端我们可以沿用普通的路径跳转,但是如果没有后端来展示404错误的话我们就使用这个策略来重定向未知路径到index.html
Angular 动画
让我们隆重介绍Angular动画。Angular是基于最新的Web Animations API,我们使用动画触发器(animation triggers)来定义一系列状态和变换属性。我们也可以用CSS样式来改写实现我们想要的效果
主要的原则是开始和结尾的动画样式由我们自定义,中间变换的计算过程交给工具本身
当然,可以通过设置时间来设置中间动画,比如1s,1.2s,200ms。其他的就是大家熟悉的CSS动画的速度属性比如ease
、liner
和ease-in-out
。
而Angular 4.2以上的版本里我们可以用顺序(sequence)和组合(group)来让动画一个接一个执行还是同时执行;查询(query)可以操作子元素而交错(stagger)可以创造一个很棒的连锁效果
这些事件将触发一个动画:
- 向或者从视图里装载或者卸载一个元素
- 改变已绑定触发器的状态 比如:
[@routerTransition]="home"
在路由转换的前后关系中,要注意,组件正在被移除并作为导航的一部分被添加到视图中的过程。
定义动画
首先让我们看一下如何使用Angular动画来让内容向左滑出
最初,定义我们的触发器routerTransition
。在这个实现中,我们使用了一个覆盖所有可能状态的通用转换
这是个通过两个独立转换* => home
、 * => about
完成的重写
我们可以在我们的转换中用两个特殊标识
void
和*
来代表:一个元素尚未被挂载到视图或者任何状态
\\router.animations.ts
import {trigger, animate, style, group, animateChild, query, stagger, transition} from '@angular/animations';
export const routerTransition = trigger('routerTransition', [
transition('* <=> *', [
/* order */
/* 1 */ query(':enter, :leave', style({ position: 'fixed', width:'100%' })
, { optional: true }),
/* 2 */ group([ // block executes in parallel
query(':enter', [
style({ transform: 'translateX(100%)' }),
animate('0.5s ease-in-out', style({ transform: 'translateX(0%)' }))
], { optional: true }),
query(':leave', [
style({ transform: 'translateX(0%)' }),
animate('0.5s ease-in-out', style({ transform: 'translateX(-100%)' }
], { optional: true }),
])
])
])
我们在第二个参数里定义了一个顺序执行通用转换的数组 。第一个参数呢,使用了一个新的查询指令去选择新的路径组件和离开的组件;查询指令可以使用类似CSS伪类的选择器方式选中,比如一些特殊的关键词::enter
,:leave
和*
在第一次查询中,:enter
和:leave
将会匹配被挂载和卸载的组件,多个选择器用逗号分开。一旦我们有了这些路由组件,我们可以设置他们的样式去达到滑动的效果。使用 { position: fixed }
组件将根据视口放置,并滑过页面。
接下来,我们将设置一个组合使得内部动画同时执行。
让我们想象一下,我们从Home切换到About页面,第一次查询将会匹配这个被加了:enter
的组件,那就是About组件。我们首先在右边很远的地方设置动画,然后让它缓缓的进入滑动到固定的位置。结果就是About组件从右边滑到左边。第二次查询中,我们用了一个相似的路径,使用:leave
来匹配同样在左边很远处的Home组件
CSS小提示:为了更好的表现,我们使用transform取代top,bottom,left,right
注意这种实现方式与我们将触发器绑定到我们想要动画的元素的动画中的用例不同。
这意味着我们不能使用状态来对路由组件进行样式,因为这样可以将样式应用于父结点(我们的示例中的主元素),而不是路由组件。
在路由初始化期间,一些查询指令会返回空的结果,为了避免抛出错误,我们在所有查询中设置了可选参数。
加上交错动画
一旦我们的基础部分完成了,我们可以很容易在这个基础上用查询和交错控制加上一些新的效果
export const routerTransition = trigger('routerTransition', [
transition('* <=> *', [
/* order */
/* 1 */ query(':enter, :leave', ...),
/* 2 */ query('.block', style({ opacity: 0 })),
/* 3 */ group([ // block executes in parallel
query(':enter', [...]),
query(':leave', [...]),
]),
/* 4 */ query(':enter .block', stagger(400, [
style({ transform: 'translateY(100px)' }),
animate('1s ease-in-out',
style({ transform: 'translateY(0px)', opacity: 1 })),
])),
])
])
最终,我们使用转换(transform)和透明(opacity)来垂直向上滑动进入并淡入。
通过使用交错,我们为每个动画引入了一个小的延迟(以毫秒为单位),创建了一个很好的窗帘效果。最后一点,我们不得不引入一个新的查询,用(opacity:0)初始化.block元素,这样当组件滑入时就不会显示。
最终步骤
作为最终的润色,我添加了一些反向交错离开Home组件的代码并且也加入了贝塞尔曲线来优化路径,最后效果如下图