astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

Vue.jsでAccordionを作ってみるく

こんにちわ。 今日は前回の続き(「絶対フォント感」を身につけようとすると新たな扉が開く話 - その1 - http://lab.astamuse.co.jp/entry/2018/06/06/131745)を書こうと思ってたのですが、 アクセス数がかなり少かった(;;)ので一旦お休みしてフロントエンドっぽい事書いていこうと思います。

概要

vueでやるとAPIやdataリストにタイトルや本文を入れたくなりますが、今回は以下の要望を満たすものを作っていこうと思います。

  • アコーディオンの中身(タイトルや本文)はhtml側で制御したい

それでは早速Vue.jsでAccordionを作ってみるく♡

step.1 下準備(html、css)

今回デザインはこんな感じで作っていきます。

See the Pen vue-accordion - html,css by 35n139e (@35n139e) on CodePen.

step.2 開閉機能を実装する

ひとまずアコーディオンの最低限の要素を実装していきます。

  • ボタンを押す
  • ターゲットコンテンツの開閉(toggle)



<div id="app">
  <js-accordion></js-accordion>
</div>



Vue.component('js-accordion', {
  template: `
  <div class="js-accordion" v-cloak>
    <button class="js-accordion--trigger" type="button" :class="{ '_state-open': isOpened }" @click="accordionToggle()">
      アコーディオン
    </button>
    <div class="js-accordion--target" :class="{ '_state-open': isOpened }" v-if="isOpened">
  <div class="js-accordion--body">
      アコーディオンの中身
  </div>
    </div>
  </div>
  `,
  data() {
    return {
      isOpened: false
    };
  },
  methods: {
    accordionToggle: function(){
      this.isOpened = !this.isOpened;
    },
  }
});

new Vue({
  el: '#app'
});


See the Pen vue-accordion - step2 by 35n139e (@35n139e) on CodePen.

step.3 タイトル・本文をhtml側で制御出来るようにする

冒頭でも書いたように今回のアコーディオンの要望は「アコーディオンの中身(タイトルや本文)はhtml側で制御したい」でしたので、'slot'や'props'を用いてそれを実現していきます。

slotが複数あるので、slotにnameを付けて紐づけしていきます。

<div id="app">
<js-accordion>
  <div slot="title">アコーディオン1</div>
  <div class="js-accordion--body" slot="body">
    <p>アコーディオン1の中身</p>
    <p>アコーディオン1の中身</p>
    <p>アコーディオン1の中身</p>
  </div>
</js-accordion>
Vue.component('js-accordion', {
  template: `
  <div class="js-accordion" v-cloak>
    <button class="js-accordion--trigger" type="button" :class="{ '_state-open': isOpened }" @click="accordionToggle()">
      <slot name="title"></slot>
    </button>
    <div class="js-accordion--target" :class="{ '_state-open': isOpened }" v-if="isOpened">
      <slot name="body"></slot>
    </div>
  </div>
  `
note
「アコーディオンの中身(見た目含め)は色々変わるけど、タイトルの部分は一緒なのでもう少しシンプルにマークアップできないかな?」 といった要望があった場合には、slotではなくpropsを用いて以下のように表現することも可能です。
<div id="app">
<js-accordion title="アコーディオン1">
  <div class="js-accordion--body">
    <p>アコーディオン1の中身</p>
    <p>アコーディオン1の中身</p>
    <p>アコーディオン1の中身</p>
  </div>
</js-accordion>


Vue.component('js-accordion', {
  template: `
  <div class="js-accordion" v-cloak>
    <button class="js-accordion--trigger" type="button" :class="{ '_state-open': isOpened }" @click="accordionToggle()">
      {{this.title}}
    </button>
    <div class="js-accordion--target" :class="{ '_state-open': isOpened }" v-if="isOpened">
      <slot></slot>
    </div>
  </div>
  `
  props: {
    title: { required: true },
  },
ほんの少しですがマークアップダイエットが出来ました。

See the Pen vue-accordion - step3 by 35n139e (@35n139e) on CodePen.


fin アニメーションをつけて完成

最後に開閉時のアニメーションを付けていきましょう

vueでanimationをつけるときは<transition>を使っていきます。

高さの伸縮は、cssだけでも表現可能なのですが制約があるので、今回はvueで開閉後の高さを取得してcssのtransitionでアニメーションさせて、 FadeInOutはCSS アニメーションで表現していきます。

まず、<transition>にイベントタイミングを設定していきます。 (詳しくはこちらhttps://jp.vuejs.org/v2/guide/transitions.html#JavaScript-%E3%83%95%E3%83%83%E3%82%AF

// 閉じた状態の高さは
el.style.height = '0'
// 開いた状態の高さは
el.style.height = el.scrollHeight + 'px';


jqueryのslideDown()ように高速でdomをいじるのではなく、 0px →開いた状態の高さpxをtransition: height 0.4s ease-in-outでアニメーションさせてあげれば表現出来ます。 (勿論jqueryでもこの方法は可能)

.js-accordion{
  &--target{
    transition: height 0.4s ease-in-out;
  }
  // (略)
  &-enter-active{
    animation-duration: 1s;
    animation-fill-mode: both;
    animation-name: js-accordion--anime__opend;
  }
  &-leave-active{
    animation-duration: 1s;
    animation-fill-mode: both;
    animation-name: js-accordion--anime__closed;
  }
}

@keyframes js-accordion--anime__opend {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
   }
}
@keyframes js-accordion--anime__closed {
  0% {
   opacity: 1;
  }

  100% {
    opacity: 0;
  }
}


// (略)
 template: `
(略)
<transition name="js-accordion" @before-enter="beforeEnter" @enter="enter" @before-leave="beforeLeave" @leave="leave">
  <div class="js-accordion--target" :class="{ '_state-open': isOpened }" v-if="isOpened">
    <slot name="body"></slot>
  </div>
</transition>
`,
// (略)
method:{
  beforeEnter: function(el) {
    el.style.height = '0';
  },
  enter: function(el) {
    el.style.height = el.scrollHeight + 'px';
  },
  beforeLeave: function(el) {
    el.style.height = el.scrollHeight + 'px';
  },
  leave: function(el) {
    el.style.height = '0';
}

Vue.jsでAccordion、完成です!

See the Pen vue-accordion - fin by 35n139e (@35n139e) on CodePen.



駆け足でしたが、以上で終わります。

今年はvue周りの書籍が複数出版されて更にVue.jsの普及が進みそうでうれしいですね。

Copyright © astamuse company, ltd. all rights reserved.