1. Home
  2. JavaScript
  3. Vue.js
  4. Vue.jsのtransitionアニメーションとSassの導入

Vue.jsのtransitionアニメーションとSassの導入

  • 公開日
  • カテゴリ:Vue.js
  • タグ:JavaScript,sass,Vue,transition,transition-group,Animation
Vue.jsのtransitionアニメーションとSassの導入

Vue.js をはじめとした JavaScript でのアプリケーション開発では、 より高価値な UX(ユーザー体験)を提供する為にリッチな UI(ユーザーインターフェース) を構築する機会も多いですが、その中でもアニメーションは良く用いられる手法の一つです。

また、Vue.js では SFC に style も一緒に記述出来る利点がありますが、 それもやはりメンテナンス性を考え、Sass などのいわゆる CSS 拡張言語で書いていきたいところです。

今回は Vue.js での Sass の導入と、transition アニメーションを行っていきます。

Contents

  1. 開発環境
  2. Vue.js で Sass を使う
    1. Sass のインストール
    2. Sass を記述する
  3. transition コンポーネント
    1. クラス名の変更
  4. transition-group コンポーネント

開発環境

今回の開発環境は以下の通りです。

  • Vue.js 2.x
  • Vue CLI 3.x
  • Node.js 12.x
  • npm 6.5

SFC(単一ファイルコンポーネント)を単体で動かすので、 インスタントプロトタイピングの為のグローバルアドオン「cli-service-global 」 がインストールされている必要があります。

Vue CLI を使った開発環境構築の詳細については以下からも確認できます
Vue CLI で Vue.js の開発環境をサクッと構築する

Vue.js で Sass を使う

Nuxt や vue create などのプロジェクト作成時に明示的に Sass を入れている以外は基本的に Sass は入っていないのでインストールする必要があります。

Sass のインストール

まずは Sass をインストールします。プロジェクトルートへ移動し、以下の npm コマンドで SASS コンパイラをインストールします。

# SASS コンパイラ インストール
npm install sass-loader node-sass

これで Sass のインストールは完了です。

Sass を記述する

では実際に Sass を使用してみます。 style要素に lang 属性を付与するだけで Sass を記述する事が出来るようになります。

<style lang="scss">

これだけであとは Sass をコンパイルしてくれるようになります。

今回は以下のようなコンポーネントを作成し、Sass を記述しました。

<template>
    <div class="container">
        <div class="items">
            <div v-for="item in items" :key="item.id" class="item">{{ item.name }}</div>
        </div>
    </div>
</template>

<script>
    export default {
        name: "app",
        data () {
            return {
                items: [
                    { id: 1, name: 'item01' },
                    { id: 2, name: 'item02' },
                    { id: 3, name: 'item03' },
                    { id: 4, name: 'item04' },
                    { id: 5, name: 'item05' },
                    { id: 6, name: 'item06' },
                    { id: 7, name: 'item07' },
                    { id: 8, name: 'item08' },
                    { id: 9, name: 'item09' }
                ]
            }
        }
    }
</script>

<style lang="scss" scoped>
    $item-bg: #a0d8ef;

    html {
        height: 100%;
        width: 100%;

        body {
            height: 100%;
            margin: 0;
            padding: 0;
            position: relative;
            text-align: center;
            width: 100%;

            .items {
                left: 50%;
                position: absolute;
                top: 50%;
                transform: translateY(-50%) translateX(-50%);
                width: 330px;
            }

            .item {
                animation: 1s ease 0s pulse infinite;
                background: $item-bg;
                border-radius: 4px;
                float: left;
                height: 75px;
                margin: 5px;
                width: 100px;
                text-align: center;
            }
        }
    }

    @for $i from 1 through 9 {
        .item {
            &:nth-child(#{$i}) {
                animation-delay: $i * (1s / 18);
            }
        }
    }

    @keyframes pulse {
        0% {
            background: $item-bg;
            transform: scale(1);
        }
        25% {
            background: darken($item-bg, 10%);
            transform: scale(1.015);
        }
        50% {
            background: $item-bg;
            transform: scale(1);
        }
    }
</style>

以下の vue コマンドを叩いてブラウザから動作確認します。

# コンポーネント が設置されている場所まで移動
cd /path/to/dir

# app.vue 実行
vue serve app.vue --open

Sass がコンパイルされ、style として適用されている事が確認できました。
サンプルページ

transition コンポーネント

Vue.js では、CSS トランジションやアニメーションの為に transition ラッパーコンポーネントが提供されていて、これを用いると より簡潔に実装する事が出来ます。

具体的には、transition コンポーネントでラップした要素に対して、その要素が追加や削除が行われた時にそのタイミングに応じたクラス名を付与(entering/leaving トランジション)します。 それに対してトランジッションを設定する事で、アニメーションを実現できます。

簡単な例として、ボタン押下でメッセージがフェードイン・フェードアウトするコードを書いてみます。

<template>
    <div class="container">
        <div class="msgBox">
            <transition>
                <p v-show="isShow" class="message">{{ message }}</p>
            </transition>
        </div>
        <button @click="toggle">{{ btnLabel }}</button>
    </div>
</template>

<script>
    export default {
        name: "app",
        data () {
            return {
                message: 'Hello, world!',
                show: false
            }
        },
        computed: {
            isShow () {
              return this.show
            },
            btnLabel () {
                return this.show ? 'hide' : 'show'
            }
        },
        methods: {
            toggle () {
                this.show = !this.show
            }
        }
    }
</script>

<style lang="scss" scoped>
    .v-enter, .v-leave-to {
        opacity: 0;
    }
    .v-enter-to, .v-leave {
        opacity: 1;
    }
    .v-enter-active, .v-leave-active {
        transition: opacity 300ms;
    }

    .container {
        margin: 20px;

        .msgBox {
            height: 40px;
        }

        button {
            padding: 5px 10px;
        }
    }
</style>

ポイントは以下です。

<transition>
    <p v-show="isShow" class="message">{{ message }}</p>
</transition>

P要素を transition コンポーネントでラップしています。こうする事で、ラップされた P要素を対象として以下で style を設定します。

<style lang="scss" scoped>
    .v-enter, .v-leave-to {
        opacity: 0;
    }
    .v-enter-to, .v-leave {
        opacity: 1;
    }
    .v-enter-active, .v-leave-active {
        transition: opacity 300ms;
    }
</style>

entering(表示時)と leaving(非表示時)に付与されるクラスに対してトランジッションを設定しフェードイン・フェードアウトを実現しています。

それぞれのタイミングで付与されるクラスについては以下の通りです。

.v-enter 挿入アニメーション開始時に付与 .v-enter-to 挿入アニメーション終了時に付与 .v-enter-active 挿入アニメーション開始〜終了まで付与 transition をここで設定 .v-leave 削除アニメーション開始時に付与 .v-leave-to 削除アニメーション終了時に付与 .v-leave-active 削除アニメーション開始〜終了まで付与 transition をここで設定 ではこれを動作させてみます。以下の vue コマンドを叩いて、ブラウザから確認します。

vue serve app.vue --open

transition コンポーネントを用いたフェードイン・フェードアウトが実装できました。
サンプルページ

クラス名の変更

また、transition コンポーネントに name 属性を付与すれば、クラス名を[.xxx-enter]へ変更出来ます。 複数のアニメーションを定義する際に使えます。

例えば、今作成したのはフェードイン・フェードアウトなので、name 属性に「fade 」を指定した場合は以下の様になります。

<template>
    <div class="container">
        <div class="msgBox">
            <transition name="fade">
                <p v-show="isShow" class="message">{{ message }}</p>
            </transition>
        </div>
        <button @click="toggle">{{ btnLabel }}</button>
    </div>
</template>

<script>
    export default {
        name: "app",
        data () {
            return {
                message: 'Hello, world!',
                show: false
            }
        },
        computed: {
            isShow () {
              return this.show
            },
            btnLabel () {
                return this.show ? 'hide' : 'show'
            }
        },
        methods: {
            toggle () {
                this.show = !this.show
            }
        }
    }
</script>

<style lang="scss" scoped>
    .fade-enter, .fade-leave-to {
        opacity: 0;
    }
    .fade-enter-to, .fade-leave {
        opacity: 1;
    }
    .fade-enter-active, .fade-leave-active {
        transition: opacity 300ms;
    }

    .container {
        margin: 20px;

        .msgBox {
            height: 40px;
        }

        button {
            padding: 5px 10px;
        }
    }
</style>

サンプルページ

transition-group コンポーネント

例えばリストのように複数の要素を扱ったりする場合は、transition-group コンポーネントが使えます。 いちいち1つ1つの要素を transition コンポーネントでラップする必要が無いので便利です。

<template>
    <div class="container">
        <div class="item_wrapper">
            <div class="items">
                <transition-group name="list">
                    <div v-for="item in items" :key="item.id" class="item" v-show="item.show">{{ item.name }}</div>
                </transition-group>
            </div>
        </div>
        <div class="btns">
            <button v-for="item in items" :key="item.id" class="switch" :class="{ hiding: !item.show }" @click="toggle(item.id)">{{ item.id }}</button>
        </div>
    </div>
</template>

<script>
    export default {
        name: "app",
        data () {
            return {
                items: [
                    { id: 1, name: 'item01', show: true },
                    { id: 2, name: 'item02', show: true },
                    { id: 3, name: 'item03', show: true },
                    { id: 4, name: 'item04', show: true },
                    { id: 5, name: 'item05', show: true },
                    { id: 6, name: 'item06', show: true },
                    { id: 7, name: 'item07', show: true },
                    { id: 8, name: 'item08', show: true },
                    { id: 9, name: 'item09', show: true }
                ],
            }
        },
        methods: {
            toggle (id) {
                let target = this.items.find((item) => {
                    return item.id === id
                })
                target.show = !target.show
            }
        }
    }
</script>

<style lang="scss" scoped>
    $item-bg: #a0d8ef;
    .item_wrapper {
        width: 100%;
    }

    .items {
        margin: auto;
        width: 330px;
    }

    .item {
        animation: 1s ease 0s pulse infinite;
        background: $item-bg;
        border-radius: 4px;
        float: left;
        height: 75px;
        margin: 5px;
        width: 100px;
        text-align: center;
    }

    .btns {
        width: 100%;
        clear: both;
        text-align: center;
    }

    .hiding {
        background-color: #bfbfbf;
    }

    @for $i from 1 through 9 {
        .item {
            &:nth-child(#{$i}) {
                animation-delay: $i * (1s / 18);
            }
        }
    }

    @keyframes pulse {
        0% {
            background: $item-bg;
            transform: scale(1);
        }

        25% {
            background: darken($item-bg, 10%);
            transform: scale(1.015);
        }

        50% {
            background: $item-bg;
            transform: scale(1);
        }
    }

    .list-enter, .list-leave-to {
        opacity: 0;
    }
    .list-enter-to, .list-leave {
        opacity: 1;
    }
    .list-enter-active, .list-leave-active {
        transition: opacity 300ms ease-out;
    }
</style>

使い方は transition コンポーネントと変わりません。まとめてラップしてあげるだけです。

ではこれを動作させてみます。以下の vue コマンドを叩いて、ブラウザから確認します。

vue serve app.vue --open

transition-group コンポーネントを用いたフェードイン・フェードアウトが実装できました。
サンプルページ

まとめ

Vue.js では簡単に Sass の導入が行えるので、style を効率的に記述する為にもおすすめです。 transition コンポーネントも、CSS トランジッションで消耗せずスマートに定義できるのでぜひ試してみてください。
サンプルコード

Author

rito

  • Backend Engineer
  • Tokyo, Japan
  • PHP 5 技術者認定上級試験 認定者
  • 統計検定 3 級