本文へジャンプ

トランジション

Vue には、状態の変化に応じてトランジションやアニメーションを扱うのに役立つ 2 つの組み込みコンポーネントがあります:

  • <Transition> は、要素やコンポーネントが DOM に enter(挿入)、leave(削除)される時にアニメーションを適用するために提供されます。これについては、このページ内で説明しています。

  • <TransitionGroup> は、要素やコンポーネントが v-for で作成されたリスト内に inserted into(追加)、removed from(削除)、moved within(移動)される時にアニメーションを適用するために提供されます。こちらについては、次の章 で説明します。

この 2 つのコンポーネント以外にも、CSS クラスをトグルしたりスタイルバインディングによるステートドリブンアニメーションなど、Vue では他のテクニックを使ってアニメーションを適用することもできます。これらのテクニックは、アニメーションテクニック の章で説明されています。

<Transition> コンポーネント

<Transition> は組み込みコンポーネントです: つまり、どのコンポーネントのテンプレートでも、登録することなく利用することができます。これは、デフォルトのスロットで渡された要素やコンポーネントに対して、アニメーションを適用するために使用されます。enter と leave は次のいずれかによってトリガーされます:

  • v-if による条件付きレンダリング
  • v-show による条件付きの表示
  • <component> による、動的コンポーネントの切り替え
  • 特別な key 属性の変更

以下は、最も一般的な使い方です:

template
<button @click="show = !show">Toggle</button>
<Transition>
  <p v-if="show">hello</p>
</Transition>
css
/* これらのクラスが何をするかは後ほど説明します! */
.v-enter-active,
.v-leave-active {
  transition: opacity 0.5s ease;
}

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

hello

TIP

<Transition> はスロットのコンテンツとして、単一の要素またはコンポーネントのみをサポートします。コンテンツがコンポーネントの場合、そのコンポーネントも単一のルート要素のみでなければなりません。

<Transition> コンポーネントの要素が挿入または削除されると、以下のようなことが起こります:

  1. Vue は、対象の要素に CSS のトランジションやアニメーションが適用されているかどうかを自動的に判別します。それらが適用されている場合は、適切なタイミングで CSS トランジションクラスが追加 / 削除されます。

  2. JavaScript フックのリスナーが存在する場合、それらのフックが適切なタイミングで呼び出されます。

  3. CSS によるトランジション / アニメーションが検出されず、JavaScript のフックも提供されない場合、ブラウザーの次のアニメーションフレームで挿入・削除のための DOM 操作が実行されます。

CSS でのトランジション

トランジションクラス

以下は、enter/leave トランジションのために適用される 6 つのクラスです。

Transition Diagram

  1. v-enter-from: enter の開始状態。対象の要素が挿入される前に追加され、要素が挿入された 1 フレーム後に削除されます。

  2. v-enter-active: enter のアクティブ状態。enter のすべてのフェーズで適用されます。対象の要素が挿入される前に追加され、トランジション / アニメーションが終了した時に削除されます。このクラスを使用して、entering トランジションの持続時間、遅延、イージング関数を定義することができます。

  3. v-enter-to: enter の終了状態。要素が挿入された 1 フレーム後(v-enter-from の削除と同時)に追加され、トランジション / アニメーションが終了したときに削除されます。

  4. v-leave-from: leave の開始状態。leave トランジションが発生するとすぐに追加され、1 フレーム後に削除されます。

  5. v-leave-active: leave のアクティブ状態。leave のすべてのフェーズで適用されます。leaving トランジションが発生するとすぐに追加され、トランジション / アニメーションが終了すると削除されます。このクラスを使用して、entering トランジションの持続時間、遅延、イージング関数を定義することができます。

  6. v-leave-to: leave の終了状態。leave トランジションが発生した 1 フレーム後(v-leave-from の削除と同時)に追加され、トランジション / アニメーションが終了したときに削除されます。

後のセクションの例にもありますが、v-enter-activev-leave-active を利用して、enter / leave トランジションに異なるイージング関数を設定できます。

名前付きトランジション

name props を設定することでトランジションに名前をつけられます:

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

名前付きトランジションの場合、トランジションクラスには v の代わりに、その名前がプレフィックスとして付きます。例えば、上記のトランジションに適用されるクラスは v-enter-active ではなく、fade-enter-active となります。フェードトランジションの CSS は次のようになります:

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

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

CSS トランジション

<Transition> は、上記の基本的な例に見られるように、ネイティブの CSS トランジション と組み合わせて使用するのが最も一般的です。CSS プロパティ transition は、アニメーションを適用する CSS プロパティ、トランジションのの持続時間、イージング関数 など、複数の設定を一括で指定することができるショートハンドです。

以下は、より高度な例として、複数のプロパティをトランジションさせ、enter と leave で異なる持続時間とイージング関数を設定した例です:

template
<Transition name="slide-fade">
  <p v-if="show">hello</p>
</Transition>
css
/*
  enter および leave アニメーションでそれぞれ異なる
  持続時間とタイミング関数を利用できます
*/
.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;
}

hello

CSS アニメーション

ネイティブの CSS アニメーション は CSS トランジションと同様に適用されますが、*-enter-from は要素が挿入された直後には削除されていませんが、animationend イベント時には削除されているという違いがあります。

多くの CSS アニメーションは、シンプルに *-enter-active*-leave-active クラスで宣言することができます。以下はその例です:

template
<Transition name="bounce">
  <p v-if="show" style="text-align: center;">
    Hello here is some bouncy text!
  </p>
</Transition>
css
.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);
  }
}

Hello here is some bouncy text!

カスタムトランジションクラス

<Transition> に以下の props を渡すことで、カスタムトランジションクラスを指定できます:

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

これらは、従来のクラス名を上書きします。これは、Vue のトランジションシステムを、Animate.css などの既存の CSS アニメーションライブラリーと組み合わせたい場合に特に便利です:

template
<!-- 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>

トランジションとアニメーションの併用

Vue では、トランジションが終了したことを検知するために、イベントリスナーを登録する必要があります。イベントは、適用される CSS ルールに応じて、transitionend または animationend になります。どちらか一方だけを使用している場合、Vue は自動的に正しいタイプを検出できます。

しかし、いくつかのケースではトランジションとアニメーションを同じ要素に適用したい場合があります。例えば Vue がトリガーする CSS アニメーションと一緒にホバー時の CSS トランジション効果を持たせる場合などです。そのような場合は、Vue に関心を持ってもらいたいタイプを type props で明示的に宣言するべきです。 この属性の値は animation または transition を取ります:

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

ネストされたトランジションと明示的なトランジション期間の設定

トランジションのクラスは <Transiotion> の直接の子要素のみに適用されますが、ネストされた CSS セレクターを使ってネストされた要素をトランジションできます:

template
<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 は省略 */

さらに、enter の際にネストされた要素にトランジションの遅延を追加することで、交互の enter アニメーションシーケンスを作成することも可能です:

css
/* ネストされた要素の、交互アニメーション効果の enter の遅延 */
.nested-enter-active .inner {
  transition-delay: 0.25s;
}

しかし、これにはちょっとした問題があります。デフォルトでは、<Transition> コンポーネントは、ルートトランジションエレメントの 最初の transitionend または animationend イベントを購読することによって、トランジションがいつ終了したかを自動的に把握しようと試みます。しかし、ネストされたトランジションでは、すべてのネストされた要素のトランジションが終了するまで待つことが望ましい動作です。

このような場合、<transition> コンポーネントの duration props を利用することで、明示的にトランジションにかかる時間(ミリ秒単位)を指定することができます。合計の時間は、遅延と内側の要素のトランジションにかかる時間に一致しているべきです:

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

Playground で試す

必要に応じて、enter / leave のトランジションにかかる時間を個別に指定することができます:

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

パフォーマンスに関する考慮事項

上に示したアニメーションは、ほとんどが transformopacity といったプロパティを使っていることにお気づきかもしれません。これらのプロパティはアニメーションを行うのに効率的です、なぜなら:

  1. これらはアニメーション中のドキュメントレイアウトに影響を与えないので、アニメーションフレームごとにコストのかかる CSS レイアウトの計算を引き起こすことはありません。

  2. ほとんどのモダンブラウザーでは transform をアニメーション化する際に GPU ハードウェアアクセラレーションを活用することができます。

それに対して、heightmargin といったプロパティは CSS レイアウトを引き起こすので、アニメーションさせるのにかなりコストがかかるため、注意して使用する必要があります。CSS-Triggers のようなリソースで、どのプロパティがアニメーションするとレイアウトが引き起こされるかを確認できます。

JavaScript フック

JavaScript で <Transition> コンポーネントのイベントを購読することで、トランジション処理にフックすることができます。

html
<Transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
  <!-- ... -->
</Transition>
js
// 要素が DOM に挿入される前に呼ばれる
// 要素の "enter-from" 状態を設定するために使用する
function onBeforeEnter(el) {}

// 要素が DON に挿入された次のフレームで呼ばれる
// アニメーションを開始するときに使用する
function onEnter(el, done) {
  // トランジションの終了を示すために done コールバックを呼ぶ
  // CSS と組み合わせて使う場合は省略可能
  done()
}

// enter トランジションが完了したときに呼ばれる
function onAfterEnter(el) {}

// enter トランジションが完了前にキャンセルされたときに呼ばれる
function onEnterCancelled(el) {}

// leave フックの前に呼ばれる
// 大体の場合は、leave フックだけ使用するべき
function onBeforeLeave(el) {}

// leave トランジションの開始時に呼ばれる
// leave アニメーションを開始する時に使用する
function onLeave(el, done) {
  // トランジションの終了を示すために done コールバックを呼ぶ
  // CSS と組み合わせて使う場合は省略可能
  done()
}

// leave トランジションが完了して
// 要素が DOM から取り除かれた時に呼ばれる
function onAfterLeave(el) {}

// v-show トランジションでのみ有効
function onLeaveCancelled(el) {}
js
export default {
  // ...
  methods: {
    // 要素が DOM に挿入される前に呼ばれる
    // 要素の "enter-from" 状態を設定するために使用する
    onBeforeEnter(el) {},

    // 要素が DON に挿入された次のフレームで呼ばれる
    // アニメーションを開始するときに使用する
    onEnter(el, done) {
      // トランジションの終了を示すために done コールバックを呼ぶ
      // CSS と組み合わせて使う場合は省略可能
      done()
    },

    // enter トランジションが完了したときに呼ばれる
    onAfterEnter(el) {},

    // enter トランジションが完了する前にキャンセルされたときに呼ばれる
    onEnterCancelled(el) {},

    // leave フックの前に呼ばれる
    // 大体の場合は、leave フックだけ使用するべき
    onBeforeLeave(el) {},

    // leave トランジションの開始時に呼ばれる
    // leave アニメーションを開始する時に使用する
    onLeave(el, done) {
      // トランジションの終了を示すために done コールバックを呼ぶ
      // CSS と組み合わせて使う場合は省略可能
      done()
    },

    // leave トランジションが完了して
    // 要素が DOM から取り除かれた時に呼ばれる
    onAfterLeave(el) {},

    // v-show トランジションでのみ有効
    onLeaveCancelled(el) {}
  }
}

これらのフックは、CSS のトランジション / アニメーションと組み合わせて、あるいは単独で使用することができます。

JavaScript のみでトランジションを利用する場合、通常は :css="false" props を追加するのがよいでしょう。これは、CSS トランジションの自動検出をスキップするよう、Vue に明示的に指示します。これによって、パフォーマンスが若干向上するだけでなく、CSS ルールの誤ったトランジションへの干渉を防ぐことができます:

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

:css="false" を利用する場合は、トランジションがいつ終了するかを完全に制御する責任があります。この場合、 done コールバックを @enter@leave フックで呼ぶ必要があります。呼ばない場合は、フックは同期的に呼び出され、トランジションは直ちに終了します。

ここでは、GSAP ライブラリーを使ってアニメーションを行うデモを紹介します。もちろん、Anime.jsMotion One など、他のアニメーションライブラリーも利用可能です:

トランジションの再利用

トランジションは、Vue のコンポーネントシステムによって再利用できます。再利用可能なトランジションを作成するには、<Transition> コンポーネントをラップしたコンポーネントを作成し、スロットコンテンツを受け渡します:

vue
<!-- MyTransition.vue -->
<script>
// JavaScript フックのロジックを記載 ...
</script>

<template>
  <!-- 組み込みトランジションコンポーネントをラップ -->
  <Transition
    name="my-transition"
    @enter="onEnter"
    @leave="onLeave">
    <slot></slot> <!-- スロットコンテンツを渡す -->
  </Transition>
</template>

<style>
/*
  必要な CSS を記載 ...
  注意: スロットコンテンツに適用されないので
  <style scoped> はここでは使用しない
*/
</style>

これで MyTransition をインポートして、組み込みのトランジションと同じように使用することができます:

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

出現時のトランジション

ノードの初期レンダリング時にもトランジションを適用したい場合は、appear props を追加します:

template
<Transition appear>
  ...
</Transition>

要素間のトランジション

v-if / v-show による要素の切り替えに加えて、常に 1 つの要素のみ表示されるのを確信できる限り、v-if / v-else / v-else-if を使って 2 つの要素間を遷移させることもできます:

template
<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>
Click to cycle through states:

Playground で試す

トランジションモード

上記の例では、enter と leave を同時にアニメーションさせ、両方の要素が DOM に同時に存在するときのレイアウトの問題を避けるために、それらの要素について position: absolute にする必要がありました。

しかし、その選択を取れない、あるいはそれが望ましい動作ではない場合があります。例えば leave を先にアニメーションさせて、leave アニメーションが終了したにのみ enter を実行するようにしたいかもしれません。そのようなアニメーションを手動で調整することは非常に複雑ですが、幸い <Transition>mode props を渡すことで、そのような挙動を有効にすることができます:

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

以下は 1 つ前のデモで mode="out-in" を利用した場合の例です:

Click to cycle through states:

<Transition> では、あまり使われませんが mode="in-out" もサポートしています。

コンポーネント間のトランジション

<Transition>動的コンポーネント でも使用することができます:

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

動的トランジション

name のような <Transition> の props は動的にすることもできます !  これにより、状態の変化に応じて異なるトランジションを動的に適用することができます:

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

これは、Vue のトランジションクラスの規約を使って CSS トランジション / アニメーションを定義し、それらを切り替えて使用したい場合に便利です。

また、JavaScript のトランジションフックでは、コンポーネントの現在の状態に応じて異なる動作を適用することもできます。最後に、動的なトランジションを作成する究極の方法は、トランジションの性質を変更する props を受け取る再利用可能なトランジションを利用することです。安っぽく聞こえるかもしれませんが、制限はあなたの想像力だけです。

キー属性によるトランジション

トランジションを発生させるために、DOM 要素を強制的に再レンダリングする必要がある場合もあります。

このカウンターコンポーネントを例に考えてみましょう:

vue
<script setup>
import { ref } from 'vue';
const count = ref(0);

setInterval(() => count.value++, 1000);
</script>

<template>
  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
</template>
vue
<script>
export default {
  data() {
    return {
      count: 1,
      interval: null 
    }
  },
  mounted() {
    this.interval = setInterval(() => {
      this.count++;
    }, 1000)
  },
  beforeDestroy() {
    clearInterval(this.interval)
  }
}
</script>

<template>
  <Transition>
    <span :key="count">{{ count }}</span>
  </Transition>
</template>

もし key 属性がなかったら、テキストノードだけが更新されトランジションは発生しません。しかし、key 属性があることで、Vue は count が変更されるたびに新しい span 要素を作成するように認識するので、Transition コンポーネントは 2 つの異なる要素間のトランジションを実行できます。


関連

トランジションが読み込まれました