SMARTCAMP Engineer Blog

スマートキャンプ株式会社(SMARTCAMP Co., Ltd.)のエンジニアブログです。業務で取り入れた新しい技術や試行錯誤を知見として共有していきます。

Vue.js + Elelment UI + Lottieでお手軽にいい感じなUI/UXを作ってみる

f:id:tkgwy:20190418183653j:plain デザイナー兼エンジニアの葉栗です!

スマートキャンプでは以前からWebフロントエンド開発にVue.jsを取り入れています。

Vue.jsなどコンポーネント指向のフレームワークは、UIフレームワークも豊富で、お手軽にリッチなUIが構築できるのでいいですよね。

今回は私のお気に入りの、Vue.js + Elelment UI + Lottie というライブラリを使って、数十分でできる簡易的なログインページを作ってみようと思います。

0から構築をはじめて、レイアウト設計、ElementUIで実装、Lottie組み込み、完成といった感じで詳しく説明していきます!

完成画面 😊

f:id:haguri:20190417194623g:plain

使用技術

Vue.js

言わずとしれたJSフレームワークです。 コンポーネント指向に基づいて設計し、SFC(Single File Component)として実装するフレームワークです。 最近はそこから一歩踏み込んで、AtomicDesignをベースに設計するケースが増えていますね。

弊社でもVue.jsでAtomicDesignでSPA!のようなプロダクト開発を進めています!(これのツラミなんかは別記事で書こうかな)

Element UI

Vue.jsのコンポーネントライブラリです。

他にVuetifyやBootstrap等ありますが、ElementUIはデフォルトであたっているCSSが少なく、デザインする上でも使い勝手がいいので採用しています。

Lottie(ロッティー)

2017年2月にAirbnbが発表したWeb、iOS、Android、React Nativeなどのアニメーションライブラリです。

LottieはJSONデータをリソースファイルとして使用しているので、軽量でスムーズなアニメーションを実装することができます。

実装します!

事前準備

yarnは事前にインストールお願いします。

npm install -g yarn

Vue CLIをインストール

yarn global add @vue/cli
vue -V
// バージョンが表示されればOK
// 3.6.3

プロジェクトを作成

名前は「new-app」というプロジェクトにします。

vue create new-app

? Please pick a preset: (Use arrow keys)
❯ default (babel, eslint)
  Manually select features

今回は、説明を簡単にするために「default」で作成します。 (vue routerやvuexを利用する場合は、「Manually select features」を選択して設定していきます)

🎉  Successfully created project new-app.
👉  Get started with the following commands:

 $ cd new-app
 $ yarn serve

作成が完了したら、表示されているコマンドを入力して、アプリケーションを起動してみます!

cd new-app
yarn serv

ビルドが開始され、サーバーが立ち上がります。 http://localhost:8080/ にアクセスして、以下のようになっていれば完成!

f:id:haguri:20190417120006p:plain

ElementUIをインストール

vue add element
? How do you want to import Element? (Use arrow keys)
❯ Fully import
  Import on demand

Elementをどのようにインストールしたいか。 「Fully import」と「Import on demand」のいずれかを選択します。 今回は「Fully import」を選択して、全てImportします。

? How do you want to import Element? Fully import
? Do you wish to overwrite Element's SCSS variables? (y/N)

ElementUI用のSCSS変数を上書きするかどうか確認されます。 今回は、「y」を選択します。

? How do you want to import Element? Fully import
? Do you wish to overwrite Element's SCSS variables? Yes
? Choose the locale you want to load (Use arrow keys)
❯ zh-CN
  zh-TW
  af-ZA
  ar
  bg
  ca
  cs-CZ
(Move up and down to reveal more choices)

最後に、上下キーで移動して「ja」を選択

✔  Successfully invoked generator for plugin: vue-cli-plugin-element
   The following files have been updated / added:

     src/element-variables.scss
     src/plugins/element.js
     package.json
     src/App.vue
     src/main.js
     yarn.lock

   You should review these changes with git diff and commit them.

上記のような表示がされれば成功です。

先程の、http://localhost:8080/ にアクセスして、el-buttonが挿入されていれば、Elemen UIの導入完了です。 f:id:haguri:20190417121650p:plain

ログイン画面を作成

ここから実際にUIを作っていきます!

今回はシンプルに、以下のApp.vueだけを変更していきます!

不要コードの削除

App.vueにデフォルトで実装されているものを消してしまいましょう。

// ./src/App.vue
<template>
  <div id="app">
  </div>
</template>

<script>
export default {
  name: 'app'
}
</script>

<style>
</style>

コンポーネント(Element UI)の配置

Element UIを画像のような構成で配置していきます。 f:id:haguri:20190418145555p:plain

// ./src/App.vue
<template>
  <div id="app">
    <el-card class="login-card">
      <!-- divでタイトル追加 -->
      <div class="login-title">ログイン</div>
      <el-form :model="loginForm">
          <el-form-item label="メールアドレス" prop="email">
              <el-input type="text" v-model="loginForm.email" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="パスワード" prop="password">
              <el-input type="password" v-model="loginForm.password" autocomplete="off"></el-input>
          </el-form-item>
          <el-button type="primary" :loading="checking" @click="handleLogin">ログイン</el-button>
      </el-form>
    </el-card>
  </div>
</template>

<script>
export default {
  name: 'app',
  data () {
    return {
      checking: false,
      loginForm: {
        email: '',
        password: '',
      }
    }
  },
  methods: {
    handleLogin() {
      this.checking = true
    }
  }
}
</script>

<style>
  body {
    text-align: center;
    /* いい感じの背景の色 */
    background-color: #E0EAF6;
  }

  .login-title {
    font-size: 32px;
    font-weight: 600;
    margin-bottom: 20px;
  }

  .login-card {
    max-width: 800px;
    margin: auto 0;
  }
</style>

これで基本的はフォーム部分のUIはできました。 次は動きのある部分を追加していきます。

Lottieの導入

yarn add vue-lottie

Lottieを使うためのコンポーネント作成

公式ドキュメントを参考にしながら、Lottie.vueを追加します!

// ./src/components/Lottie.vue
<template>
  <div :style="style" ref="lavContainer"></div>
</template>

<script>
import lottie from 'lottie-web';

export default {
  props: {
    options: {
      type: Object,
      required: true
    },
    height: Number,
    width: Number,
  },
  data () {
    return {
      style: {
        width: this.width ? `${this.width}px` : '100%',
        height: this.height ? `${this.height}px` : '100%',
        overflow: 'hidden',
        margin: '0 auto'
      }
    }
  },
  mounted () {
    this.anim = lottie.loadAnimation({
        container: this.$refs.lavContainer,
        renderer: 'svg',
        loop: this.options.loop !== false,
        autoplay: this.options.autoplay !== false,
        animationData: this.options.animationData.default,
        rendererSettings: this.options.rendererSettings
      }
    );
    this.$emit('animCreated', this.anim)
  }
}
</script>

これでLottieが使えるようになったので、画像の動く顔の部分を追加していきます。 f:id:haguri:20190418145640p:plain

アニメーション追加

Lottieのアニメーションは自分でAfter Effectsで作成もできますし、誰かが作ったものを使用することもできます!

https://lottiefiles.com/ ここで検索して気に入ったのがあれば、JSONデータでダウンロードできるので、それをアプリケーション内でimportして、使用します。

まずは、この動く顔を追加してみます。 lottiefiles.com

src/assets 配下にダウンロードしたファイルを「welcome.json」として配置します。 それをApp.vueでimportします。

// ./src/App.vue
<template>
  <div id="app">
    <el-card class="login-card">
      <!-- lottieを追加 -->
      <lottie class="login-lottie" :options="welcomeLottie" :height="300" :width="300" :animCreated="handleAnimation"/>
      <div class="login-title">ログイン</div>
      <el-form :model="loginForm">
          <el-form-item label="メールアドレス" prop="email">
              <el-input type="text" v-model="loginForm.email" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="パスワード" prop="password">
              <el-input type="password" v-model="loginForm.password" autocomplete="off"></el-input>
          </el-form-item>
          <el-button type="primary" :loading="checking" @click="handleLogin">ログイン</el-button>
      </el-form>
    </el-card>
  </div>
</template>

<script>
// Lottieを使用
import Lottie from "@/components/Lottie.vue"
// 動く顔のアニメーション
import * as welcome from "@/assets/welcome.json"

export default {
  name: 'app',
  // 追加
  components: {
    Lottie
  },
  data () {
    return {
      checking: false,
      loginForm: {
        email: '',
        password: '',
      }
    }
  },
  // 追加
  computed: {
    welcomeLottie () {
      return { animationData: welcome }
    }
  },
  methods: {
    // 追加
    handleAnimation (anim) {
      this.anim = anim
    },
    handleLogin() {
      this.checking = true
    }
  }
}
</script>

<style>
  body {
    text-align: center;
    background-color: #E0EAF6;
  }

  .login-title {
    font-size: 32px;
    font-weight: 600;
    margin-bottom: 20px;
  }

  .login-card {
    max-width: 800px;
    margin: 200px auto 0;
    /* 動く顔の位置調整のため */
    padding: 120px 0 20px;
  }

  /* 動くやつの位置調整 */
  .login-lottie {
    position: absolute;
    top: 32px;
    right: 0;
    bottom: 0;
    left: 0;
    margin: 0 auto;
  }
</style>

ログインできた幸せを祝福する花火を追加

最後は画像のように一気に、Element UIとLottieを追加していきます。 f:id:haguri:20190418151640p:plain

// ./src/App.vue
<template>
  <div id="app">
    <el-card class="login-card">
      <lottie class="login-lottie" :options="welcomeLottie" :height="300" :width="300" :animCreated="handleAnimation"/>
      <div class="login-title">ログイン</div>
      <el-form :model="loginForm">
          <el-form-item label="メールアドレス" prop="email">
              <el-input type="text" v-model="loginForm.email" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="パスワード" prop="password">
              <el-input type="password" v-model="loginForm.password" autocomplete="off"></el-input>
          </el-form-item>
          <el-button type="primary" :loading="checking" @click="handleLogin">ログイン</el-button>
      </el-form>
    </el-card>
    <!-- ダイアログ追加 -->
    <el-dialog :visible.sync="dialogTableVisible">
      <div class="login-title">Welcome</div>
      <!-- 花火のアニメーション -->
      <lottie :options="fireworksLottie" :height="300" :width="300" :animCreated="handleAnimation"/>
    </el-dialog>
  </div>
</template>

<script>
import Lottie from "@/components/Lottie.vue"
import * as welcome from "@/assets/welcome.json"
// 花火のアニメーション
import * as fireworks from "@/assets/fireworks.json"

export default {
  name: 'app',
  components: {
    Lottie
  },
  data () {
    return {
      checking: false,
      dialogTableVisible: false,
      loginForm: {
        email: '',
        password: '',
      }
    }
  },
  computed: {
    welcomeLottie () {
      return { animationData: welcome }
    },
    fireworksLottie () {
      return { animationData: fireworks }
    }
  },
  methods: {
    handleAnimation (anim) {
      this.anim = anim
    },
    handleLogin() {
      this.checking = true
      // 演出のためです...。追加しました。
      setTimeout(() => {
        this.checking = false
        this.dialogTableVisible = true
      }, 1000)
    }
  }
}
</script>

<style>
  body {
    text-align: center;
    background-color: #E0EAF6;
  }

  .login-title {
    font-size: 32px;
    font-weight: 600;
    margin-bottom: 20px;
  }

  .login-card {
    max-width: 800px;
    margin: 200px auto 0;
    padding: 120px 0 20px;
  }

  .login-lottie {
    position: absolute;
    top: 32px;
    right: 0;
    bottom: 0;
    left: 0;
    margin: 0 auto;
  }
</style>

■ 使用したアニメーション lottiefiles.com lottiefiles.com

完成!

以下に追加したソースコードを記載します。

// ./src/App.vue
<template>
  <div id="app">
    <el-card class="login-card">
      <lottie class="login-lottie" :options="welcomeLottie" :height="300" :width="300" :animCreated="handleAnimation"/>
      <div class="login-title">ログイン</div>
      <el-form :model="loginForm">
          <el-form-item label="メールアドレス" prop="email">
              <el-input type="text" v-model="loginForm.email" autocomplete="off"></el-input>
          </el-form-item>
          <el-form-item label="パスワード" prop="password">
              <el-input type="password" v-model="loginForm.password" autocomplete="off"></el-input>
          </el-form-item>
          <el-button type="primary" :loading="checking" @click="handleLogin">ログイン</el-button>
      </el-form>
    </el-card>
    <el-dialog :visible.sync="dialogTableVisible">
      <div class="login-title">Welcome</div>
      <lottie :options="fireworksLottie" :height="300" :width="300" :animCreated="handleAnimation"/>
    </el-dialog>
  </div>
</template>

<script>
import Lottie from "@/components/Lottie.vue"

import * as welcome from "@/assets/welcome.json"
import * as fireworks from "@/assets/fireworks.json"

export default {
  name: 'app',
  components: {
    Lottie
  },
  data () {
    return {
      checking: false,
      dialogTableVisible: false,
      loginForm: {
        email: '',
        password: '',
      }
    }
  },
  computed: {
    welcomeLottie () {
      return { animationData: welcome }
    },
    fireworksLottie () {
      return { animationData: fireworks }
    }
  },
  methods: {
    handleAnimation (anim) {
      this.anim = anim
    },
    handleLogin() {
      this.checking = true
      // 演出のため
      setTimeout(() => {
        this.checking = false
        this.dialogTableVisible = true
      }, 1000)
    }
  }
}
</script>

<style>
  body {
    text-align: center;
    background-color: #E0EAF6;
  }

  .login-title {
    font-size: 32px;
    font-weight: 600;
    margin-bottom: 20px;
  }

  .login-card {
    max-width: 800px;
    margin: 200px auto 0;
    padding: 120px 0 20px;
  }

  .login-lottie {
    position: absolute;
    top: 32px;
    right: 0;
    bottom: 0;
    left: 0;
    margin: 0 auto;
  }
</style>
// ./src/components/Lottie.vue
<template>
  <div :style="style" ref="lavContainer"></div>
</template>

<script>
import lottie from 'lottie-web';
export default {
  props: {
    options: {
      type: Object,
      required: true
    },
    height: Number,
    width: Number,
  },
  data () {
    return {
      style: {
        width: this.width ? `${this.width}px` : '100%',
        height: this.height ? `${this.height}px` : '100%',
        overflow: 'hidden',
        margin: '0 auto'
      }
    }
  },
  mounted () {
    this.anim = lottie.loadAnimation({
        container: this.$refs.lavContainer,
        renderer: 'svg',
        loop: this.options.loop !== false,
        autoplay: this.options.autoplay !== false,
        animationData: this.options.animationData.default,
        rendererSettings: this.options.rendererSettings
      }
    );
    this.$emit('animCreated', this.anim)
  }
}
</script>

f:id:haguri:20190417194623g:plain

これでにくめない顔のログイン画面の、すごい祝福されるログイン画面ができました 😊

さいごに

Element UIとLottieは導入が簡単だし、ライブラリが豊富なので、すぐにアプリケーションをリッチにすることができます!

今回のLottieの使い方は少し大げさ(笑)ですが、ボタンクリック時のインタラクションや、ローディング表示などで導入することで、UXも良くなりますし、なにより見た目のクオリティも格段に上げることができると思います。

ぜひ使ってみてください!

また弊社ではデザインブログもやっています!(私も書いてます!) スマートキャンプのプロダクトデザインに込めた思いや、デザイン小話など書いていますので見ていただければうれしいです!

note.mu

ライター:葉栗 雄貴 / Haguri Yuki(Designer & Engineer)