Nuxt3でアコーディオンメニューの動きを作る
Nuxt3でアコーディオンメニューの動きを作る
こんにちは、合同会社Stegの@keigoです。 今回は、Nuxt3 (Vue3) でアコーディオンメニューの動きを実装したときの手法を書いておきます。 もっといい実装の仕方があったらおしえてください。
完成品は以下のようなものです。
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()"があるかと思います。こちらのクリックイベントにて、閉じ開きをトリガーします。
開く前(閉じられた状態)は、以下のような状態になっています。

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

しかし、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クラスを付与し、高さを指定してあげることができます。
クリックして開くと、以下のような状態になります。

おまけ
ホバー時のオーバーレイを作る
これ地味に面倒なんですよね、やった人にはわかると思うのですが、hoverでbackground-colorを普通に変えると、padding部分が効かなくなってしまうんですよね。
↓こんなかんじになっちゃう

自分は、親要素を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>