# Enter & Leave トランジション

Vue は、DOM からアイテムが追加、更新、削除されたときにトランジション効果を適用するための方法を複数提供しています:

  • 自動的に CSS トランジションやアニメーションのためのクラスを適用します。
  • Animate.css (opens new window) のようなサードパーティの CSS アニメーションライブラリと連携します。
  • トランジションフックが実行されている間、JavaScript を使って直接 DOM 操作を行います。
  • サードパーティの JavaScript アニメーションライブラリと連携します。

このページでは、entering/leaving とリストのトランジションについてのみ取り扱いますが、次の章では、状態のトランジション について扱います。

# 単一要素/コンポーネントのトランジション

Vue は、transition ラッパーコンポーネントを提供しています。このコンポーネントは、次のコンテキストにある要素やコンポーネントに entering/leaving トランジションを追加することを可能にします:

  • 条件付きの描画 (v-if を使用)
  • 条件付きの表示 (v-show を利用)
  • 動的コンポーネント
  • コンポーネントルートノード (Component root nodes)

これは、アクションのように見える非常にシンプルな例です:

<div id="demo">
  <button @click="show = !show">
    Toggle
  </button>

  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
1
2
3
4
5
6
7
8
9
const Demo = {
  data() {
    return {
      show: true
    }
  }
}

Vue.createApp(Demo).mount('#demo')
1
2
3
4
5
6
7
8
9
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
1
2
3
4
5
6
7
8
9

See the Pen Simple Transition Component by Vue (@Vue) on CodePen.

transition コンポーネントにラップされた要素が挿入あるいは削除されるとき、次のことが行われます:

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

  2. もし、トランジションコンポーネントが JavaScript フック を提供している場合は、適切なタイミングでそれらのフックが呼ばれます。

  3. もし、CSS トランジション/アニメーションが検出されず、JavaScript フックも提供されない場合、挿入、削除のいずれか、あるいは両方の DOM 操作を次のフレームでただちに実行します。(注意: ここでのフレームはブラウザのアニメーションフレームを指します。 Vue の nextTick のコンセプトのそれとは異なるものです)

# トランジションクラス

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

  1. v-enter-from: enter の開始状態。要素が挿入される前に適用され、要素が挿入された 1 フレーム後に削除されます。
  2. v-enter-active: enter の活性状態。トランジションに入るフェーズ中に適用されます。要素が挿入される前に追加され、トランジション/アニメーションが終了すると削除されます。このクラスは、トランジションの開始に対して、期間、遅延、およびイージングカーブを定義するために使用できます。
  3. v-enter-to: バージョン 2.1.8 以降でのみ利用可能です。 enter の終了状態。要素が挿入された 1 フレーム後に追加され (同時に v-enter が削除されます)、トランジション/アニメーションが終了すると削除されます。
  4. v-leave-from: leave の開始状態。トランジションの終了がトリガされるとき、直ちに追加され、1フレーム後に削除されます。
  5. v-leave-active: leave の活性状態。トランジションが終わるフェーズ中に適用されます。leave トランジションがトリガされるとき、直ちに追加され、トランジション/アニメーションが終了すると削除されます。このクラスは、トランジションの終了に対して、期間、遅延、およびイージングカーブを定義するために使用できます。
  6. v-leave-to: バージョン 2.1.8 以降でのみ利用可能です。 leave の終了状態。leave トランジションがトリガされた 1 フレーム後に追加され (同時に v-leave が削除されます)、トランジション/アニメーションが終了すると削除されます。

Transition Diagram

各クラスは、トランジションの名前が先頭に付きます。<transition> 要素に名前がない場合は、デフォルトで v- が先頭に付きます。例えば、<transition name="my-transition"> の場合は、v-enter-from クラスではなく、my-transition-enter-from となります。

v-enter-activev-leave-active は、次のセクションの例で見ることができるような、enter/leave トランジションで異なるイージングカーブの指定を可能にします。

# CSS トランジション

トランジションを実現する最も一般な方法として、CSS トランジションを使います。これはシンプルな例です:

<div id="demo">
  <button @click="show = !show">
    Toggle render
  </button>

  <transition name="slide-fade">
    <p v-if="show">hello</p>
  </transition>
</div>
1
2
3
4
5
6
7
8
9
const Demo = {
  data() {
    return {
      show: true
    }
  }
}

Vue.createApp(Demo).mount('#demo')
1
2
3
4
5
6
7
8
9
/* 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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

See the Pen Different Enter and Leave Transitions by Vue (@Vue) on CodePen.

# CSS アニメーション

CSS アニメーションは、CSS トランジションと同じように適用されますが、異なるのは v-enter-from が要素が挿入された直後に削除されないことです。しかし、animationend イベント時には削除されています。

これは簡潔にするために CSS ルールの接頭辞を除いた例です。

<div id="example-2">
  <button @click="show = !show">Toggle show</button>
  <transition name="bounce">
    <p v-if="show">
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris facilisis
      enim libero, at lacinia diam fermentum id. Pellentesque habitant morbi
      tristique senectus et netus.
    </p>
  </transition>
</div>
1
2
3
4
5
6
7
8
9
10
const Demo = {
  data() {
    return {
      show: true
    }
  }
}

Vue.createApp(Demo).mount('#demo')
1
2
3
4
5
6
7
8
9
.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.5);
  }
  100% {
    transform: scale(1);
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

See the Pen CSS Animation Transition Example by Vue (@Vue) on CodePen.

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

次の属性で、カスタムトランジションクラスを指定できます:

  • enter-from-class
  • enter-active-class
  • enter-to-class (2.1.8+)
  • leave-from-class
  • leave-active-class
  • leave-to-class (2.1.8+)

これらは、クラス名の規約を上書きします。これは、Vue のトランジションシステムと Animate.css (opens new window) のような既存の CSS アニメーションライブラリを組み合わせたいときに特に便利です。

こちらが例となります:

<link
  href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.0/animate.min.css"
  rel="stylesheet"
  type="text/css"
/>

<div id="demo">
  <button @click="show = !show">
    Toggle render
  </button>

  <transition
    name="custom-classes-transition"
    enter-active-class="animate__animated animate__tada"
    leave-active-class="animate__animated animate__bounceOutRight"
  >
    <p v-if="show">hello</p>
  </transition>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const Demo = {
  data() {
    return {
      show: true
    }
  }
}

Vue.createApp(Demo).mount('#demo')
1
2
3
4
5
6
7
8
9

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

Vue はトランジションが終了したことを把握するためのイベントリスナのアタッチを必要とします。イベントは、適用される CSS ルールに応じて transitionendanimationend のいずれかのタイプになります。あなたがトランジションとアニメーション、どちらか一方だけ使用する場合は、Vue は自動的に正しいタイプを判断することができます。

しかし、例えば、ホバーの CSS トランジション効果と Vue による CSS アニメーションのトリガの両方を持つ場合など、時には、同じ要素に両方を使うこともあるかもしれません。これらのケースでは、Vue に扱って欲しいタイプを type 属性で明示的に宣言するべきでしょう。この属性の値は、animation あるいは transition を取ります。

# 明示的なトランジション期間の設定

TODO: validate and provide an example

2.2.0 から新規追加

ほとんどの場合、 Vue は、自動的にトランジションが終了したことを見つけ出すことは可能です。デフォルトでは、 Vue はルート要素の初めの transitionend もしくは animationend イベントを待ちます。しかし、これが常に望む形とは限りません。例えば、幾つかの入れ子となっている内部要素にてトランジションの遅延がある場合や、ルートのトランジション要素よりも非常に長いトランジション期間を設けている場合の、一連のトランジションのまとまりなどです。

このような場合 <transition> コンポーネントがもつ duration プロパティを利用することで、明示的に遷移にかかる時間(ミリ秒単位)を指定することが可能です:

<transition :duration="1000">...</transition>
1

また、活性化時と終了時の期間を、個別に指定することも可能です:

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

# JavaScript フック

属性によって JavaScript フックを定義することができます:

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @enter-cancelled="enterCancelled"
  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
  @leave-cancelled="leaveCancelled"
  :css="false"
>
  <!-- ... -->
</transition>
1
2
3
4
5
6
7
8
9
10
11
12
13
// ...
methods: {
  // --------
  // ENTERING
  // --------

  beforeEnter(el) {
    // ...
  },
  // CSS と組み合わせて使う時、done コールバックはオプションです
  enter(el, done) {
    // ...
    done()
  },
  afterEnter(el) {
    // ...
  },
  enterCancelled(el) {
    // ...
  },

  // --------
  // LEAVING
  // --------

  beforeLeave(el) {
    // ...
  },
  // CSS と組み合わせて使う時、done コールバックはオプションです
  leave(el, done) {
    // ...
    done()
  },
  afterLeave(el) {
    // ...
  },
  // v-show と共に使うときだけ leaveCancelled は有効です
  leaveCancelled(el) {
    // ...
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

これらのフックは、CSS トランジション/アニメーション、または別の何かと組み合わせて使うことができます。

JavaScript のみを利用したトランジションの場合は、done コールバックを enterleave フックで呼ぶ必要があります。呼ばない場合は、フックは同期的に呼ばれ、トランジションはただちに終了します。また、 :css="false" を追加することで、CSS の検出をスキップすることを Vue に伝えられます。これによってわずかにパフォーマンスが改善するほか、CSS のルールの誤ったトランジションへの干渉を防ぐことができます。

今から例をみていきましょう。これは GreenSock (opens new window) を使ったシンプルな JavaScript トランジションの例です:

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.3.4/gsap.min.js"></script>

<div id="demo">
  <button @click="show = !show">
    Toggle
  </button>

  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
    :css="false"
  >
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const Demo = {
  data() {
    return {
      show: false
    }
  },
  methods: {
    beforeEnter(el) {
      gsap.set(el, {
        scaleX: 0.8,
        scaleY: 1.2
      })
    },
    enter(el, done) {
      gsap.to(el, {
        duration: 1,
        scaleX: 1.5,
        scaleY: 0.7,
        opacity: 1,
        x: 150,
        ease: 'elastic.inOut(2.5, 1)',
        onComplete: done
      })
    },
    leave(el, done) {
      gsap.to(el, {
        duration: 0.7,
        scaleX: 1,
        scaleY: 1,
        x: 300,
        ease: 'elastic.inOut(2.5, 1)'
      })
      gsap.to(el, {
        duration: 0.2,
        delay: 0.5,
        opacity: 0,
        onComplete: done
      })
    }
  }
}

Vue.createApp(Demo).mount('#demo')
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

See the Pen JavaScript Hooks Transition by Vue (@Vue) on CodePen.

# 初期描画時のトランジション

ノードの初期描画時にトランジションを適用したい場合は、appear 属性を追加することができます:

<transition appear>
  <!-- ... -->
</transition>
1
2
3

# 要素間のトランジション

あとで コンポーネント間のトランジション について説明しますが、v-if/v-else を使った通常の要素同士でもトランジションできます。最も共通の2つの要素のトランジションの例として、リストコンテナとリストが空と説明するメッセージの間で行うものがあります:

<transition>
  <table v-if="items.length > 0">
    <!-- ... -->
  </table>
  <p v-else>Sorry, no items found.</p>
</transition>
1
2
3
4
5
6

v-if を複数使ったり、ひとつの要素に対して動的プロパティでバインディングを行ういずれの場合でも、複数個の要素を対象にトランジションすることが可能です。例:

TODO: rewrite example and put in codepen example

<transition>
  <button v-if="docState === 'saved'" key="saved">
    Edit
  </button>
  <button v-if="docState === 'edited'" key="edited">
    Save
  </button>
  <button v-if="docState === 'editing'" key="editing">
    Cancel
  </button>
</transition>
1
2
3
4
5
6
7
8
9
10
11

このようにも書き換えることもできます:

<transition>
  <button :key="docState">
    {{ buttonMessage }}
  </button>
</transition>
1
2
3
4
5
// ...
computed: {
  buttonMessage() {
    switch (this.docState) {
      case 'saved': return 'Edit'
      case 'edited': return 'Save'
      case 'editing': return 'Cancel'
    }
  }
}
1
2
3
4
5
6
7
8
9
10

# トランジションモード

まだひとつ問題が残っています。以下のボタンをクリックしてください:

See the Pen Transition Modes Button Problem by Vue (@Vue) on CodePen.

それは、"on" ボタンと "off" ボタン間でトランジションを行うとき、片方のボタンがトランジションアウトして、別の片方がトランジションインするとき、両方のボタンが描画されてしまうことです。これは、<transition> のデフォルトの振る舞いです - entering と leaving は同時に起きます。

時には、これで問題なく、うまく動作する場合があります。例えば、位置が絶対位置で指定されているアイテムのトランジションを行うような場合です:

See the Pen Transition Modes Button Problem- positioning by Vue (@Vue) on CodePen.

ただ、そうするわけにはいかない場合や、より複雑な in と out の動きの扱いについてコーディネートする必要がある場合に、Vue は非常に便利な トランジションモード というユーティリティを提供しています。

  • in-out: 最初に新しい要素がトランジションして、それが完了したら、現在の要素がトランジションアウトする。
  • out-in: 最初に現在の要素がトランジションアウトして、それが完了したら、新しい要素がトランジションインする。

TIP

out-in がほとんどの場合に求めているものと感じるはずです。

今から、out-in を使って、先程の on/off ボタンのトランジションを書き換えてみましょう:

<transition name="fade" mode="out-in">
  <!-- ... the buttons ... -->
</transition>
1
2
3

See the Pen Transition Modes Button Problem- solved by Vue (@Vue) on CodePen.

特別なスタイルの追加無しで、ひとつのシンプルな属性を追加するだけでオリジナルのトランジションを修正できました。

これを利用することによって、以下のようなより表現力のある動きをコーディネートすることができます。二つの要素がお互いの間でトランジションしていますが、そのお互いの大きさが同じであり、水平方向の 0 の位置で重なるため、一つの要素の動きの流れに見えます。このような細かな調整は、よりリアルなマイクロインタラクションの表現で役立ちます:

See the Pen Transition Modes Flip Cards by Vue (@Vue) on CodePen.

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

コンポーネント間のトランジションは、 key 属性が必要ではないのでさらに単純です。代わりに、ただ 動的コンポーネント でラップするだけです:

TODO: update to Vue 3

<transition name="component-fade" mode="out-in">
  <component :is="view"></component>
</transition>
1
2
3
new Vue({
  el: '#transition-components-demo',
  data: {
    view: 'v-a'
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
.component-fade-enter-active,
.component-fade-leave-active {
  transition: opacity 0.3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}
1
2
3
4
5
6
7
8

TODO: example

Deployed on Netlify.
最終更新日: 10/10/2020, 9:44:55 PM