Angular : Multiple Element Animation


모가오 시스템의 웹 클라이언트는 Angular를 사용합니다.
이번에 다룰 주제는 Angular에서 다중 요소 애니메이션입니다. 
UI Library는 Angular Material을 사용했습니다.
아래 예제에 나와 있는 애니메이션을 구현하겠습니다.

글을 작성한 목적은 위의 기능을 생각했으나 예시를 찾기가 쉽지 않아 해당 내용을 공유해 보고자 합니다.
기본 API는 아래에서 찾을 수 있습니다.
https://angular.io/api/animations

기본 애니메이션 예시는 아래와 같습니다.
https://angular.io/guide/animations

예시 애니메이션은 다음 세가지로 구성됩니다.
1. 패널이 위로 올라가는 애니메이션
2. Your Opinion 이라는 문장이 왼쪽으로 사라지는 애니메이션
3. 버튼 패널이 오른쪽에서 왼쪽으로 슬라이드되는 애니메이션

이를 위해 다음과 같이 레이아웃을 구성합니다.

레이아웃을 위한 HTML 코드는 아래와 같습니다.

<!-- User Input Comment -->
<div class="input-panel" [@expandInput]="isInputExpand">
<mat-divider></mat-divider>
<div class="header-panel">
<div class="panel" [@slideLeftOut]="!isInputExpand">
Your Opinion?
<button mat-icon-button color="green" (click)="onOpinionClick(Stance.agree)"></button>
<button mat-icon-button color="gray" (click)="onOpinionClick(Stance.neutral)"></button>
<button mat-icon-button color="red" (click)="onOpinionClick(Stance.disagree)"></button>
</div>
<div class="panel" [@slideLeftIn]="isInputExpand" >
<button mat-icon-button [color]="getStanceColor(inputStance)"></button>
<button mat-button (click)="onWriteCommentClick()">Write</button>
<button mat-button (click)="onCancelCommentClick()">Close</button>
</div>
</div>
<mat-form-field>
<textarea matInput placeholder="Leave a comment" [(ngModel)]="newInputComment"></textarea>
</mat-form-field>
</div>

*[@expandInput]=”isInputExpand”
@expandInput 는 이벤트를 위한 트리거입니다.
해당 값은 Controller에서 isInputExpand 변수를 참고합니다.
해당 패널은 위로 올라가는 애니메이션을 구성합니다.

* [@slideLeftOut]=”!isInputExpand”
질문 패널은 왼쪽으로 슬라이드되어 나가며 표시될 때는 왼쪽에서 오른쪽으로 슬라이드되어 나타나게 됩니다.
isInputExpand 값을 참고하여 동작합니다.

* [@slideLeftIn]=”isInputExpand”
버튼 패널은 오른쪽에서 왼쪽으로 슬라이드 되며 들어옵니다.
isInputExpand 값을 참고하여 동작합니다.

결국 위의 코드로 isInputExpand 값이 변경되면 세가지 트리거가 발생되고 애니메이션이 진행됩니다.
ts 코드는 다음과 같습니다.

@Component({
selector: 'mgo-comments',
templateUrl: './comment.component.html',
styleUrls: ['./comment.component.css'],
animations:
[
trigger
(
'expandInput',
[
state('true', style({
transform: 'translateY(0px)'
})),
state('false', style({
transform: 'translateY(95px)'
})),

transition('false => true', [
group([
query("@*", [animateChild()] ),
animate('200ms ease-in'),
])
]),
transition('true => false',
group([
query("@*", [animateChild()] ),
animate('200ms ease-out'),
])
)
]
),

trigger
(
'slideLeftOut',
[
state('true', style({ transform: 'translateX(0px)' })),
state('false', style({ transform: 'translateX(-100%)' })),
transition('false => true', animate('300ms ease-in')),
transition('true => false', animate('300ms ease-out'))
]
),
trigger
(
'slideLeftIn',
[
state('true', style({ transform: 'translateX(0px)' })),
state('false', style({ transform: 'translateX(100%)' })),
transition('false => true', animate('300ms ease-in')),
transition('true => false', animate('300ms ease-out'))
]
),
]
})
export class CommentComponent implements OnInit { 
...
}

* animations 
코드는 animations 항목에서 추가합니다.

* group()
Angular 문서인 아래를 참고하세요.
https://angular.io/api/animations

우리는 애니메이션을 동시에 수행하려고 하며 동시에 수행한다면 group() 밖에 이용할 수 있는 함수가 없습니다.
query() 함수는 child 에 있는 애니메이션을 수행하기 위해 기술 했고 animate()로 panel 에 대한 애니메이션을 동시에 수행합니다.
아래는 지금 구현하려고 하는 애니메이션의 핵심 동작입니다.

trigger
(
'expandInput',
[
state('true', style({
transform: 'translateY(0px)'
})),
state('false', style({
transform: 'translateY(95px)'
})),

transition('false => true', [
group([
query("@*", [animateChild()] ),
animate('200ms ease-in'),
])
]),
transition('true => false',
group([
query("@*", [animateChild()] ),
animate('200ms ease-out'),
])
)
]
)

아래의 페이지도 읽어보시면 좋습니다.

https://angular.io/api/animations
https://angular.io/guide/animations
https://www.yearofmoo.com/2017/06/new-wave-of-animation-features.html