この記事をおすすめしたい人
- WordPressしか知らないのにNuxt.jsを始めようとしてる人
- Nuxt.jsでハンバーガーメニューを実装したい人
- v-onとかv-bindを実践で勉強したい人
- v-forとかv-ifを実践で勉強したい人
- つまりオレ
何も知らない超初心者が脱WordPressしたくてNuxt.jsでサイト構築していくシリーズです。
ひとまずNuxt.jsで作ったサイトを公開しました。
今回は、よくあるハンバーガーメニューをNuxt.js(Vue.js)式で実装しながら、Vue.jsの基本的な書き方について勉強したいと思います。
WordPressの上でな!
※ このブログはWordPress製ですが、Nuxt.jsに移行予定です
このページの目次
ハンバーガーメニューを作るモジュールはあるのか?
これまでいろんなモジュールに助けられてきたので、今回もきっとハンバーガーメニューモジュールがあるんだろうと検索してみました。
vue-tasty-burgersとかvue-burger-buttonとかが出てきました。
確かに立派なハンバーガーボタンを作るコンポーネントが追加されるっぽいです。
でもこれ…
メニューじゃなくてボタンを作るだけのモジュールちゃうのん?
公式やその他サイトの見てみたんですが、そのサンプルはいずれも肝心な部分↓が抜けています。
methods: {
onToggle (active) {
// Toggle menu
}
}
これはおそらくですね…。
Nuxtでサイト作ろうとしてる人でハンバーガーメニュー展開も自分でできない人なんかいないからだと思います…。
しょうがないので、Vue.jsのテンプレート構文やら条件付きレンダリングやらを勉強しながら自前で実装してみることにしました。
ハンバーガーメニューに求められる条件
自前で実装するにあたり、改めてハンバーガーメニューの動作を確認してみます。
- 開くボタンを押すとメニュー展開
- 閉じるボタンを押すとメニューを閉じる
- メニュー内リンクを押すとメニューを閉じる
- メニュー外を押してもリンクを閉じる
JavaScriptとかjQueryとかで一般的にこれをどう実装するのか分かりませんが、余計な学習コストは増やしたくないので、展開時にクラス名を追加してCSSで開閉するものとします。
メニュー内リンクを押すとメニューを閉じる?
ピンとこないかもしれませんが、ハンバーガーメニューからページ内リンクで移動するケースを考えてみてください。
ページ遷移が発生しないので、それ用の処理を入れておかないとメニューが閉じないんです。
Nuxt.jsでサイトを作るとSPA(Single Page Application)になるようで、別ページへ移動した場合にも同様の現象が発生しました。 なのでこれは必須の処理です。
メニュー外を押してもリンクを閉じる?
ハンバーガーメニューの幅を80%とかにすると、左右どちらかに余白ができます。
この余白部分をクリックした時にもメニューを閉じさせるように、ということです。
これは必須じゃないと思いますが、僕はけっこうこういう操作をするので実装したいと思います。
基本的な処理の流れ
以下の4機能を実装する前提です。
- 開くボタンを押すとメニュー展開
- 閉じるボタンを押すとメニューを閉じる
- メニュー内リンクを押すとメニューを閉じる
- メニュー外を押してもリンクを閉じる
ページ内にひとつの変数を持たせておいて、デフォルトをfalseとします。
開くボタンを押すと変数をtrueに変更、閉じるボタン/リンク/メニュー外を押した際にはfalseになるようにします。
そしてこの変数がtrueの場合、メニューを囲っているdivに「open」とクラス名を追加。 falseの場合はクラスを削除。
これでいけそうですね。
まずは展開状態を示す変数の確保
Vue.jsのdataメソッドを使います。
export default {
data() {
return {
flag: false,
}
},
}
この「data return」という格好がなかなかに理解しにくかったですが、これは「ページ内で使い回す変数の宣言」をする場所です。
関数じゃない。
詳しくは「data return」の記事を見てください。
今回は、上のように「flag」という変数を初期値falseで用意します。
こうすると、<script>内からは「this.flag」で、<template>内からは「flag」で変数にアクセスできるようになります。
開くボタンの実装
解説サイトでは見た目も整えた状態のコードで説明してあることが多いですが、僕はあれが嫌いなので、以下、該当部分だけのめっちゃシンプルなコードで書きます。 HTMLやCSSの実装はお任せします。
ではボタンのHTMLコードを。
<button id="menu-open">(略)</button>
後でCSSで操作するように一応IDだけ付けています。
このボタンに「クリックしたらflagをtrueに変更」というコードを追加します。 これにはVue.jsのテンプレート構文の「v-on」というのを使います。
<button id="menu-open" v-on:click="flag = true">(略)</button>
このように書くと、クリック時にダブルクオーテーション内の処理が実行されます。 <form>のsubmitだと「v-on:submit」みたいです。
上のように処理が短い場合にはそのまま書くこともできますし、処理が長い場合には「methods」で関数を定義しておいて、関数を呼ぶこともできます。
v-onの書き方の注意点
「v-on」のダブルクオーテーション内は「生のテキスト」ではなく「コード記述部分」です。
どういうことかというと、
v-on:click="title = トップページ"
のような書き方ではエラーが出るんです。
v-on:click="title = 'トップページ'"
文字列を使う場合はシングルクオーテーションで囲まなければいけないようです。
見た目が変にHTMLなせいで最初これで躓いてしまいました。
このルールは「v-on」に限らず、「v-for」「v-if」などでも同様です。
閉じるボタンの実装
開くボタンの逆操作をするだけなので簡単です。
<button id="menu-close" v-on:click="flag = false">(略)</button>
開くボタンと閉じるボタンを兼用する場合
開くボタンをクリックすると「×」マークに変わって、今度は閉じるボタンになる、みたいなタイプです。
その場合はtrueやfalse固定で代入してはまずいので、この↓ように。
<button id="menu-open" v-on:click="flag = !flag">(略)</button>
現在値を反転させて代入します。 開いていればfalse、閉じていればtrue。
メニュー背景の実装
メニューが展開した際の余白の部分のコードです。
<div id="menu-bg">(略)</div>
CSSはこんな↓感じ。
#menu-bg {
position: fixed;
display: none;
top: 0;
left: 0;
z-index: 9998;
width: 100%;
height: 100%;
background: black;
opacity: .5;
&.open {
display: block;
}
}
必要条件はこの↓ような感じ。
- 初期状態では非表示
- fixedで画面全体を覆うように
- 透過した黒とかにしてうっすら後ろが見えるように
- z-indexはページよりは上で、メニュー本体よりは下
- openクラスが追加された際に表示
この背景に「変数flagがtrueならクラスopenを追加」するコードを書きます。 今度は「v-bind」というのを使います。
<div id="menu-bg" v-bind:class="flag ? 'open' : ''">(略)</div>
省略して書けるようで、その場合はこう↓。
<div id="menu-bg" :class="{open:flag}">(略)</div>
クリックした際に閉じる機能の追加
閉じる機能自体は先ほどと同じなので、先ほどの「v-on」を追加します。
<div id="menu-bg" :class="{open:flag}" v-on:click="flag = false">(略)</div>
メニュー本体の実装
開閉するメニュー本体のコードです。
<nav id="menu-body">(略)</nav>
CSSはこんな↓感じ。
#menu-body {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
width: 80%;
height: 100%;
transform: translateX(-100%);
&.open {
transform: translateX(0%);
}
}
必要条件はこの↓ような感じ。
- 初期状態では左に100%ずれて隠れている
- fixedで幅と高さは任意
- z-indexは一番上(メニュー背景より上)
- openクラスが追加された際に出現
上記CSSは左からスライドしてくるコードなので、右スライドに変える場合は赤字の「left」を「right」に、「translateX(-100%)」を「translateX(100%)」に変更します。
これにメニュー背景と同様に、「変数flagがtrueならクラスopenを追加」するコードを加えます。
<nav id="menu-body" :class="{open:flag}">
リンクをクリックでメニューを閉じる機能
メニュー本体の中身はだいたい次のようなコードだと思います。
<ul>
<li><nuxt-link to="(略)">(略)</nuxt-link></li>
<li><nuxt-link to="(略)">(略)</nuxt-link></li>
<li><nuxt-link to="(略)">(略)</nuxt-link></li>
</ul>
<nuxt-link>はNuxt.jsでの<a>タグです。 SPAで先読みに使うんだとか。 「href」じゃなくて「to」になる点に注意。
ここに先ほどと同様に「クリックすると変数flagをfalse」にするコードを追加します。
<nuxt-link to="(略)" v-on:click="flag = false">
どういうわけかこれだとメニューが閉じません。 この↓ように<a>タグにするとメニューが閉じます。
<a href="(略)" v-on:click="flag = false">
<a>タグはよくて<nuxt-link>でダメということは、コンポーネントではv-onが使えないのかなとVue.jsのマニュアルを調べていると、「コンポーネントでv-onする時はnative修飾子つけなあかんよ」と。
<nuxt-link to="(略)" v-on:click.native="flag = false">
これで期待通りの動作となりました。
v-forを使ってもう少しメニューを便利に
さて、ここまでで基本的な機能は実装できましたが、メニューの<ul>タグ内が非常に見づらいです。
<ul>
<li><nuxt-link to="(略)" v-on:click.native="flag = false">(略)</nuxt-link></li>
<li><nuxt-link to="(略)" v-on:click.native="flag = false">(略)</nuxt-link></li>
<li><nuxt-link to="(略)" v-on:click.native="flag = false">(略)</nuxt-link></li>
</ul>
新規で項目追加する場合にもコードのミスを起こしやすいし、コードの変更する場合には全部の<li>を書き換えなくてはならないのでメンテナンス性が最悪です。
これを改善するために「v-for」というのを使ってみます。
まずはメニューの項目を定義
メニュー展開フラグの「flag」と合わせて、dataメソッドでメニュー項目を定義します。
export default {
data() {
return {
flag: false,
menus: [
{ public: true, href: '/', title: 'ホーム' },
{ public: true, href: '/blog/', title: 'ブログ' },
{ public: false, href: '/test/', title: 'テストページ' },
],
}
},
}
各メニューの情報は次の3要素にしました。
- 「public」=公開済みかどうかのフラグ
- 「href」=ページのURL
- 「title」=ページの名前
「public」はなくてもいいかと思いますが、開発時だけ下書きページへアクセスできるようにしたりと、まぁぶっちゃけ「v-if」の練習のためです。
メニューをv-forで処理
「v-for」は名前の通りループ処理です。
v-forを使って先ほど定義したメニュー配列をループ処理します。
書き方はこう↓。
<ul>
<li v-for="menu in menus">
<nuxt-link></nuxt-link>
</li>
</ul>
<ul>ではなくて、繰り返し出力される<li>の方にv-forをつけます。
後ろの「menus」がdataで宣言した配列名です。
前の「menu」はループ中で使う現在の要素の変数名です。 こちらは自由に命名することができます。
v-forループ内の書き方
これで<li>の中ではメニューオブジェクトに「menu」という変数でアクセスできるようになります。 メニューオブジェクトには「public」「href」「title」の3要素を持たせてあるので、<nuxt-link>の部分はこう↓書きます。
<nuxt-link :to="menu.href">{{menu.title}}</nuxt-link>
「to」の前にセミコロンが付いていますが、これはテキストじゃなくて変数を展開しないといけないので「v-bind」にしているからです。 変数を参照する場合は属性名にセミコロンを付ける。
「title」の方には中括弧が二重についています。 これはタグの内側?生のテキストを書くところ?で変数を書く場合にはこのように書きます。
この中括弧での変数展開ですが、HTMLタグを含めることができません。
例えば、「title」に「<span class="red">新着情報!</span>」みたいな値を入れておくと、HTMLタグがエスケープされてタグごと出力されてしまいます。 HTMLタグをHTMLタグとして出力したい場合には「v-html」というのを使います。
<nuxt-link :to="menu.href" v-html="menu.title"></nuxt-link>
これで<nuxt-link>の内側にHTMLタグが機能したままmenu.titleを展開できます。
v-ifによるループの除外
メニューオブジェクトに公開/非公開の「public」というフラグを持たせたので、フラグがfalseの時はメニューを非表示にしてみます。
これには「v-if」を使います。
<nuxt-link>にv-ifをつけると外側に空の<li>が残ってしまうので、v-ifは<li>の方につけます。
<li v-for="menu in menus" v-if="menu.public == true">
ちょっと丁寧に書きましたが、これ↓でもおkです。
<li v-for="menu in menus" v-if="menu.public">
これで「public」が真に設定されたメニューだけ表示されます。
1点注意として、Vue.jsのマニュアルを見ていると、「v-forとv-ifは同時に使ったらダメ」とあります。
どういうケースで併用したらダメなのかピンときませんでしたが、今回のような併用はおkと書いてあるので問題なし。
まとめ
初心者でも無事にハンバーガーメニューを実装することができました。
今回は、ハンバーガーメニューを実装する過程でv-onとかv-forとかを学ぶことが目的だったので、コピペでポンと使えるまとめ方はしにくいです。
自分でコードが書けるよう勉強している人達の参考になればと思います。 実装サンプルはここを。
今回登場したv-XXは次の5つです。
- v-on
- v-bind
- v-for
- v-html
- v-if
これで変数に対して処理する方法はだいたいいけているので、次は入力を受けるv-modelを勉強したいと思います。
以上、WordPressからお届けしました!
Nuxtサイトリリース編の目次
- 第1回 作ってみての感想
- 第2回 重要そうなモジュール7個
- 第3回 <script>の中身をお勉強
- 第4回 ハンバーガーメニューの実装(このページ)
- 第5回 ユーザ入力を受け取るv-model