0%

angular animations动画(一)

动画是现代web应用设计中的一个重要方面。好的用户界面要能在不同的状态之间进行更平滑的转场。设计良好的动画不但会让ui更有趣,还会让它更容易使用。

angular的动画系统赋予了制作各种动画效果的能力,以构建出与原生CSS动画性能相同的动画。还获得了额外的让动画 逻辑与其他应用代码紧紧集成在一起的能力,这让动画可以被更容易的出发和控制。

准备工作

在应用中添加动画时,需要在根模块中引入一些与动画相关的模块和函数:

1
2
3
4
5
6
7
8
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
@NgModule({
imports: [
BrowserModule,
BrowserAnimationsModule,
],
})
export class AppModule{}

在两个状态中转换

构建一个简单的动画,让一个元素用模型驱动的方式在两个状态之间进行转换,动画会被定义在@Component元数据中。

首先我们引入动画必须的一些符号:

1
import {animate, state, style, transition, trigger} from '@angular/animations';

然后在组件的元数据中定义一个名叫heroState的动画触发器。它在两个状态activeinactive之间进行转换。当按钮处于激活状态时,会变大、变亮:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component({
// other
animations: [
trigger('heroState', [
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)',
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)',
})),
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out')),
]),
]
})

然后可以在组件的页面里面通过[@triggerName]来将上面定义的动画附加到元素上去:

1
<button [@heroState]="state" (click)="changeState()">test animation</button>

这里我们通过按钮的点击事件来触发状态的转换,使用state来记录状态:

1
2
3
4
state = 'inactive';
changeState() {
this.state = this.state === 'inactive' ? 'active' : 'inactive';
}

这样,点击button,就可以实现状态的转换,相应的动画也会应用上去。

状态与转换

angular动画是由状态和状态之间的转换效果所定义的。

定义状态

动画状态是一个由程序代码定义的字符串的值,上面的例子中,activeinactive是我们定义的两种状态。定义状态的样式:

1
2
3
4
5
6
7
8
state('inactive', style({
backgroundColor: '#eee',
transform: 'scale(1)',
})),
state('active', style({
backgroundColor: '#cfd8dc',
transform: 'scale(1.1)',
})),

这些state具体定义了每个状态的最终样式,一旦元素转换到了那个状态,该状态的样式就会被应用到此元素上。当它停留在此状态时,这些样式也会一直保留。从这个意义上来讲,这里其实定义的不是动画,而是定义该元素在不同的状态具有的样式。

定义转换

定义完状态,我们只拥有了状态的样式,动画是一种过渡,我们可以定义转换来实现动画的过渡。每个转换都会控制一条在一组样式和下一组样式之间切换的时间线:

1
2
transition('inactive => active', animate('100ms ease-in')),
transition('active => inactive', animate('100ms ease-out')),

transition方法是定义在满足条件时运行的一系列动画步骤:

  • 单方向时间线:preState => nextState
  • 两状态相同的时间线:preState <=> nextState

有时候希望一些样式只在动画期间生效,但是结束后并不保留它们。这时可以把这些样式内联在transition中定义。例如:

1
2
3
4
5
6
7
transition('inactive => active', [
style({
backgroundColor: '#cf8ddc',
transform: 'scale(1.3)',
}),
animate('100ms ease-in'),
]),

这样,当转换开始的时候,该元素会立马获得一组样式,然后转换到下一个状态,当转换结束时,状态并不会保留,因为没有定义在state里面。

*(通配符)状态转换

*(通配符)状态匹配任何动画状态。当定义那些不需要管当前处于什么状态的样式及转换时,很有用:

  • active => * 当元素的状态从active转换成其他任何状态时,生效
  • * => * 在任意两个状态之间切换时,生效
  • * => active从任何状态转换到active状态时,生效

void状态转换

有一种特殊的状态void,它可以应用在任何动画中。它表示元素没有被附加到视图。这种情况可能是由于它未被加进来或者已经被移除了。比如,当一个元素离开视图时,* => void转换就会生效,而不用管它在离场前是什么状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component({
//...
animations: [
trigger('heroState', [
state('show', style({
transform: 'translateX(100%)',
border: '2px solid red'
})),
transition('void <=> show', animate('200ms ease-in')),
])
]
})
// 控制div的显示和隐藏
show = true;
// 点击来触发div的显示
changeState() {
this.show = !this.show;
}

html:

1
2
3
<button nz-button [nzType]="'primary'" [@heroState]="state" (click)="changeState()">Submit</button>
<div [@heroState]="'show'" class="square" *ngIf="show">
</div>

上面构造了一个button和div,使用button来控制div的显示和隐藏,同时应用动画上去。

:enter和:leave别名

:enter:leave分别是void => ** => void的别名。这些别名提供多个动画函数使用。定位进入视图的元素比较麻烦,因为它不在DOM中。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component({
//...
animations: [
trigger('blockState', [
transition(':enter', [
style({
opacity: 0,
}),
animate('2s', style({opacity: 1})),
]),
transition(':leave', [
style({opacity: 1}),
animate('2s', style({opacity: 0})),
])
]),
]
})

html:

1
2
<div @blockState class="square" *ngIf="show">
</div>

这样,当元素进入组件的时候,将透明度设为0,然后2s内增长到1。删除这个元素的时候,将透明度设置为0。

:increment和:decrement

待探索

转场中的逻辑值

如果某个触发器以逻辑型的值作为绑定,那么久可以使用能与truefalse10相比较的transition()表达式来匹配这个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component({
//...
animations: [
trigger('openClose', [
state('true', style({height: '*'})),
state('false', style({height: '0px'})),
transition('false <=> true', animate('500ms ease-in-out')),
])
]
})
show = true;
changeState() {
this.show = !this.show;
}

html:

1
2
3
<button nz-button [nzType]="'primary'" [@heroState]="state" (click)="changeState()">Submit</button>
<div [@openClose]="show" class="square">
</div>

这样,点击按钮的时候,div会按照show的值进行展示和收缩。

动画回调

当动画启动和终止时,trigger()函数会发出一些回调。例如,我们对上面的动画接收开始和结束的回调:

1
2
3
4
5
<div class="square"
[@openClose]="show"
(@openClose.start)="onAnimationEvent($event)"
(@openClose.done)="onAnimationEvent($event)">
</div>

js:

1
2
3
onAnimationEvent($event) {
console.log(event);
}

动画回调的潜在用途之一,是用来覆盖比较慢的api调用,比如查询数据,这时可以建立一个循环动画。

关键帧动画

关键帧包括一个用来定义动画中每个样式何时开始更改的偏移属性。偏移是个0到1之间的相对值,分别标记动画的开始和结束时间。定义关键帧的偏移量是可选的,如果省略它们,就会自动分配均匀间隔的偏移。

例如:

1
<button nz-button [nzType]="'primary'" [@keyframes]="state" (click)="changeState()">Submit</button>

默认分配均匀间隔的偏移,变化是匀速的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component({
//...
animations: [
trigger('keyframes', [
transition('* => *', [
animate('2s', keyframes([
style({backgroundColor: 'blue'}),
style({backgroundColor: 'red'}),
style({backgroundColor: 'orange'}),
]))
])
]),
]
})

自定义的offset偏移量的动画,速率是根据offset来控制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component({
//...
animations: [
trigger('keyframes', [
transition('* => *', [
animate('2s', keyframes([
style({backgroundColor: 'blue', offset: 0}),
style({backgroundColor: 'red', offset: 0.8}),
style({backgroundColor: 'orange', offset: 1}),
]))
])
]),
]
})

带脉动效果的关键帧

通过在整个动画中定义特定偏移处的样式,可以使用关键帧在动画中创建脉动效果。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Component({
//...
animations: [
trigger('openClose', [
state('true', style({
height: '200px',
opacity: 1,
backgroundColor: 'yellow',
})),
state('false', style({
height: '100px',
opacity: 0.5,
backgroundColor: 'green',
})),
transition('false <=> true', animate('1s', keyframes([
style({opacity: 0.1, offset: 0.1}),
style({opacity: 0.6, offset: 0.2}),
style({opacity: 1, offset: 0.5}),
style({opacity: 0.2, offset: 0.7}),
]))),
]),
]
})

angular动画定义在组件的元数据animations中,使用trigger函数来定义一组动画,定义的动画的名字可以当做元素的属性来绑定在模板中[@animationTriggerName],在trigger中使用state函数来定义动画的状态,然后使用transition函数来定义动画的执行过程,包括状态之间的转换方向。在里面使用animate来定义动画的执行。

码字辛苦,打赏个咖啡☕️可好?💘