明けましておめでとうございます。今年初の記念すべき内容は、NuxtやVueでの連続したアコーディオンの実装です笑
アコーディオンだけではなく、複数の開閉メニューがある場合とかにも使えます。
今回はこんな感じのを作ります。
環境
nuxt 2.14.0
テンプレートを作成する
まずはテンプレート作成からです。
<template>
<div class="accordion">
<ul>
<li v-for="item in getContents" :key="item.id">
<button
type="button"
class="accordion__toggle"
>
<span v-text="item.name" />
</button>
<p v-text="item.content" />
</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
getContents() {
const data = [
{
id: 1,
name: 'アコーディオン1',
content: 'コンテンツコンテンツコンテンツ',
},
{
id: 2,
name: 'アコーディオン2',
content: 'コンテンツコンテンツコンテンツ',
},
{
id: 3,
name: 'アコーディオン3',
content: 'コンテンツコンテンツコンテンツ',
},
]
return data
},
},
}
</script>
<style lang="scss" scoped>
.accordion {
background-color: #fafafa;
color: #444;
width: 100%;
position: fixed;
top: 67px;
left: 0;
overflow: scroll;
height: 100vh;
padding: 36px 16px;
}
.accordion__toggle {
font-size: 22px;
font-weight: bold;
line-height: 2.818;
text-align: left;
display: block;
width: 100%;
border-bottom: 1px solid #ebebeb;
position: relative;
&::before,
&::after {
content: '';
display: inline-block;
width: 18px;
height: 3px;
background-color: #444;
position: absolute;
top: 50%;
right: 22px;
}
&::before {
transform: translate(0, -50%);
}
&::after {
transition: all 0.3s ease-in-out;
transform: translate(0, -50%) rotate(90deg);
}
}
</style>
アコーディオンの実装
フラグを作る
まずは開閉用のフラグを作成します。
今回のアコーディオンは配列の中身をv-forで展開しているのですが、この配列のindexを使用して開閉フラグを操作します。
そのためまずは配列から作成します。
<script>
export default {
data() {
return {
isOpen: [],
}
},
}
</script>
続いて、開閉するアコーディオンの数だけ作成したisOpenの配列にfalseフラグを挿入します。
created() {
this.isOpen = Array(this.getContents.length).fill(false)
},
これでisOpenにアコーディオンの数だけフラグが格納されました。
フラグをテンプレートで使用する
先程作成したテンプレートに作成したフラグを適用させていきます。
<li v-for="(item, index) in getContents" :key="item.id">
<button
type="button"
class="accordion__toggle"
:class="{ 'is-active': isOpen[index] }"
>
<span v-text="item.name" />
</button>
<p v-show="isOpen[index]" v-text="item.content" />
</li>
v-forの第二引数にindexを追加して、表示と非表示を切り替えたいコンテンツにv-showを追加しました。
また、buttonタグの+アイコンも、開閉中は−のアイコンに変えたいのでクラスを付与しました。
これに合わせてスタイルも追加しましょう。
.accordion__toggle {
// 略
&.is-active {
&::after {
transform: translate(0, -50%) rotate(0deg);
}
}
}
フラグの切り替え
開閉に対応するフラグをtrueに切り替えていきます。
関数を作成します。
methods: {
handleToggle(index) {
this.isOpen.splice(index, 1, !this.isOpen[index])
},
},
index番号を引数にとり、spliceメソッドを使用して対応する配列番号の値を反転させます。
つまりisOpen
のindex番目がfalse
だったらtrue
に、true
だったらfalse
に、という内容になります。
buttonタグでclick時に関数を呼び出すように追記します。
<button
type="button"
class="accordion__toggle"
:class="{ 'is-active': isOpen[index] }"
@click="handleToggle(index)"
>
<span v-text="item.name" />
</button>
これで開閉できるようになりました。
開閉にアニメーションをつける
ついでにせっかくなのでtransitionを使用して開閉アニメーションも追加します。
<transition
name="topSlide"
@before-enter="beforeEnter"
@enter="enter"
@before-leave="beforeLeave"
@leave="leave"
>
<p v-show="isOpen[index]" v-text="item.content" class="topSlide" />
</transition>
開閉するpタグにもtopSlideというクラス名を追加しました。
スタイルも追加します。
.topSlide {
transition: height 0.3s ease-in-out;
overflow: hidden;
}
.topSlide-enter-active {
animation-duration: 0.3s;
animation-fill-mode: both;
}
.topSlide-leave-active {
animation-duration: 0.3s;
animation-fill-mode: both;
}
イベントを使用して高さを取得する関数を作成します。
methods: {
// 略
beforeEnter(el) {
el.style.height = '0'
},
enter(el) {
el.style.height = el.scrollHeight + 'px'
},
beforeLeave(el) {
el.style.height = el.scrollHeight + 'px'
},
leave(el) {
el.style.height = '0'
},
}
これでアコーディオンっぽくなりました!
全体のコード
今までの全体のコードはこんな感じです。
<template>
<div class="accordion">
<ul>
<li v-for="(item, index) in getContents" :key="item.id">
<button
type="button"
class="accordion__toggle"
:class="{ 'is-active': isOpen[index] }"
@click="handleToggle(index)"
>
<span v-text="item.name" />
</button>
<transition
name="topSlide"
@before-enter="beforeEnter"
@enter="enter"
@before-leave="beforeLeave"
@leave="leave"
>
<p v-show="isOpen[index]" v-text="item.content" class="topSlide" />
</transition>
</li>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
isOpen: [],
}
},
computed: {
getContents() {
const data = [
{
id: 1,
name: 'アコーディオン1',
content: 'コンテンツコンテンツコンテンツ',
},
{
id: 2,
name: 'アコーディオン2',
content: 'コンテンツコンテンツコンテンツ',
},
{
id: 3,
name: 'アコーディオン3',
content: 'コンテンツコンテンツコンテンツ',
},
]
return data
},
},
created() {
// アコーディオンの数だけ開閉フラグを作成
this.isOpen = Array(this.getContents.length).fill(false)
},
methods: {
// メニューを開閉する
handleToggle(index) {
this.isOpen.splice(index, 1, !this.isOpen[index])
},
// スライド開閉要素の高さ取得
beforeEnter(el) {
el.style.height = '0'
},
enter(el) {
el.style.height = el.scrollHeight + 'px'
},
beforeLeave(el) {
el.style.height = el.scrollHeight + 'px'
},
leave(el) {
el.style.height = '0'
},
},
}
</script>
<style lang="scss" scoped>
.accordion {
background-color: #fafafa;
color: #444;
width: 100%;
position: fixed;
top: 67px;
left: 0;
overflow: scroll;
height: 100vh;
padding: 36px 16px;
}
.accordion__toggle {
font-size: 22px;
font-weight: bold;
line-height: 2.818;
text-align: left;
display: block;
width: 100%;
border-bottom: 1px solid #ebebeb;
position: relative;
&::before,
&::after {
content: '';
display: inline-block;
width: 18px;
height: 3px;
background-color: #444;
position: absolute;
top: 50%;
right: 22px;
}
&::before {
transform: translate(0, -50%);
}
&::after {
transition: all 0.3s ease-in-out;
transform: translate(0, -50%) rotate(90deg);
}
&.is-active {
&::after {
transform: translate(0, -50%) rotate(0deg);
}
}
}
.topSlide {
transition: height 0.3s ease-in-out;
overflow: hidden;
}
.topSlide-enter-active {
animation-duration: 0.3s;
animation-fill-mode: both;
}
.topSlide-leave-active {
animation-duration: 0.3s;
animation-fill-mode: both;
}
</style>
じつはこれはアコーディオンじゃない
アコーディオンといっているこの効果ですが、本当はアコーディオンとは言わないみたいですね。スライドトグルだったかな、、。
アコーディオンの定義はひとつ開いたら開いているものは閉じる、結果複数のコンテンツが開いていることがないもの、みたいです。
今回はアコーディオンの定義が曖昧な人が多かったのでこのような表現で記載しました。本当にアコーディオンを探していた方スミマセン・・!
アコーディオンにするなら
本物のアコーディオンを実装するのであればtrueに切り替える前にisOpenを初期化してしまえば良いです。
handleToggle(index) {
if (!this.isOpen[index]) {
this.isOpen = Array(this.getContents.length).fill(false)
}
this.isOpen.splice(index, 1, !this.isOpen[index])
},
終わりに
紛らわしい書き方をしてすみませんでした!
原理がわかると簡単にできるようになり応用も可能です。私はこの方法でチェックリストとかも作りました!
参考になればと思います!