Angular提供了一个强大的动画系统,允许开发者创建复杂的组件动画、路由转场和状态过渡效果。
AppModule中导入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: [AppComponent],
bootstrap: [AppComponent]
})
export class AppModule { }
点击下方动画框或按钮体验不同动画效果
// fade.animation.ts - 淡入淡出动画
import {
trigger,
state,
style,
animate,
transition
} from '@angular/animations';
export const fadeAnimation = trigger('fade', [
// 定义状态
state('void', style({
opacity: 0
})),
state('*', style({
opacity: 1
})),
// 定义转场
transition(':enter', [
animate('300ms ease-in')
]),
transition(':leave', [
animate('300ms ease-out', style({ opacity: 0 }))
])
]);
// 在组件中使用
@Component({
selector: 'app-fade-demo',
templateUrl: './fade-demo.component.html',
animations: [fadeAnimation]
})
export class FadeDemoComponent {
isVisible = true;
toggle(): void {
this.isVisible = !this.isVisible;
}
}
<!-- fade-demo.component.html -->
<div class="fade-demo">
<button (click)="toggle()">{{isVisible ? '隐藏' : '显示'}}元素</button>
<div @fade *ngIf="isVisible" class="animated-element">
我会淡入淡出
</div>
</div>
// toggle.animation.ts - 切换状态动画
import { trigger, state, style, animate, transition } from '@angular/animations';
export const toggleAnimation = trigger('toggle', [
// 定义多个状态
state('open', style({
height: '200px',
opacity: 1,
backgroundColor: '#4caf50'
})),
state('closed', style({
height: '100px',
opacity: 0.8,
backgroundColor: '#f44336'
})),
state('disabled', style({
height: '50px',
opacity: 0.5,
backgroundColor: '#9e9e9e',
cursor: 'not-allowed'
})),
// 定义转场
transition('open => closed', [
animate('300ms ease-out')
]),
transition('closed => open', [
animate('300ms ease-in')
]),
transition('* => disabled', [
animate('500ms')
]),
transition('disabled => *', [
animate('500ms')
])
]);
// 在组件中使用
@Component({
selector: 'app-toggle-demo',
template: `
<div class="toggle-demo">
<button (click)="state = 'open'">打开</button>
<button (click)="state = 'closed'">关闭</button>
<button (click)="state = 'disabled'">禁用</button>
<div [@toggle]="state" class="toggle-box">
当前状态: {{state}}
</div>
</div>
`,
animations: [toggleAnimation]
})
export class ToggleDemoComponent {
state: 'open' | 'closed' | 'disabled' = 'open';
}
// keyframe.animation.ts - 关键帧动画
import {
trigger,
state,
style,
animate,
transition,
keyframes,
animation,
useAnimation
} from '@angular/animations';
// 定义可重用的动画
export const bounceAnimation = animation([
animate('1000ms', keyframes([
style({ transform: 'scale(1)', offset: 0 }),
style({ transform: 'scale(1.2)', offset: 0.2 }),
style({ transform: 'scale(0.8)', offset: 0.4 }),
style({ transform: 'scale(1.1)', offset: 0.6 }),
style({ transform: 'scale(0.9)', offset: 0.8 }),
style({ transform: 'scale(1)', offset: 1 })
]))
]);
// 关键帧动画触发器
export const keyframeTrigger = trigger('keyframe', [
transition(':enter', [
useAnimation(bounceAnimation)
]),
transition('* => bounce', [
useAnimation(bounceAnimation)
])
]);
// 复杂的进度条动画
export const progressAnimation = trigger('progress', [
state('start', style({ width: '0%' })),
state('complete', style({ width: '100%' })),
transition('start => complete', [
animate('2000ms ease-in-out', keyframes([
style({ width: '0%', backgroundColor: '#f44336', offset: 0 }),
style({ width: '30%', backgroundColor: '#ff9800', offset: 0.3 }),
style({ width: '60%', backgroundColor: '#ffeb3b', offset: 0.6 }),
style({ width: '100%', backgroundColor: '#4caf50', offset: 1 })
]))
])
]);
// 在组件中使用关键帧动画
@Component({
selector: 'app-keyframe-demo',
template: `
<div class="keyframe-demo">
<button (click)="triggerBounce()">触发弹跳动画</button>
<button (click)="startProgress()">开始进度条</button>
<div @keyframe="animationState" class="bounce-box">
弹跳方块
</div>
<div class="progress-container">
<div @progress="progressState" class="progress-bar"></div>
</div>
</div>
`,
animations: [keyframeTrigger, progressAnimation]
})
export class KeyframeDemoComponent {
animationState = '';
progressState = 'start';
triggerBounce(): void {
this.animationState = 'bounce';
// 重置状态以便再次触发
setTimeout(() => {
this.animationState = '';
}, 1000);
}
startProgress(): void {
this.progressState = 'start';
// 触发动画
setTimeout(() => {
this.progressState = 'complete';
}, 100);
}
}
// stagger.animation.ts - 交错动画
import {
trigger,
transition,
style,
animate,
query,
stagger
} from '@angular/animations';
export const listAnimation = trigger('listAnimation', [
transition('* => *', [
// 为进入的元素设置初始状态
query(':enter', [
style({ opacity: 0, transform: 'translateY(-20px)' }),
stagger(100, [
animate('300ms ease-out', style({
opacity: 1,
transform: 'translateY(0)'
}))
])
], { optional: true }),
// 为离开的元素设置动画
query(':leave', [
stagger(-50, [
animate('200ms ease-in', style({
opacity: 0,
transform: 'translateX(100px)'
}))
])
], { optional: true })
])
]);
// 在组件中使用
@Component({
selector: 'app-list-demo',
template: `
<div class="list-demo">
<button (click)="addItem()">添加项目</button>
<button (click)="removeItem()">移除项目</button>
<ul @listAnimation class="animated-list">
<li *ngFor="let item of items">
{{item}}
<button (click)="removeSpecificItem(item)">移除</button>
</li>
</ul>
</div>
`,
animations: [listAnimation]
})
export class ListDemoComponent {
items: string[] = ['项目1', '项目2', '项目3'];
counter = 4;
addItem(): void {
this.items.push(`项目${this.counter++}`);
}
removeItem(): void {
if (this.items.length > 0) {
this.items.pop();
}
}
removeSpecificItem(item: string): void {
const index = this.items.indexOf(item);
if (index > -1) {
this.items.splice(index, 1);
}
}
}
// route.animations.ts - 路由转场动画
import {
trigger,
animateChild,
group,
transition,
animate,
style,
query
} from '@angular/animations';
// 路由转场动画
export const routeAnimation = trigger('routeAnimation', [
// 定义不同路由之间的转场
transition('* <=> *', [
// 设置初始状态
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%'
})
]),
// 动画顺序
query(':enter', [
style({ opacity: 0 })
]),
// 同时执行离开和进入动画
group([
query(':leave', [
animate('300ms ease-out', style({
opacity: 0,
transform: 'translateX(-100px)'
}))
]),
query(':enter', [
animate('400ms ease-in', style({
opacity: 1,
transform: 'translateX(0)'
}))
])
])
])
]);
// 淡入淡出路有动画
export const fadeRouteAnimation = trigger('fadeRoute', [
transition('* <=> *', [
query(':enter', [
style({ opacity: 0 })
], { optional: true }),
query(':leave', [
animate('200ms', style({ opacity: 0 }))
], { optional: true }),
query(':enter', [
animate('300ms', style({ opacity: 1 }))
], { optional: true })
])
]);
// 滑动路由动画
export const slideRouteAnimation = trigger('slideRoute', [
transition(':increment', [
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%'
})
]),
query(':enter', [
style({ left: '100%' })
]),
query(':leave', [
animate('300ms ease-out', style({ left: '-100%' }))
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%' }))
])
]),
transition(':decrement', [
style({ position: 'relative' }),
query(':enter, :leave', [
style({
position: 'absolute',
top: 0,
left: 0,
width: '100%'
})
]),
query(':enter', [
style({ left: '-100%' })
]),
query(':leave', [
animate('300ms ease-out', style({ left: '100%' }))
]),
query(':enter', [
animate('300ms ease-out', style({ left: '0%' }))
])
])
]);
// 在路由配置中使用
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { HomeComponent } from './home.component';
import { AboutComponent } from './about.component';
import { ContactComponent } from './contact.component';
const routes: Routes = [
{
path: '',
component: HomeComponent,
data: { animation: 'home' }
},
{
path: 'about',
component: AboutComponent,
data: { animation: 'about' }
},
{
path: 'contact',
component: ContactComponent,
data: { animation: 'contact' }
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }
// 在AppComponent中使用路由动画
@Component({
selector: 'app-root',
template: `
<div class="app-container">
<nav>
<a routerLink="/">首页</a>
<a routerLink="/about">关于</a>
<a routerLink="/contact">联系</a>
</nav>
<!-- 路由出口 -->
<div [@routeAnimation]="getAnimationData(routerOutlet)">
<router-outlet #routerOutlet="outlet"></router-outlet>
</div>
</div>
`,
animations: [routeAnimation]
})
export class AppComponent {
getAnimationData(outlet: RouterOutlet): string {
return outlet && outlet.activatedRouteData && outlet.activatedRouteData['animation'];
}
}
// configurable.animation.ts - 可配置动画
import {
trigger,
transition,
animate,
style,
AnimationMetadata,
AnimationOptions
} from '@angular/animations';
// 可配置的动画工厂函数
export function createFadeAnimation(
duration: string = '300ms',
easing: string = 'ease-in-out'
): AnimationMetadata[] {
return [
transition(':enter', [
style({ opacity: 0 }),
animate(`${duration} ${easing}`, style({ opacity: 1 }))
]),
transition(':leave', [
animate(`${duration} ${easing}`, style({ opacity: 0 }))
])
];
}
export function createSlideAnimation(
direction: 'left' | 'right' | 'top' | 'bottom' = 'left',
distance: string = '100px'
): AnimationMetadata[] {
const fromStyle: any = { opacity: 0 };
switch(direction) {
case 'left':
fromStyle.transform = `translateX(-${distance})`;
break;
case 'right':
fromStyle.transform = `translateX(${distance})`;
break;
case 'top':
fromStyle.transform = `translateY(-${distance})`;
break;
case 'bottom':
fromStyle.transform = `translateY(${distance})`;
break;
}
return [
transition(':enter', [
style(fromStyle),
animate('300ms ease-out', style({
opacity: 1,
transform: 'translate(0, 0)'
}))
]),
transition(':leave', [
animate('300ms ease-in', style(fromStyle))
])
];
}
// 动画选项配置
export const animationOptions: AnimationOptions = {
params: {
duration: '500ms',
easing: 'cubic-bezier(0.68, -0.55, 0.265, 1.55)',
color: '#3498db',
scale: 1.2
}
};
// 参数化动画
export const parametricAnimation = trigger('parametric', [
transition(':enter', [
style({
opacity: 0,
transform: 'scale({{scale}})'
}),
animate('{{duration}} {{easing}}', style({
opacity: 1,
transform: 'scale(1)',
backgroundColor: '{{color}}'
}))
])
]);
// 在组件中使用可配置动画
@Component({
selector: 'app-configurable-demo',
template: `
<div class="configurable-demo">
<div class="controls">
<select [(ngModel)]="animationType">
<option value="fade">淡入淡出</option>
<option value="slideLeft">左侧滑动</option>
<option value="slideRight">右侧滑动</option>
<option value="slideTop">顶部滑动</option>
<option value="slideBottom">底部滑动</option>
</select>
<input type="range" [(ngModel)]="duration" min="100" max="2000" step="100">
<span>持续时间: {{duration}}ms</span>
</div>
<div [@animation]="getAnimationParams()" class="animated-box">
可配置动画
</div>
</div>
`,
animations: [
trigger('animation', [
transition(':enter, * => fade', createFadeAnimation()),
transition('* => slideLeft', createSlideAnimation('left')),
transition('* => slideRight', createSlideAnimation('right')),
transition('* => slideTop', createSlideAnimation('top')),
transition('* => slideBottom', createSlideAnimation('bottom'))
])
]
})
export class ConfigurableDemoComponent {
animationType = 'fade';
duration = 300;
getAnimationParams(): any {
return {
value: this.animationType,
params: {
duration: `${this.duration}ms`
}
};
}
}
// 使用参数化动画
@Component({
selector: 'app-parametric-demo',
template: `
<div class="parametric-demo">
<div [@parametric]="{
value: show,
params: {
duration: '800ms',
easing: 'ease-out',
color: '#4caf50',
scale: 0.5
}
}" class="parametric-box" *ngIf="show">
参数化动画
</div>
<button (click)="toggle()">切换</button>
</div>
`,
animations: [parametricAnimation]
})
export class ParametricDemoComponent {
show = true;
toggle(): void {
this.show = !this.show;
}
}
// animation-hooks.component.ts - 动画钩子
import {
Component,
HostBinding
} from '@angular/core';
import {
trigger,
transition,
animate,
style,
state,
AnimationEvent
} from '@angular/animations';
@Component({
selector: 'app-animation-hooks',
template: `
<div class="hooks-demo">
<button (click)="toggle()">{{isVisible ? '隐藏' : '显示'}}</button>
<div
[@boxAnimation]="animationState"
(@boxAnimation.start)="onAnimationStart($event)"
(@boxAnimation.done)="onAnimationDone($event)"
class="animated-box">
<p>动画钩子演示</p>
<p>状态: {{currentState}}</p>
<p>阶段: {{currentPhase}}</p>
</div>
<div class="event-log">
<h4>动画事件日志</h4>
<div *ngFor="let log of logs">{{log}}</div>
</div>
</div>
`,
animations: [
trigger('boxAnimation', [
state('visible', style({
opacity: 1,
transform: 'translateX(0) scale(1)'
})),
state('hidden', style({
opacity: 0,
transform: 'translateX(100px) scale(0.8)'
})),
transition('visible <=> hidden', [
animate('500ms ease-in-out')
])
])
]
})
export class AnimationHooksComponent {
@HostBinding('@.disabled') animationsDisabled = false;
isVisible = true;
animationState = 'visible';
currentState = '';
currentPhase = '';
logs: string[] = [];
toggle(): void {
this.isVisible = !this.isVisible;
this.animationState = this.isVisible ? 'visible' : 'hidden';
}
onAnimationStart(event: AnimationEvent): void {
this.currentState = event.toState;
this.currentPhase = 'start';
const log = `动画开始: ${event.triggerName} - ${event.fromState} → ${event.toState}`;
this.logs.unshift(log);
// 限制日志数量
if (this.logs.length > 10) {
this.logs.pop();
}
console.log('动画开始:', event);
}
onAnimationDone(event: AnimationEvent): void {
this.currentPhase = 'done';
const log = `动画完成: ${event.triggerName} - 总时长: ${event.totalTime}ms`;
this.logs.unshift(log);
console.log('动画完成:', event);
}
disableAnimations(): void {
this.animationsDisabled = true;
}
enableAnimations(): void {
this.animationsDisabled = false;
}
}
will-change属性提示浏览器优化transform和opacity代替布局属性ngZone.runOutsideAngular()处理复杂动画@.disabled绑定在需要时禁用动画// performance.animations.ts - 高性能动画
import { trigger, transition, style, animate } from '@angular/animations';
// 高性能动画 - 使用transform和opacity
export const highPerformanceAnimation = trigger('highPerf', [
transition(':enter', [
style({
opacity: 0,
transform: 'translate3d(0, 100px, 0) scale3d(0.8, 0.8, 1)' // 使用3D变换
}),
animate('300ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({
opacity: 1,
transform: 'translate3d(0, 0, 0) scale3d(1, 1, 1)'
}))
]),
transition(':leave', [
animate('200ms cubic-bezier(0.25, 0.8, 0.25, 1)', style({
opacity: 0,
transform: 'translate3d(0, -50px, 0) scale3d(0.9, 0.9, 1)'
}))
])
]);
// 批量动画优化
export const batchAnimation = trigger('batch', [
transition(':increment', [
// 批量更新时使用简单动画
style({ opacity: 0.8 }),
animate('100ms ease-out', style({ opacity: 1 }))
])
]);
// 条件动画禁用
@Component({
selector: 'app-performance-demo',
template: `
<div class="performance-demo" [@.disabled]="disableAllAnimations">
<div class="controls">
<button (click)="toggleAnimations()">
{{disableAllAnimations ? '启用' : '禁用'}}所有动画
</button>
<button (click)="addItems()">添加100个项目</button>
</div>
<div class="items-container">
<div *ngFor="let item of items; trackBy: trackById"
[@highPerf]
class="item">
项目 {{item.id}}
</div>
</div>
</div>
`,
animations: [highPerformanceAnimation]
})
export class PerformanceDemoComponent {
items: { id: number }[] = [];
disableAllAnimations = false;
private nextId = 0;
constructor(private ngZone: NgZone) {}
ngOnInit(): void {
// 初始加载一些项目
for (let i = 0; i < 20; i++) {
this.items.push({ id: this.nextId++ });
}
}
addItems(): void {
// 在Angular区域外执行大量添加操作
this.ngZone.runOutsideAngular(() => {
const newItems = [];
for (let i = 0; i < 100; i++) {
newItems.push({ id: this.nextId++ });
}
// 批量更新
this.ngZone.run(() => {
this.items = [...this.items, ...newItems];
});
});
}
toggleAnimations(): void {
this.disableAllAnimations = !this.disableAllAnimations;
}
trackById(index: number, item: any): number {
return item.id;
}
}
// 与GSAP集成
import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
import gsap from 'gsap';
@Component({
selector: 'app-gsap-demo',
template: `
<div #animationContainer class="gsap-demo">
<div #animatedElement class="gsap-box">
GSAP动画
</div>
<button (click)="playAnimation()">播放动画</button>
<button (click)="reverseAnimation()">反转动画</button>
<button (click)="restartAnimation()">重新开始</button>
</div>
`
})
export class GsapDemoComponent implements AfterViewInit {
@ViewChild('animatedElement') animatedElement!: ElementRef;
@ViewChild('animationContainer') animationContainer!: ElementRef;
private timeline: any;
ngAfterViewInit(): void {
this.createTimeline();
}
private createTimeline(): void {
this.timeline = gsap.timeline({
paused: true,
defaults: { duration: 1, ease: "power2.inOut" }
});
this.timeline
.to(this.animatedElement.nativeElement, {
x: 300,
rotation: 360,
scale: 1.5,
backgroundColor: '#4caf50'
})
.to(this.animatedElement.nativeElement, {
y: 100,
rotation: -180,
scale: 0.8,
backgroundColor: '#2196f3'
}, "-=0.5") // 重叠动画
.to(this.animatedElement.nativeElement, {
x: 0,
y: 0,
rotation: 0,
scale: 1,
backgroundColor: '#ff9800'
});
}
playAnimation(): void {
this.timeline.play();
}
reverseAnimation(): void {
this.timeline.reverse();
}
restartAnimation(): void {
this.timeline.restart();
}
}
// 与Animate.css集成
@Component({
selector: 'app-animate-css-demo',
template: `
<div class="animate-css-demo">
<div class="animation-selector">
<select [(ngModel)]="selectedAnimation">
<option *ngFor="let anim of animations" [value]="anim">
{{anim}}
</option>
</select>
<button (click)="triggerAnimation()">播放动画</button>
</div>
<div class="animated-element"
[class.animated]="isAnimating"
[class]="selectedAnimation">
动画元素
</div>
</div>
`,
styles: [`
.animated-element {
width: 200px;
height: 200px;
background: #3498db;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
margin: 20px auto;
border-radius: 8px;
}
`]
})
export class AnimateCssDemoComponent {
animations = [
'bounce', 'flash', 'pulse', 'rubberBand', 'shakeX', 'shakeY',
'headShake', 'swing', 'tada', 'wobble', 'jello', 'heartBeat',
'backInDown', 'backInLeft', 'backInRight', 'backInUp',
'bounceIn', 'bounceInDown', 'bounceInLeft', 'bounceInRight', 'bounceInUp'
];
selectedAnimation = 'bounce';
isAnimating = false;
triggerAnimation(): void {
this.isAnimating = true;
// 动画完成后重置
setTimeout(() => {
this.isAnimating = false;
}, 1000);
}
}
// notification.component.ts - 带动画的通知组件
import {
Component,
Input,
Output,
EventEmitter,
HostBinding,
OnDestroy
} from '@angular/core';
import {
trigger,
state,
style,
animate,
transition,
AnimationEvent
} from '@angular/animations';
export type NotificationType = 'success' | 'error' | 'warning' | 'info';
@Component({
selector: 'app-notification',
template: `
<div class="notification" [@notification]="animationState" (@notification.done)="onAnimationDone($event)">
<div class="notification-content" [class]="type">
<div class="notification-icon">
<i [class]="getIconClass()"></i>
</div>
<div class="notification-body">
<h4 *ngIf="title">{{title}}</h4>
<p>{{message}}</p>
</div>
<button class="notification-close" (click)="close()">
<i class="fas fa-times"></i>
</button>
</div>
</div>
`,
styles: [`
.notification {
position: fixed;
top: 20px;
right: 20px;
z-index: 1000;
max-width: 400px;
min-width: 300px;
}
.notification-content {
display: flex;
align-items: center;
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
background: white;
border-left: 4px solid;
}
.success {
border-left-color: #4caf50;
}
.error {
border-left-color: #f44336;
}
.warning {
border-left-color: #ff9800;
}
.info {
border-left-color: #2196f3;
}
.notification-icon {
font-size: 24px;
margin-right: 15px;
}
.notification-body {
flex: 1;
}
.notification-body h4 {
margin: 0 0 5px 0;
font-size: 16px;
}
.notification-body p {
margin: 0;
color: #666;
}
.notification-close {
background: none;
border: none;
font-size: 18px;
color: #999;
cursor: pointer;
margin-left: 10px;
padding: 5px;
}
.notification-close:hover {
color: #333;
}
`],
animations: [
trigger('notification', [
state('void', style({
transform: 'translateX(100%)',
opacity: 0
})),
state('visible', style({
transform: 'translateX(0)',
opacity: 1
})),
state('hidden', style({
transform: 'translateX(100%)',
opacity: 0
})),
transition('void => visible', [
animate('300ms cubic-bezier(0.68, -0.55, 0.265, 1.55)')
]),
transition('visible => hidden', [
animate('200ms ease-out')
])
])
]
})
export class NotificationComponent implements OnDestroy {
@Input() type: NotificationType = 'info';
@Input() title?: string;
@Input() message: string = '';
@Input() duration: number = 5000; // 自动关闭时间(毫秒)
@Output() closed = new EventEmitter();
animationState: 'void' | 'visible' | 'hidden' = 'void';
private timeoutId?: number;
private autoClose = true;
ngOnInit(): void {
// 初始显示动画
setTimeout(() => {
this.animationState = 'visible';
}, 0);
// 设置自动关闭
if (this.duration > 0 && this.autoClose) {
this.timeoutId = window.setTimeout(() => {
this.close();
}, this.duration);
}
}
close(): void {
this.animationState = 'hidden';
// 清除自动关闭定时器
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
}
onAnimationDone(event: AnimationEvent): void {
if (event.toState === 'hidden') {
this.closed.emit();
}
}
getIconClass(): string {
switch (this.type) {
case 'success':
return 'fas fa-check-circle text-success';
case 'error':
return 'fas fa-exclamation-circle text-danger';
case 'warning':
return 'fas fa-exclamation-triangle text-warning';
case 'info':
return 'fas fa-info-circle text-info';
default:
return 'fas fa-info-circle';
}
}
ngOnDestroy(): void {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
}
}
}