Skip to content

1. 소개


애니메이션은 HTML 엘리먼트의 스타일이 시간이 가면서 변하는 것을 의미한다. 애니메이션이 잘 활용된다면 애플리케이션을 좀 더 재미있고 사용성 좋게 만들 수 있기 때문에 겉치레인 것만은 아니다. 애니메이션은 이런 방식으로 도움이 된다:


  • 애니메이션이 없으면 화면이 갑작스럽게 전환되어 부자연스러울 수 있다.
  • 애니메이션이 있으면 사용자의 동작에 반응하는 것을 표현할 수 있기 때문에 사용자가 느끼는 앱 사용성을 크게 향상시킨다.
  • 애니메이션을 적절하게 사용하면 사용자의 관심을 앱 기획 의도에 맞게 집중시킬 수 있다.


일반적으로 애니메이션은 여러 스타일이 시간에 따라 변경되는 것을 의미한다. HTML 엘리먼트는 이동할 수도 있고 색이 변경될 수도 있으며, 나타나거나 사라질 수 있고 화면 밖으로 사라질 수도 있다. 그리고 이런 스타일은 동시에 변할 수도 있고 순차적으로 변할 수도 있다. 물론 변하는 시점을 세밀하게 조정할 수도 있다.


Angular가 제공하는 애니메이션 시스템은 CSS를 활용하기 때문에 브라우저가 지원한다면 어떤 프로퍼티에도 애니메이션을 적용할 수 있다. 위치, 크기, 색상, 외곽선 등의 프로퍼티가 모두 애니메이션 대상이다.


1. 사전지식

이 문서는 Angular에 대해 이미 익숙한 개발자를 대상으로 한다. 다음 내용에 대해서는 충분히 이해하고 있는 것이 좋다:



2. 시작하기

Angular 애니메이션 모듈은 @angular/animations@angular/platform-browser로 구성된다. Angular CLI로 프로젝트를 생성하면 이 패키지들은 자동으로 프로젝트에 설치된다.


프로젝트에 Angular 애니메이션을 추가하려면 먼저 애니메이션과 관련된 모듈을 Angular 애플리케이션에 추가해야 한다.


1) 1단계: 애니메이션 모듈 활성화하기

Angular 애플리케이션 최상위 모듈에 BrowserAnimationsModule을 추가한다.


src/app/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: [],
  bootstrap: [],
})
export class AppModule {}


Note

  • Angular CLI로 애플리케이션을 생성했다면 최상위 모듈은 src/app/app.module.ts 파일에 정의되어 있다.


2) 2단계: 컴포넌트 파일에 애니메이션 기능 로드하기

컴포넌트에 애니메이션을 적용하려면 @angular/animations 패키지에서 다음 심볼들을 로드한다.


src/app/app.component.ts
import { Component, HostBinding } from "@angular/core";
import {
  trigger,
  state,
  style,
  animate,
  transition,
  // ...
} from "@angular/animations";


3) 3단계: 애니메이션 메타데이터 프로퍼티 추가하기

그리고 컴포넌트 파일의 @Component() 데코레이터에 animations:로 시작하는 메타데이터 프로퍼티를 추가한다. 애니메이션의 세부 설정은 이 프로퍼티에 정의한다.


src/app/app.component.ts
@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css'],
  animations: [
    // 애니메이션 트리거는 여기에 작성합니다.
  ]
})


3. 트랜지션 구현하기

간단한 트랜지션을 구현해보기 위해 HTML 엘리먼트의 상태를 변경해 보자. 사용자의 마지막 동작에 따라 화면에는 버튼에 Open이나 Closed라는 문구가 표시된다. 그리고 open 상태일 때는 버튼이 노란색이 되고 closed 상태일 때는 녹색이 될 것이다.


투명도나 색상은 HTML 파일에서 일반적인 CSS 스타일을 지정하면 구현할 수 있다. 그리고 Angular는 이런 CSS 스타일을 애니메이션과 연결하기 위해 style() 함수를 사용한다. 먼저 애니메이션 상태를 정의하기 위해 open이나 closed와 같은 상태의 이름을 선언한다.


Note

  • 간단한 트랜지션을 만들기 위해 open-close 컴포넌트를 생성하려면 터미널에서 ng generate component open-close 명령을 실행하면 된다.
  • 그러면 src/app/open-close.component.ts 파일에 컴포넌트가 생성된다.


1) 애니메이션 상태와 스타일

트랜지션의 각 지점을 의미하는 애니메이션 상태는 state() 함수로 정의한다. 이 함수는 두 개의 인자를 받는데, 첫 번째 인자는 open이나 closed와 같은 상태 이름이고 두 번째 인자는 style() 함수이다.


style() 함수를 사용하면 상태 이름과 스타일셋을 연결할 수 있다. 이 때 사용하는 스타일의 이름은 반드시 캐멀-케이스(camelCase)를 사용해야 한다.


state() 함수와 style() 함수가 어떻게 동작하는지 확인해 보자. 이 예제 코드에는 상태마다 여러 스타일 어트리뷰트가 동시에 지정되어 있다. open 상태에서 버튼의 높이는 200px이며 투명도는 1, 배경색은 노란색이다.


src/app/open-close.component.ts
// ...
state('open', style({
  height: '200px',
  opacity: 1,
  backgroundColor: 'yellow'
})),


그리고 closed 상태에서 버튼의 높이는 100px이며 투명도는 0.5, 배경색은 녹색이다.


src/app/open-close.component.ts
state('closed', style({
  height: '100px',
  opacity: 0.8,
  backgroundColor: 'blue'
})),


2) 트랜지션 타이밍

스타일은 애니메이션 없이도 적용할 수 있다. 하지만 이렇게 사용하면 버튼 스타일이 변경될 때 전환효과도 없고 크기도 갑자기 변하게 된다.


이런 방식 대신 일정 시간동안 애니메이션이 한 상태에서 다른 상태로 천천히 변하게 하려면 애니메이션 트랜지션을 정의하면 된다. 트랜지션은 transition() 함수로 정의하는데, 이 함수는 두 개의 인자를 받는다. 첫 번째 인자는 상태가 변하는 방향을 정의하는 표현식이며, 두 번째 인자는 animate() 단계를 정의한다.


animate() 함수를 사용하면 트랜지션의 길이나 시작 지연시간, 가속도를 지정할 수 있다. 그리고 애니메이션이 여러 단계로 구성된다면 animate() 함수의 두 번째 인자에 keyframes() 함수를 사용할 수도 있다.


애니메이션 메타데이터: 지속시간, 딜레이, 가속도

트랜지션 함수의 두 번째 인자에 사용하는 animate() 함수는 timingsstyles를 인자로 받는다.


그리고 timings 인자는 숫자를 간단하게 사용하거나, 지속시간(duration), 시작 딜레이(delay), 가속도(easing) 부분으로 구성된 문자열을 사용한다:


  • animate(duration) 또는 animate('duration delay easing')


첫 번째 인자 duration은 필수 항목이다. 트랜지션 지속시간을 단위 없이 사용하면 밀리초단위이고 단위를 붙여 지정할 수도 있다:


  • 숫자만 사용하면 밀리초단위이다: 100
  • 밀리초단위를 명시할 수 있다: '100ms'
  • 초단위로 지정할 수 있다: '0.1s'


두 번째 인자 delay는 트랜지션이 시작되기 전 지연시간을 의미하며 duration과 비슷하게 사용한다:


  • 100ms 기다렸다가 200ms 동안 지속한다면 이렇게 지정한다: '0.2s 100ms'


세 번째 인자 easing은 애니메이션이 어떤 가속도로 진행될지 지정한다. 예를 들어 ease-in을 사용하면 느리게 시작했다가 점점 빨라진다.


  • 100ms 기다렸다가 200ms 시작하는데, 처음에는 빠르게 진행되다가 천천히 마무리하려면 이렇게 지정한다: '0.2s 100ms ease-out'
  • 딜레이 없이 200ms동안 진행되는데, 천천히 시작되었다가 중간에 가장 빠르고 다시 천천히 느려지도록 하려면 이렇게 지정한다: '0.2 ease-in-out'
  • 즉시 시작해서 200ms동안 진행되는데, 천천히 시작하고 최대속도로 마무리하려면 이렇게 지정한다: '0.2s ease-in'


이 가이드 문서에서 설명하는 앱은 open 상태에서 closed 상태로 1초동안 진행된다.


src/app/open-close.component.ts
transition('open => closed', [
  animate('1s')
]),


이 코드에서 => 연산자는 단방향 트랜지션을 의미하며 <=> 연산자는 양방향 트랜지션을 의미한다. 그리고 트랜지션이 진행되는 시간은 animate() 함수로 지정하는데, open 상태에서 closed 상태로 변할 때는 1s가 지정되었다.


그리고 closed 상태에서 open 상태로 변하는 것은 0.5초가 지정되었다.


src/app/open-close.component.ts
transition('closed => open', [
  animate('0.5s')
]),


Note

  • statetransition 함수를 사용할 때 이런 내용을 참고한다.
  • state() 함수에는 트랜지션이 완료된 시점의 스타일을 지정하며, 애니메이션이 종료되면 이 스타일은 그대로 남는다.
  • transition() 함수에는 애니메이션이 진행되는 동안 표시될 스타일을 지정하며, 이 스타일은 애니메이션이 진행되는 동안에만 표시된다.
  • 애니메이션을 비활성화하면 transition()에 지정된 스타일은 생략되고 state()에 지정한 스타일만 적용된다.
  • transition() 함수에는 transition( 'on => off, off => void' )와 같이 상태가 전환되는 것을 여러 번 표현할 수도 있다.


3) 애니메이션 트리거하기

애니메이션을 시작하려면 트리거(trigger)가 필요하다. 트리거 함수인 trigger()는 애니메이션 상태와 트랜지션, 그리고 애니메이션의 이름을 인자로 받아서 HTML 템플릿에 있는 엘리먼트에 트리거를 연결한다.


trigger() 함수는 템플릿에서 변화를 감지할 프로퍼티 이름과 연결된다. 그리고 이후에 이 프로퍼티의 값이 변경되면 트리거가 실행되면서 정의된 동작이 시작된다. 이 동작은 트랜지션일 수도 있지만 함수일 수도 있다.


이 예제에서는 openClose라는 이름의 트리거를 button 엘리먼트에 연결해 보자. 이 트리거는 open 상태와 closed 상태를 전환하는 트리거이며, 두 반향으로 진행되는 트랜지션이다.


Note

  • trigger() 함수가 실행되는 동안 엘리먼트의 상태는 한 번만 바뀌어야 한다.
  • 그렇지 않으면 동시에 여러 트리거가 실행될 수 있다.


4) 애니메이션 정의하기, HTML 템플릿과 연결하기

애니메이션은 컴포넌트 메타데이터에 정의되어 HTML 엘리먼트의 스타일을 조작한다. 좀 더 자세하게 설명하면 애니메이션을 정의하는 코드는 @Component() 데코레이터의 animations: 프로퍼티에 정의한다.


src/app/open-close.component.ts
@Component({
  selector: "app-open-close",
  animations: [
    trigger("openClose", [
      // ...
      state(
        "open",
        style({
          height: "200px",
          opacity: 1,
          backgroundColor: "yellow",
        })
      ),
      state(
        "closed",
        style({
          height: "100px",
          opacity: 0.8,
          backgroundColor: "blue",
        })
      ),
      transition("open => closed", [animate("1s")]),
      transition("closed => open", [animate("0.5s")]),
    ]),
  ],
  templateUrl: "open-close.component.html",
  styleUrls: ["open-close.component.css"],
})
export class OpenCloseComponent {
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }
}


컴포넌트에 정의한 애니메이션 트리거는 트리거 이름을 대괄호로 감싸고 @ 심볼을 붙여서 컴포넌트 템플릿에 연결한다. 그러면 이 트리거와 템플릿 표현식이 Angular 프로퍼티 바인딩 문법으로 연결되는데, 아래 코드에서 triggerName은 애니메이션 트리거 이름이며 expression은 애니메이션 상태를 선택하는 평가식이다.


<div [@triggerName]="expression">...</div>
;


그러면 이제 이 평가식이 실행되어 새로운 상태로 변경될 때 트리거가 발생하면서 애니메이션이 시작된다.


아래 코드에서는 애니메이션 트리거가 isOpen 프로퍼티에 반응한다.


src/app/open-close.component.html
<nav>
  <button (click)="toggle()">Toggle Open/Close</button>
</nav>

<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
  <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>


이 예제 코드에서 isOpen 표현식의 평가 결과에 따라 상태는 open이나 closed가 되며, 이 상태가 openClose 트리거로 전달된다. 그리고 이렇게 변경된 상태에 따라 openClose에 정의된 애니메이션 코드가 실행된다.


엘리먼트가 화면에 나타나거나(DOM에 추가될 때) 화면에서 벗어날 때(DOM에서 제거될 때)도 애니메이션을 적용할 수 있다. 애니메이션 트리거는 *ngIf로도 시작할 수 있다.


Note

  • 애니메이션은 컴포넌트 파일의 @Component() 데코레이터에서 animations: 프로퍼티에 정의한다.
  • 그리고 HTML 템플릿 파일에서 애니메이션 트리거 이름과 HTML 엘리먼트를 연결하면 된다.


5) 코드 리뷰

이 문서에서 다룬 앱 코드를 확인해 보자.


@Component({
  selector: "app-open-close",
  animations: [
    trigger("openClose", [
      // ...
      state(
        "open",
        style({
          height: "200px",
          opacity: 1,
          backgroundColor: "yellow",
        })
      ),
      state(
        "closed",
        style({
          height: "100px",
          opacity: 0.8,
          backgroundColor: "blue",
        })
      ),
      transition("open => closed", [animate("1s")]),
      transition("closed => open", [animate("0.5s")]),
    ]),
  ],
  templateUrl: "open-close.component.html",
  styleUrls: ["open-close.component.css"],
})
export class OpenCloseComponent {
  isOpen = true;

  toggle() {
    this.isOpen = !this.isOpen;
  }
}
<nav>
  <button (click)="toggle()">Toggle Open/Close</button>
</nav>

<div [@openClose]="isOpen ? 'open' : 'closed'" class="open-close-container">
  <p>The box is now {{ isOpen ? 'Open' : 'Closed' }}!</p>
</div>
:host {
  display: block;
  margin-top: 1rem;
}

.open-close-container {
  border: 1px solid #dddddd;
  margin-top: 1em;
  padding: 20px 20px 0px 20px;
  color: #000000;
  font-weight: bold;
  font-size: 20px;
}


4. 애니메이션 API

@angular/animations 모듈이 제공하는 애니메이션 API는 모두 Angular 애플리케이션의 애니메이션 도메인에서만 사용하는 언어라고 볼 수 있다. 전체 목록은 API 문서에서 확인할 수 있으며 이 문서에서는 중요한 것들만 추려서 확인해 보자.


함수 이름 용도
trigger() 애니메이션 관련 함수를 관리하는 컨테이너의 역할을 하며 애니메이션을 시작한다. HTML 템플릿에 triggerName을 바인딩하는 방식으로 사용한다. 첫 번째 인자로 트리거 이름을 전달하며 두 번째 인자는 배열을 받는다.
style() 애니메이션이 진행되는 동안 HTML 엘리먼트에 적용될 CSS 스타일을 정의한다. 객체 형식의 문법을 사용한다.
state() 트랜지션하는 각 지점의 이름과 각 지점에서 적용될 CSS 스타일을 지정한다. 이 때 지정하는 상태의 이름은 애니메이션 함수 안에서 사용할 수 있다.
animate() 트랜지션 타이밍을 지정한다. 이 때 시작 지연시간과 가속도 문자열은 생략할 수 있으며, 내부적으로 style()을 실행할 수 있다.
transition() 두 상태를 전환할 때 실행될 애니메이션을 지정한다. 배열 형태의 문법을 사용한다.
keyframes() 특정 시점에 적용될 스타일을 지정할 수 있으며 animate() 함수 안에서 사용한다. 이 함수 안에서 style()를 여러 번 사용할 수 있으며, 배열 형태의 문법을 사용한다.
group() 병렬로 실행될 애니메이션(세부 애니메이션)을 각각 그룹으로 묶을 때 사용한다. 전체 애니메이션은 세부 애니메이션이 전부 끝나야 종료되며 sequence()나 transition() 함수 안에서 사용한다.
query() HTML 엘리먼트를 탐색할 때 사용한다.
sequence() 순서대로 실행될 애니메이션을 지정한다.
stagger() 엘리먼트 여러 개에 애니메이션을 적용할 때 시작 시점을 설정할 수 있다.
animation() 애니메이션을 다른 곳에서 재사용할 때 useAnimation()과 함께 사용한다.
useAnimation() 다른 곳에 정의된 애니메이션을 사용한다. animation()과 함께 사용한다.
animateChild() 부모 컴포넌트와 자식 컴포넌트의 애니메이션을 동시에 실행할 때 사용한다.

References