본문 바로가기
front/vue

[vue] Transition

by juniKang 2022. 8. 8.

뷰는 상태 변화에 대한 응답으로 애니메이션과 트렌지션을 도와주는 두 가지 내장 컴포넌트를 제공한다.

  • 엘리먼트나 컴포넌트가 돔에 들어갈 때와 나갈 때 애니메이션을 적용하기 위한 <Transition> 이 있다. 이건 아래에 다룬다.
  • v-for 리스트안에서 엘리먼트나 컴포넌트가 삽입되고, 나가고 또는 이동할 때 애니메이션을 적용하기 위한 <TransitionGroup>이 있다. 이것은 다음 챕터에 다룬다.

이 두가지 컴포넌트 제외하고, CSS 클래스 토글링이나 스타일 바인딩을 통한 state-driven 애니메이션같은 다른 기술들을 사용해서 뷰에서 애니메이션을 적용할 수 있다. 이 추가적인 기술들은 애니메이션 테크닉 챕터에서 다룬다.

 


The <Transition> Component

<Transition> 은 내장된 컴포넌트다: 그래서 등록할 필요없이 모든 컴포넌트의 템플릿에서 사용할 수 있다. 디폴트 슬롯을 통해 엘리먼트나 컴포넌트에 들어갈 때, 나갈 때 애니메이션을 적용할 수 있다. 들어올 때, 나갈 때 애니메이션은 다음중 하나에 의해 트리거 된다:

  • v-if 를 통한 조건적인 렌더링
  • v-show 를 통한 조건적인 디스플레이
  • 스페셜 엘리먼트인 <component>를 통해 동적인 컴포넌트 토글링

다음은 가장 기본적인 사용예이다:

 <button @click="show = !show">Toggle</button>
 <Transition>
   <p v-if="show">hello</p>
 </Transition>
/* 이 클래스들이 무엇을 하는지 다음에 설명하겠다 */
.v-enter-acitve,
.v-leave-active {
  transition: opacity 0.5s ease;
}

.v-enter-from,
.v-leave-to {
  opacity: 0;
}
TIP
<Transition>은 슬롯 내용물로 오직 하나의 엘리먼트나 컴포넌트만을 지원한다. 만약 내용물이 컴포넌트라면, 컴포넌트는 단 하나의 루트 엘리먼트만을 가지고 있어야 한다.

<Transition> 컴포넌트 안에있는 엘리먼트가 삽입되거나 제거될 때 일어나는 일은 다음과 같다:

  1. 뷰는 자동적으로 타겟 엘리먼트가 CSS 트랜지션이나 애니메이션이 적용되었는지 감지한다. 감지되면, 다수의 CSS 트랜지션 클래스들이 타이밍에 맞추어 추가되고 제거된다. 
  2. 자바스크립트 훅을 위한 리스너가 있다면, 이 훅들은 적절한 타이밍에 호출된다.
  3. CSS 트랜지션/애니메이션이 감지되지 않고, 자바스크립트 훅도 제공되지 않으면, 삽입이나 삭제를 위한 돔 작업은 브라우저의 다음 애니메이션 프레임에 실행된다.

CSS-Based Transitions

트랜지션 클래스

삽입/ 삭제 트랜지션에 적용되는 여섯가지 클래스들이 있다.

  1. v-enter-from : 삽입 시작 상태. 엘리먼트가 삽입되기전에 추가되고, 엘리먼트가 삽입된 다음의 한 프레임에 제거된다.
  2. v-enter-active : 삽입 활성화 상태. 삽입단계 전체에 적용된다. 엘리먼트가 삽입되기 전에 추가되고, 트랜지션/애니메이션이 종료될 때 제거된다. 이 클래스는 삽입 트랜지션을 위한 지속 시간, 지연 시간, 이동 곡선을 정의할 때 사용된다.
  3. v-enter-to : 삽입 종료 상태. 엘리먼트가 삽입된 다음 한 프레임에 추가된다(v-enter-from이 제거될 때와 동시이다). 트랜지션/애니메이션이 종료될때 사라진다.
  4. v-leave-from : 제거 시작 상태. 제거 트랜지션이 트리거될 때 즉시 추가된다. 한 프레임 후에 제거된다.
  5. v-leave-action : 제거 활성화 상태. 제거 단계 전체에 적용된다. 제거 트랜지션이 트리거되는 즉시 추가되고, 트랜지션/애니메이션이 종료될 때 제거된다. 제거 트랜지션을 위한 지속 시간, 지연 시간, 이동 곡선을 정의할 때 사용된다.
  6. v-leave-to : 제거 종료 상태. 제거 트랜지션이 트리거되고 한 프레임 후에 추가 된다(v-leave-from이 제거될 떄와 동시이다). 트랜지션/애니메이션이 종료될 때 제거된다.

v-enter-active와 v-leave-active는 삽입 / 제거 트랜지션을 위한 서로 다른 특정한 이동 곡선을 지정할 수 있는 능력을 준다. 이어지는 섹션에서 예제를 확인해보자.

Named Transitions

트랜지션은 name 프로퍼티를 통해 이름지어질 수 있다:

<Transition name="fade">
  ...
</Transition>

이름지어진 트랜지션으로, 트랜지션 클래스들은 v 대신의 그 이름을 접두어로 사용할 수 있다. 예를 들어, 위의 트랜지션에 적용되는 클래스는 v-enter-active 대신에 fade-enter-active를 쓸 수 있다. fade 트랜지션을 위한 CSS는 다음과 같다:

.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

CSS Transitions

위의 예에서 봣듯이. <Transition> 은 네이티브 CSS 트랜지션과 대부분 혼합해서 사용된다. transition CSS 프로퍼티는 애니메이션 될 수 있는 프로퍼티들과 트랜지션의 지속시간, 그리고 이동 곡선을 포함하는 트랜지션의 다양한 측면을 특정할 수 있게 해주는 단축형이다. 

 

다음은 삽입과 제거에 다른 지속 시간과 이동 곡선을 적용하는, 여러가지 프로퍼티들을 전환한 좀 더 향상된 예제이다. 

<Transition name="slide-fade">
  <p v-if="show">hello</p>
</Transition>
/*
  삽입과 제거 애니메이션은 다른 지속 시간과 
  타이밍 함수를 사용할 수 있다.
*/
.slide-fade-enter-active {
  transition: all 0.3s ease-out;
}

.slide-fade-leave-active {
  transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);
}

.slide-fade-enter-from,
.slide-fade-leave-to {
  transform: translateX(20px);
  opacity: 0;
}

 

CSS 애니메이션

네이티브 CSS 애니메이션들은 CSS 트랜지션과 같은 방법으로 적용된다. 다른 점은, *-enter-from 은 엘리먼트가 삽입된 후에 즉시 제거되지는 않는다. 하지만 animationend 이벤트에 제거된다.

 

대부분의 CSS 애니메이션은, *-enter-active 와 *-leave-active 클래스들에서 간단하게 선언한다. 다음 예제이다:

<Transition name="bounce">
  <p v-if="show" style="text-align: center;">
    Hello here is some bouncy text!
  </p>
</Transition>
.bounce-enter-active {
  animation: bounce-in 0.5s;
}
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}

 

커스텀 트랜지션 클래스

<Transition>에 다음 프로퍼티들을 넘겨서 커스텀 트랜지션 클래스들을 특정할 수 있다 :

  • enter-from-class
  • enter-active-class
  • enter-to-class
  • leave-from-class
  • leave-active-class
  • leave-to-class

이것들은 기존 클래스 이름들을 오버라이드 한다. 뷰의 트랜지션 시스템을 Anmate.css같은 이미 존재하는 CSS 애니메이션 라이브러리와 같이 사용할때 특히 유용하다.

<!-- Animate.css가 이 페이지에 포함되었다고 가정한다 -->
<Transition
  name="custom-classes"
  enter-active-class="animate__animated animate__tada"
  leave-active-class="animate__animated animate__bounceOutRight"
>
  <p v-if="show">hello</p>
</Transition>

 

트랜지션과 애니메이션을 함께 사용하기

 뷰는 이벤트리스너를 붙이기 위해 트랜지션이 종료되었을 때를 알아야 한다. transitionend 나 animationend 둘 다, CSS규칙의 타입에 따라 적용된다. 만약 둘 중 하나만 사용할 경우, 뷰는 자동으로 적절한 타입을 감지한다.

 

하지만, 때로 둘다 같은 엘리먼트에 적용될 때가 있다. 예를 들면, 뷰에의해 트리거되는 CSS 애니메이션과 마우스를 올렸을 때 CSS 트랜지션 이펙트를 가지고 싶을 떄가 있다. 그럴 때는, animation 이나 transition을 type 프로퍼티로 넘겨서 뷰가 어떤걸 적용시켜야 하는지 명시적으로 타입을 선언해줘야 한다.

<Transition type="animation">...</Transition>

 

중첩된 트랜지션과 트랜지션 지속 시간 명시

비록 트랜지션 클래스들이 <Transition>에 있는 자식 엘리먼트에만 적용된다고 해도, 중첩된 CSS 셀렉터를 사용해서 중첩된 엘리먼트들에 트랜지션을 적용할 수 있다.

<Transition name="nested">
  <div v-if="show" class="outer">
    <div class="inner">
      Hello
    </div>
  </div>
</Transition>
/* 중첩된 엘리먼트를 타게팅하는 css 규칙 */
.nested-enter-active .inner,
.nested-leave-active .inner {
  transition: all 0.3s ease-in-out;
}

.nested-enter-from .inner,
.nested-leave-to .inner {
  transform: translateX(30px);
  opacity: 0;
}

/* ... 다른 필요한 CSS 는 생략 */

중첩된 엘리먼트가 삽입될 때 트랜지션 딜레이를 추가할 수 있어서, staggered 삽입 애니메이션 연쇄를 일으킬 수 있다.

/* staggered 이펙트를 하기 위해 중첩된 엘리먼트의 삽입을 지연시킨다. */
.nested-enter-active .inner {
  transition-delay: 0.25s;
}

하지만, 이런 방법은 작은 이슈를 발생시킨다. 기본적으로, <Transition> 컴포넌트는 루트 트랜지션 엘리먼트에 첫번째 transitioned 나 animationed 이벤트를 리스닝해서 트랜지션이 종료되었다고 생각한다. 중첩된 트랜지션에서는, 우리가 생각했던 이벤트는 모든 중첩 엘리먼트들의 트랜지션이 종료될 떄까지 기다려야 한다.

 

<transition> 컴포넌트에 있는 duration 프로퍼티를 사용해서 명시적으로 트랜지션 지속시간 (밀리세컨즈)를 특정할 수 있다. 전체 지속시간은 중첩 엘리먼트의 트랜지션 지속시간을 더한 지속시간이어야 한다.

<Transition :duration="550">...</Transition>

 

필요하다면, 객체를 이용해서 삽입과 제거 지속시간을 분리해서 특정할 수 있다:

<Transition :duration="{ enter: 500, leave: 800 }">...</Transition>

 

성능을 위한 고려사항

애니메이션은 위처럼 transform 과 opacity 같은 프로퍼티들을 사용한다. 이 프로퍼티들은 애니메이션에 사용하기에 효율적이다 왜냐하면:

  1. 애니메이션이 재생되는 동안 document 레이아웃에 영향을 끼치지 않는다. 그래서 매 애니메이션 프레임마다 값비싼 CSS 레이아웃 계산을 트리거하지 않는다.
  2. 대부분의 최신 브라우저들은 transform을 애니메이팅할 때 GPU 하드웨어 가속을 레버리지 할 수 있다.

이와 대조적으로, height나 margin같은 프로퍼티들은 CSS 레이아웃을 트리거한다. 그래서 그것들은 애니메이션 비용이 더 비싸다. 그리고 문제를 일으킬 수 있다. 어떤 프로퍼티가 애니메이트 할 때 레이아웃을 트리거하는지 보기위해 CSS-Triggers같은 걸 사용해서 자원을 체크할 수 있다.


자바스크립트 훅

<Transition> 컴포넌트에 있는 이벤트를 리스닝해서 자바스크립트로 트랜지션 과정을 훅 할 수 있다.

<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
  <!-- ... -->
</Transition>
// 엘리먼트가 DOM으로 삽입되기 전에 호출된다.
// 엘리먼트의 "enter-from" 상태를 설정하기 위해 사용할 수 있다.
function onBeforeEnter(el) {}

// 엘리먼트가 삽입되고 한 프레임 후에 호출된다.
// 삽입 애니메이션을 시작하기위해 사용할 수 있다.
function onEnter(el, done) {
  // 트랜지션 종료를 가리키기위해 done 콜백을 호출한다.
  // 선택사항으로, CSS와 혼합해서 사용할때 사용된다.
  done()
}

// 삽입 트랜지션이 종료되면 호출된다.
function onAfterEnter(el) {}
function onEnterCancelled(el) {}

// 제거 훅 전에 호출된다.
// 대부분, 그냥 leave 훅을 사용하면 된다.
function onBeforeLeave(el) {}

// 제거 트랜지션이 시작되면 호출된다.
// 제거 애니메이션을 시작할때 사용한다.
function onLeave(el, done) {
  // 트랜지션 종료를 가르키기위해 done 콜백을 호출한다.
  // 선택사항으로, CSS와 혼합해서 사용한다.
  done()
}

// 엘리먼트가 DOM에서 제거되고,
// 트랜지션이 종료되었을 때 호출한다.
function onAfterLeave(el) {}

// v-show 트랜지션과만 가능하다.
function onLeaveCancelled(el) {}

이 훅들은 CSS 트랜지션, 애니메이션 들을 혼합해서 사용할 때 사용한다.

 

자바스크립트만 사용하는 트랜지션을 사용할 때는, 보통 :css="false" 프로퍼티를 더하는 것은 좋은 생각이다. 이건 명시적으로 뷰에게 자동 CSS 트랜지션 감지를 스킵하라고 알려준다. 약간의 성능향상도 있지만, 이건 트랜지션을 우연히 방해하는 CSS룰을 막아준다:

<Transition
  ...
  :css="false"
>
  ...
</Transition>

:css="false" 로, 트랜지션이 종료되었을 때 전적으로 컨트롤할 책임이 있다. 이런경우, done 콜백이 @enter와 @leave 훅에 필요하다. 그렇지 않으면 훅들이 동기적으로 호출되고, 트랜지션이 즉시 종료될 것이다.

 

애니메이션들을 수행하기위한 여기 GreenSock 라이브러리를 사용한 데모가 있다. 물론, Anime.js나 Motion One같은 다른 애니메이션 라이브러리를 사용해도 된다.


재사용 가능한 트랜지션

트랜지션은 뷰의 컴포넌트 시스템에 의해 재사용될 수 있다. 재사용가능한 트랜지션을 만들기 위해, <Transition> 컴포넌트를 감싼 컴포넌트를 만들고, slot 으로 내용을 넘긴다:

<!-- MyTransition.vue -->
<script>
// 자바스크립트 훅 로직...
</script>

<template>
  <!-- 내장 트랜지션 컴포넌트를 감싼다. -->
  <Transition
    name="my-transition"
    @enter="onEnter"
    @leave="onLeave">
    <slot></slot> <!-- 슬롯 컨텐츠를 넘긴다. -->
  </Transition>
</template>

<style>
/*
  필요한 CSS...
  Note: slot 컨텐츠에 적용되지 않으니
  <style scoped>사용을 피하라.
*/
</style>

이제 MyTransition은 imported 하고 내장된 것처럼 사용할 수 있다:

<MyTransition>
  <div v-if="show">Hello</div>
</MyTransition>

Transition on Appear

만약 노드의 처음 렌더에 트랜지션을 적용하고 싶다면, apper 프로퍼티를 추가할 수 있다.

<Transition appear>
  ...
</Transition>

엘리먼트들 사이에 트랜지션

v-if나 v-show로 엘리먼트를 토글링하는 것에 더해서, v-if / v-else / v-else-if 를 사용해서 엘리먼트들을 트랜지션 할 수 있다. 순간에 보여지는 엘리먼트가 오직 하나인 한 트랜지션이 가능하다:

<Transition>
  <button v-if="docState === 'saved'">Edit</button>
  <button v-else-if="docState === 'edited'">Save</button>
  <button v-else-if="docState === 'editing'">Cancel</button>
</Transition>

트랜지션 모드

이전 예제에서, 삽입하고 제거되는 엘리먼트들은 동시간에 애니메이션 된다. 그리고 position: absolute로 엘리먼트들이 돔에 존재할 때 레이아웃 이슈를 피할 수 있다.

 

하지만, 경우에따라 선태사항이 아니다. 또는 생각했던 것처럼 동작이 안된다. 우리는 아마도 제거 엘리먼트가 먼저 끝나고, 그리고 삽입 엘리먼트는 제거 애니메이션이 종료된 후에 삽입되기를 바랬다. 이런 애니메이션들을 수작업으로 하는건 매우 복잡하다. 행운이게도, <Transition>에 mode 프로퍼티를 넘겨서 가능하게 할 수 있다.

<Transition mode="out-in">
  ...
</Transition>

 

<Transition>은 또한 mode="in-out" 도 지원한다.

 


컴포넌트에서 트랜지션

<Transition> 은 동적인 컴포넌트에서도 사용될 수 있다.

<Transition name="fade" mode="out-in">
  <component :is="activeComponent"></component>
</Transition>

동적인 트랜지션

name같은 <Transition> 프로퍼티들은 동적일 수 있다! 상태 변화에 기반하여 동적으로 다른 트랜지션을 적용할 수 잇게 해준다.

<Transition :name="transitionName">
  <!-- ... -->
</Transition>

정의된 CSS 트랜지션/ 애니메이션을 뷰의 트랜지션 클래스 컨벤션을 사용하면서 다른것과 바꾸고 싶을 때 유용하다.

 

컴포넌트의 현재 상태에 기반한 자바스크립트 트랜지션 훅의 다른 행동들을 적용할 수도 있다. 마지막으로, 동적인 트랜지션을 만드는 궁극적인 방법은 재사용 가능한 트랜지션 컴포넌트를 사용하는 것이다. 오글거릴 수 있지만, 유일한 한계는 상상력이다.

'front > vue' 카테고리의 다른 글

[vue] Vue and Web Components  (1) 2022.08.09
[vue] TransitionGroup  (0) 2022.08.08
[vue] watch()  (0) 2022.08.08
[vue] ref, reactive 반응형  (0) 2022.08.07
[vue] app.mount()  (0) 2022.07.30

댓글