ng 动画
- 位置、大小、变形、颜色、边框
- 可以监听动画的开始和结束
- trigger 定义一个动画名称
- style({}) 执行的动画
- animate 执行时间,延迟,过度曲线
- transition 动画转场
- State 可以在指定的场景切换
-
- => void,void => * 在有和无的状态之间切换
在 state 和 transition 函数中使用样式时有一些需要注意的地方。
请用 state() 来定义那些在每个转场结束时样式,这些样式在动画结束时会保留。
使用 transition() 来定义那些中间样式,以便在动画过程中产生运动的错觉。
当禁用了动画时,也会忽略 transition() 中的样式,但 state() 中的样式不会。
你可以在同一个 transition() 参数中包含多个状态对:
transition( 'on => off, off => void' )
Core模块导入 BrowserAnimationsModule
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
@NgModule({
imports: [ BrowserAnimationsModule],
exports: [BrowserAnimationsModule]
})
export class CoreModule {}
AppComponent 创建动画
import { Component } from '@angular/core';
import {
trigger,
state,
style,
animate,
transition,
} from '@angular/animations';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.styl'],
animations: [
// trigger 定义动画名字
trigger('toggle', [
// 转场时的样式
state(
'show',
style({
opacity: 1,
}),
),
state(
'hide',
style({
opacity: 0,
}),
),
// 转场时的动画效果
transition('show => hide', [animate('0s')]),
transition('hide => show', [animate('200ms')]),
]),
],
})
export class AppComponent {
public isShow: boolean = true;
constructor() {}
public toggle(): void {
this.isShow = !this.isShow;
}
public get animeState() {
return this.isShow ? 'show' : 'hide';
}
}
使用:
<div class="container">
<button (click)="toggle()">toggle</button>
<div class="box" [@toggle]="animeState"></div>
</div>
配合 *ngIf
import { Component } from '@angular/core';
import {
trigger,
state,
style,
animate,
transition,
} from '@angular/animations';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.styl'],
animations: [
// trigger 定义动画名字
trigger('toggle', [
transition(':enter', [
// 动画执行中的动画,结束的时候不会保留
style({
opacity: 0,
'border-radius': '50%',
}),
// 动画结束后,保留的样式
animate('0.2s', style({
opacity: 1,
'border-radius': '0%',
background: 'red'
}))
]),
transition(':leave', [
animate('0s', style({
opacity: 0,
}))
]),
]),
],
})
export class AppComponent {
public isShow: boolean = true;
constructor() {}
public toggle(): void {
this.isShow = !this.isShow;
}
}
使用:
<div class="container">
<button (click)="toggle()">toggle</button>
<br>
<div class="box" @toggle *ngIf="isShow"></div>
</div>
禁用动画
当 @.disabled 绑定为 true 时,就会禁止渲染所有动画
<div class="container" [@.disabled]="true">
<button (click)="toggle()">toggle</button>
<br>
<div class="box" @toggle *ngIf="isShow">
</div>
</div>
禁用 Angular 应用中的所有动画,只要把 @.disabled 绑定放在顶级的 Angular 组件上即可
export class AppComponent {
@HostBinding('@.disabled')
public animationsDisabled = false;
}
监听动画回调
public toggleAnimationEvent(e:AnimationEvent): void {
console.log(e.phaseName);
}
绑定回调事件 @trigger.start 和 @trigger.done
<div class="container">
<button (click)="toggle()">toggle</button>
<br>
<div class="box"
@toggle
(@toggle.start)="toggleAnimationEvent($event)"
(@toggle.done)="toggleAnimationEvent($event)"
*ngIf="isShow">
</div>
</div>
关键帧动画 keyframes
import { Component } from '@angular/core';
import {
trigger,
state,
style,
animate,
transition,
keyframes,
AnimationEvent
} from '@angular/animations';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.styl'],
animations: [
// trigger 定义动画名字
trigger('toggle', [
transition(':enter', [
animate('2s',
keyframes([
style({ background: 'blue', offset: 0 }),
style({ background: 'green', 'margin-left': 200, offset: 0.3 }),
style({ background: 'yellow', 'margin-left': 0, offset: 0.5 }),
]),
style({
opacity: 1,
'border-radius': '0%',
background: 'red'
}))
]),
transition(':leave', [
animate('0s', style({
opacity: 0,
}))
]),
]),
],
})
export class AppComponent {
public isShow: boolean = true;
constructor() {}
public toggle(): void {
this.isShow = !this.isShow;
}
}
使用:
<div class="container">
<button (click)="toggle()">toggle</button>
<br>
<div class="box"
@toggle
*ngIf="isShow">
</div>
</div>
使用通配符自动计算属性 文档
animations: [
trigger('shrinkOut', [
state('in', style({ height: '*' })),
transition('* => void', [
style({ height: '*' }),
animate(250, style({ height: 0 }))
])
])
]
动画序列 文档
列表的 item 连续进场
import { Component, HostBinding } from '@angular/core';
import {
trigger,
state,
style,
animate,
transition,
keyframes,
AnimationEvent,
trigger,
query,
stagger
} from '@angular/animations';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.styl'],
animations: [
trigger('pageAnimations', [
transition(':enter', [
query('ul li', [
style({opacity: 0, transform: 'translateX(-100px)'}),
stagger(200, [
animate('500ms cubic-bezier(0.35, 0, 0.25, 1)', style({ opacity: 1, transform: 'none' }))
])
])
])
]),
],
})
export class AppComponent {
@HostBinding('@pageAnimations')
public isShow: boolean = true;
public names: string[] = ["ajanuw", "alone", "suou"];
constructor() {}
public toggle(): void {
this.isShow = !this.isShow;
}
}
使用:
<div class="container">
<button (click)="toggle()">toggle</button>
<br>
<ul *ngIf="isShow" @pageAnimations>
<li *ngFor="let name of names">{{ name }}</li>
</ul>
</div>
ul {
list-style-type: none;
padding: 0;
li {
padding 8px;
margin: 8px;
background-color: #ff00003b;
}
}
group() 动画属性设置不同的速度曲线
animations: [
trigger("pageAnimations", [
transition(":enter", [
query("ul li", [
style({ opacity: 0, transform: "translateX(-100px) rotate(2deg)" }),
stagger(200, [
group([
animate(
"500ms cubic-bezier(0.35, 0, 0.25, 1)",
style({ transform: "translateX(0) rotate(0deg)" })
),
animate(
"700ms cubic-bezier(0.28, 0.08, 0.78, 1.13)",
style({ opacity: 1, })
),
]),
]),
]),
]),
]),
],
sequence 依次执行动画属性
animations: [
trigger("pageAnimations", [
transition(":enter", [
query("ul li", [
style({ opacity: 0, transform: "translateX(-100px)" }),
stagger(200, [
sequence([
animate(
"200ms cubic-bezier(0.35, 0, 0.25, 1)",
style({ opacity: 1 })
),
animate(
"2s cubic-bezier(0.35, 0, 0.25, 1)",
style({ transform: "translateX(0)" })
),
]),
]),
]),
]),
]),
],
可复用动画 文档
路由转场动画 文档
import { Component } from "@angular/core";
import { RouterOutlet } from "@angular/router";
import {
trigger,
transition,
style,
query,
animateChild,
group,
animate,
} from "@angular/animations";
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.styl"],
animations: [
trigger("routeAnimations", [
// * 转场状态必须和路由配置中定义的 data 属性的值相一致
transition("HomePage <=> AboutPage", [
// 转场期间,新视图将直接插入在旧视图后面
// 并且这两个元素会同时出现在屏幕上。要防止这种情况,就要为宿主视图以及要删除和插入的子视图指定一些额外的样式
// 宿主视图必须使用相对定位模式,而子视图则必须使用绝对定位模式
// 这些视图中添加样式,就可以让容器就地播放动画,而不会让 DOM 移动
style({ position: "relative" }), // 宿主视图
query(":enter, :leave", [
// 子视图
style({
position: "absolute",
top: 0,
left: 0,
width: "100%",
}),
]),
// query(":enter") 语句会返回已插入的视图
query(":enter", [
style({
transform: "translateX(-100%)",
}),
]),
// query(":leave") 语句会返回已移除的视图
query(":leave", animateChild()), // 调用 animateChild(),来运行其子动画
group([
query(":leave", [
animate(
"300ms ease-out",
style({
transform: "translateX(100%)",
})
),
]),
query(":enter", [
animate(
"300ms ease-out",
style({
transform: "none",
})
),
]),
]),
query(":enter", animateChild()),
]),
]),
],
})
export class AppComponent {
constructor() {}
prepareRoute(outlet: RouterOutlet) {
return (
outlet &&
outlet.activatedRouteData &&
outlet.activatedRouteData["animation"]
);
}
}
使用:
<div>
<div>
<a [routerLink]="['/header']">header</a>
<a [routerLink]="['/footer']">footer</a>
</div>
<div [@routeAnimations]="prepareRoute(outlet)">
<router-outlet #outlet="outlet"></router-outlet>
</div>
</div>
路由定义:
const routes: Routes = [
{
path: "header",
component: HeaderComponent,
data: { animation: "HomePage" },
},
{
path: "footer",
component: FooterComponent,
data: { animation: "AboutPage" },
},
];