Back

Nuxt3でアコーディオンメニューの動きを作る

Nuxt3でアコーディオンメニューの動きを作る

こんにちは、合同会社Stegの@keigoです。 今回は、Nuxt3 (Vue3) でアコーディオンメニューの動きを実装したときの手法を書いておきます。 もっといい実装の仕方があったらおしえてください。

完成品は以下のようなものです。 Vue.jsで実装するアコーディオンメニューのイメージgif Vuetifyでいう、ExpansionPanelsです。

まずはアニメーションなしで作る

template部分 (styleはまわりは省略)

<template>
  <div ref="expansionPanel" class="expansion-panel">
    <div @click="toggleExpansionPanel()" class="expansion-panel-title">
      <span>
        タイトル
      </span>
      <span class="arrow">
    </div>
    <div ref="hiddenExpansionPanel" class="expansion-panel-hidden-container">
      <span>コンテンツコンテンツ</span>
      <span>コンテンツコンテンツ</span>
    </div>
  </div>
</template>

上記コードの3行目に、@click="toggleExpansionPanel()"があるかと思います。こちらのクリックイベントにて、閉じ開きをトリガーします。

開く前(閉じられた状態)は、以下のような状態になっています。

Vue3で実装するアコーディオンメニューを開く前の内部状態

クリックして開くと、以下のような状態になります。

Vue3で実装するアコーディオンメニューを開く前の内部状態

しかし、heightが固定値pxではないと、transition: all 0.2s ease-out; のようなトランジションは効きません。 したがって、要素の高さのpxを取得し、固定値で指定してあげる必要があります。

閉じ開きのアニメーションを作る

要素の高さをVue3記法で取得するには、以下のようなコードになります。

<script setup>
const expansionPanelOpenedHeight = ref(0 + 'px')  // cssに入れる用の変数宣言
const expansionPanel = ref(null)
const hiddenExpansionPanel = ref(null)
onMounted(() => { 
  const topContainerHeight = expansionPanel.value.offsetHeight  // 開く前の要素の高さ
  const hiddeinContainerHeight = hiddenExpansionPanel.value.offsetHeight  // 隠れている部分の要素の高さ
  expansionPanelOpenedHeight.value = topContainerHeight + hiddeinContainerHeight + 'px' // 上二つの合計を固定値pxの文字列にして代入
})
</script>

上記コードによって、expansionPanelOpenedHeightという変数にXXXpxという値が格納されました。

あとはcssにて、

.open {
  height: v-bind(expansionPanelOpenedHeight);
}

と書いてあげることによって、変数が使用できます。 clickイベントなどによって上記 .openクラスを付与し、高さを指定してあげることができます。

クリックして開くと、以下のような状態になります。

Vue3で実装するアコーディオンメニューを開く前の内部状態

おまけ

ホバー時のオーバーレイを作る

これ地味に面倒なんですよね、やった人にはわかると思うのですが、hoverでbackground-colorを普通に変えると、padding部分が効かなくなってしまうんですよね。

↓こんなかんじになっちゃう padding部分がhoverで効かない

自分は、親要素をposition: relative;、擬似要素でposition: absolute (例の要素を重ねるやつ) したうえで、擬似要素のwidthとheight指定によってpadding部分もちゃんと塗ってくれるようにしました。 あとは、

&:hover {
    &::after {
      background-color: $black;
      opacity: 0.02;
    }
  }

でホバーしたら擬似要素の色と透明度を指定するようにしました。

矢印が回転するようにする

これは別のコンポーネントに切り出しました。 expansionPanelToggleというbooleanのpropsを渡してあげて、あとは子コンポーネントでclassを付与したりしなかったりしています。

<svg :class="{ rotate: expansionPanelToggle }"
  svg {
    transition: all 0.2s ease-out;
  }
.rotate {
  transform: rotate(180deg);
}

ちなみにVue3では以下のようにPropsを受け取ります

<script setup lang="ts">
interface Props {
  expansionPanelToggle: boolean
}
const props = withDefaults(defineProps<Props> (), {
  expansionPanelToggle: false
});
</script>