Vue.jsでアコーディオンを作った時のメモです。

サンプル

サンプルは下記リンクです。

Vue Accordion

コード

  • テンプレートのslotを用いてコンテンツを挿入しています。
  • Vue.jsのトランジションをJavaScriptフックに適用して、アコーディオンの開閉時の高さを動かしています。

html

<div class="container">

  <div id="app">

    <accordion>
      <div slot="title" class="title">アコーディオン1のタイトル</div>
      <section slot="content" class="content" >
        <h1>アコーディオン1のコンテンツ</h1>
        <p>アコーディオン1のコンテンツ</p>
      </section>
    </accordion>

    <accordion>
      <div slot="title" class="title">アコーディオン2のタイトル</div>
      <section slot="content" class="content">
        <h1>アコーディオン2のコンテンツ</h1>
        <p>アコーディオン2のコンテンツ</p>
      </section>
    </accordion>

    <accordion>
      <div slot="title" class="title">アコーディオン3のタイトル</div>
      <section slot="content" class="content">
        <h1>アコーディオン3のコンテンツ</h1>
        <p>アコーディオン3のコンテンツ</p>
      </section>
    </accordion>

  </div>

</div>

css

.container {
  padding-top: 48px;
}
.accordion-title-wrapper {
  position: relative;
  display: block;
  cursor: pointer;
}
.title {
  padding: 16px;
  background: #f6f7f7;
}
h1 {
  font-size: 175%;
}
.accordion-title-wrapper .accordion-button-icon {
  position: absolute;
  top: 50%;
  transform: translateY(-50%) rotate(0deg);
  right: 16px;
  transition: 0.2s ease-in-out;
}    
.accordion-content-wrapper {
  overflow: hidden;
  transition: 0.2s ease-in-out;
}
.accordion .content {
  padding: 16px;
}
.accordion .content :last-child {
  margin-bottom: 16px;
}
.accordion-title-wrapper.open .accordion-button-icon {
  transform: translateY(-50%) rotate(45deg);
}

javascript

Vue.component('accordion', {
  template: `
    <div class="accordion">
      <div class="accordion-title-wrapper" v-on:click="toggle()" v-bind:class="{ 'open': isOpen }">

        <slot name="title"></slot>

        <svg xmlns="http://www.w3.org/2000/svg" class="accordion-button-icon icon icon-tabler icon-tabler-plus" width="48" height="48" viewBox="0 0 24 24" stroke-width="1" stroke="#2c3e50" fill="none" stroke-linecap="round" stroke-linejoin="round">
          <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
          <line x1="12" y1="5" x2="12" y2="19" />
          <line x1="5" y1="12" x2="19" y2="12" />
        </svg>

      </div>

      <transition name="accordion" v-on:before-enter="beforeEnter" v-on:enter="enter" v-on:before-leave="beforeLeave" v-on:leave="leave">

        <div class="accordion-content-wrapper" v-if="isOpen">
          <slot name="content"></slot>
        </div>

      </transition>
      
    </div>
    `,

  data() {
    return {
      isOpen: false,
    };
  },
  methods: {
    toggle: function () {
      this.isOpen = !this.isOpen;
    },
    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';
    }
  }
});

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