【vue】 transitionで要素の出現や削除時にアニメーションさせる

vueでは、項目の追加、更新、削除時に、だんだん出現したりなどのトランジション効果をかんたんに付けることができます。

使い方

追加や削除が発生する項目に対しトランジションをつけるには、transitionで囲みます。transitionで囲まれた要素がアニメーションをします。

<transition name="fade">
    <p v-if="visible">sample</p>
</transition>

transition時のアニメーションはCSSに書きます。クラス名は、{name}-enter-fromのような構成です。

つまり、transitionname="foobar"を設定したとすると、

  • foobar-enter-from
  • foobar-enter-to
  • foobar-enter-active
  • foobar-leave-from
  • foobar-leave-to
  • foobar-leave-active

の3つがアニメーション中に付与されます。enterは出現時、leaveは消滅時に付与されます。

付与されるclassは、アニメーションの進行によって変化します。

  1. v-enter-fromv-enter-activeが付与
    • class="v-enter-from v-enter-active"
  2. v-enter-from削除, v-enter-to付与
    • class="v-enter-active v-enter-to"
  3. アニメーションが終わると, v-enter-tov-enter-active削除
    • class=""

という流れです、削除時もfrom+active付与→from削除+to付与→activeto削除という、同じ流れです。

fromは一瞬で消えるため、css側のtransitionでそれをアニメーション対象として指定するのが一般的です。

transition時のクラスの変化
/*
 * 表示するときのアニメーション
 *
 * .xxx-enter-fromは, 表示状態になった直後に消える
 * .xxx-enter-activeは, 表示状態になる直前に付与され、アニメーションが終わると消える
 * .xxx-enter-toは, 表示状態になった直後に付与(同時にfromが消える)され, アニメーションが終わると消える
 */
.fade-enter-active {
    transition: opacity 1s ease;
}

.fade-enter-from {
    opacity: 0;
}

.fade-enter-to {
    color: #f00;
}

/*
 * 非表示にするときのアニメーション
 *
 * .xxx-leave-fromは、アニメーションが始まった瞬間に消える
 * .xxx-leave-activeは、非表示状態になる直前に付与され、アニメーションが終わると消える
 * .xxx-leave-toは、アニメーションが始まった瞬間に付与 (同時にfromが消える) され、アニメーションが終わると消える
 */
.fade-leave-active {
    transition: opacity 1s ease;
}

.fade-leave-from {
    /* opacity: 1; */
}

.fade-leave-to {
    opacity: 0;
}

See the Pen vue transition by Totori (@souki202) on CodePen.

実際のアプリケーションでうまく適用されない場合、cssの優先順位で負けている可能性があるので、セレクタを見直すと解決する可能性があります。

nameをv-bindすれば、値に応じて連動して使われるクラス名が変わるため、使用するアニメーションを簡単に切り替えることもできます。

初回描画にトランジション

要素が最初に描画されるタイミングでもトランジション可能です。appear属性を付けます。

<transition name="fade" appear>
    <p>foobar</p>
</transition>

v-if使用時のトランジション

例えば、次のような場合を考えてみます。

<button @click="type = (++type > 2 ? 0 : type)">表示切り替え</button>
<transition name="fade">
    <div v-if="type == 0" class="alert alert-success">成功</div>
    <div v-else-if="type == 1" class="alert alert-warning">警告</div>
    <div v-else-if="type == 2" class="alert alert-danger">失敗</div>
</transition>

このコードでは、ボタンを押すごとに表示されるメッセージが変化する、というものです。

transition内では最初の1つの要素しか表示できません。複数の要素を表示したい場合、この後紹介するtransition-groupを用いる必要があります。

これに対し、

/*
 * 表示するときのアニメーション
 */
.fade-enter-active {
  transition: opacity 1s ease;
}

.fade-enter-from {
  opacity: 0;
}

/*
 * 非表示にするときのアニメーション
 */
.fade-leave-active {
  transition: opacity 1s ease;
}

.fade-leave-to {
  opacity: 0;
}

/* opacityはデフォルト1なので書かなくて良い */

を適用すると、次のようになります。

See the Pen vue transition2 by Totori (@souki202) on CodePen.

一瞬両方表示されますが、これは正常な挙動です。というのも、トランジション中は要素が残っており、かつ消え始めるタイミングと表示が始まるタイミングが被っているためです。

解決1: 表示位置を同じにする

これを解決するには、例えばtransition対象にrelativeabsoluteを用います。

つまり、3つのメッセージの位置を、absoluteを使うことで全て同じ位置にするということです。

<div class="alert-container">
    <transition name="fade">
        <div v-if="type == 0" class="alert alert-success">成功</div>
        <div v-else-if="type == 1" class="alert alert-warning">警告</div>
        <div v-else-if="type == 2" class="alert alert-danger">失敗</div>
    </transition>
</div>
.alert-container {
    position: relative;
}

.alert-container > div {
    position: absolute;
    left: 0;
    right: 0;
}

See the Pen by Totori (@souki202) on CodePen.

解決2: out-inを用いる

out-intransitionのモードの1つで、消える要素が消え終わった後に、表示が始まるモードです。

<transition name="fade" mode="out-in">
    ...
</transition>

in-outというモードもありまず。表示が先に始まって、それが終わったら消える要素が消え始めるというものです。あまり使いません。

デフォルトは、消え始めるのと表示が始まるのが同時です。

See the Pen by Totori (@souki202) on CodePen.

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

付与されるそれぞれのクラス名は変更することができます。この機能は、既存の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>
source: https://v3.ja.vuejs.org/guide/transitions-enterleave.html#css-%E3%82%A2%E3%83%8B%E3%83%A1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3

こうすることで、.xxx-enter-activeanimate__animated animate__tadaに置き換わります。

See the Pen Untitled by Totori (@souki202) on CodePen.

以下のクラスを置換対象にできます。

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

この機能で指定しなかったクラスは、今まで通り.xxx-enter-toなどになります。

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>

それぞれ、vue側のmethodsに宣言します。つまり、@clickと同じようなイベントとして使えるということです。

const app = Vue.createApp({
    data() {
        return {
            type: 0,
        };
    },
    methods: {
        /**
         * enter
         */
        beforeEnter(el) {
            console.log(el);
            console.log("before enter");
        },
        // CSSでアニメーションを行う場合, doneの付与は自由です
        enter(el, done) {
            console.log(el);
            console.log("enter");
            // ...
            done(); // CSSでアニメーション終了の役割をします
        },
        afterEnter(el) {
            console.log(el);
            console.log("after enter");

            // ...
        },
        // 表示アニメーション中に非表示に切り替わった場合に呼ばれます
        enterCancelled(el) {
            console.log(el);
            console.log("enter cancelled");
            // ...
        },
    
        /**
         * leave
         */
        beforeLeave(el) {
            console.log(el);
            console.log("before leaving");
        },
        // CSSでアニメーションを行う場合, doneの付与は自由です
        leave(el, done) {
            console.log(el);
            console.log("leave");
            // ...
            done();
        },
        afterLeave(el) {
            console.log(el);
            console.log("before leaving");
        // ...
        },
        // 非表示アニメーション中に表示に切り替わった場合に呼ばれます
        leaveCancelled(el) {
            console.log(el);
            console.log("leave cancelled");
            // ...
        }
    }
});
result
<div class="alert alert-success">成功</div>
before leaving
<div class="alert alert-success">成功</div>
leave
<div class="alert alert-success">成功</div>
before leaving
<div class="alert alert-warning">警告</div>
before enter
<div class="alert alert-warning">警告</div>
enter
<div class="alert alert-warning">警告</div>
after enter

:css="false"は、CSSのアニメーションの検出をスキップする機能です。そのトランジションでCSSを利用しない場合に指定すると少し軽くなります。

done()は, 例えば非同期通信など、一定時間後に呼びたい場合に組み合わせると便利です。

// 1秒後にdoneする
enter(el, done) {
    setTimeout(() => {
        done();
    }, 1000);
}

まとめ

transitionは、要素の出現時と消滅時にアニメーションを行うのに便利です。v-ifを使用して複数の要素から1つだけを表示するという場合、何もしないとアニメーション中に2つ表示されるため、調整が必要です。

リストのトランジションには、transition-groupを用います、詳しくは下の記事にあります。

vue transition thumb

役に立ったらシェアしよう!