
javascriptでナビゲーションメニューバーを作る方法(基本編)
javascriptでレスポンシブルデザイン対応のナビゲーションメニューを作る方法を紹介します。
メニューが上下に展開される動作は、CSSのtranslateY();、easing、opacityプロパティが担当しています。
javascriptは、ボタンを押した時にtranslateY();などの数値を変更したactiveクラスを付け外しするために使われています。
javascriptでナビゲーションメニューバーを作る
今回はjavascripでヘッダーの基本的なナビゲーションメニューを作る方法をご紹介します。
完成したHTML・CSSコード・JSコードを全て公開しますので、最終的にはそれぞれをコピペすればすぐに使えます。
今回は基本構成となるので、仕組みの紹介が中心です。
もちろんPCスマホ一体型レスポンシブデザインに対応しています。
ナビメニュー(完成状態)
最終的に完成したメニューバーが以下のようになります。
スマホで閲覧した時はきちんとメニューが折りたたまれて、MENUボタンが表示されます。
上記は記事内に構成をしているのでスマホ版だとメニューが上に消えていく様子までわかります。
実際はヘッダーに設置するのでその部分は見えません。
前準備
まずはヘッダーメニューを構成するHTMLページ、CSSファイル、JSファイルを準備します。
CSSの読込
CSSコードをヘッダー部分で読み込みます。CSSの名前は自由に設定して下さい。
ここではheader-style.cssにしています。
<link rel="stylesheet" href="css/header-style.css">
JSコードの読込
JSファイルは中身は空の状態でOKですので、header.jsを用意します(名前は自由)。
<script src="js/header.js"></script>
完成形を見てわかる通り、必ずhtmlコードよりも下で読み込みをする様にして下さい。
headタグ内で読み込みするとうまく稼働しません。
HTMLタグの枠構成・CSS
まずはheaderタグとその内側に一つdiv要素を作り、さらにその中に以下3つのdiv要素を作って構成します。
・ロゴエリア(start)
・メニューリストエリア(middle)
・「Sign in」特設ボタンエリア(end)
HTMLタグコード
<header class="site-header"> <div class="site-header__wrapper"> <div class="site-header__start"> </div> <div class="site-header__middle"> </div> <div class="site-header__end"> </div> </div> </header>
CSSのflexboxを使って横並び構成を実現
site-header__start
site-header__middle
site-header__end
外枠にflexboxを設定
.site-header__wrapper { display: flex; align-items: center; justify-content: space-between; padding: 1rem; }
上記の3つの要素を横並びにするために、外側を囲っているsite-header__wrapperに「display: flex;」を適用します。
それぞれの要素内の中央揃えになる様にし(align-items: center;)、枠には1remの余白を付けています。
HTMLタグ内部構成・CSS
ロゴマーク部分
site-header__start内にロゴを構成します。
ここは基本的には自由です。h1などの見出しタグで囲ったり、aタグにURLを入れる事が多いでしょう。
ロゴ部分設定例
<h1><a href="URL" class="brand">Logo</a></h1>
Logoの部分は画像でもOKです。
メニューButton部分
次にsite-header__middleの中です。内側をまずはnavタグで囲みます。
<nav class="nav"> </nav>
メニューButton部分
buttonタグはスマホ版のみに表示をさせるトグルボタン部分となります。
<button class="nav__toggle" aria-expanded="false" type="button"> menu </button>
中身を三本線で構成にすれば、ハンバーガーメニューになる訳ですね。
または「MENU」と書いた画像に変えてボタンらしくするのもよいと思います。
このボタン部分はPC版では見せないので、display: none;としています。
トグルボタン部分CSS
.nav__toggle { display: none; }
逆にスマホ版ではボタンを見せなければなりません。ですのでdisplay: block;にしています。
トグルボタン部分CSS
@media (max-width: 629px) { .nav__toggle { display: block; position: absolute; right: 1rem; top: 1rem; } }
外枠の.site-headerにposition: relative;を設定しているので、position: absolute;で自由に設置場所を指定できます。
メニューリスト部分
次はメニューリスト部分です。以下の様にulタグとliタグで構成をします。
<ul class="nav__wrapper"> <li class="nav__item"><a href="#">Home</a></li> <li class="nav__item"><a href="#">About</a></li> <li class="nav__item"><a href="#">Services</a></li> <li class="nav__item"><a href="#">Example</a></li> <li class="nav__item"><a href="#">Contact</a></li> </ul>
メニューの横並びについて
この各種メニュー部分にもflexboxが設定してあります。
このメニューリスト部分はPCでは横並びですが、スマホ版では縦並びにならなければなりません。
そこでメディアクエリでmix-width: 630pxとし、画面幅が630pxより狭くなったらflexが解除され縦並びになる様にしています。
メニューリスト部分メディアクエリ
@media (min-width: 630px) { .nav__wrapper { display: flex; } }
site-header__middle最終構成
<nav class="nav"> <button class="nav__toggle" aria-expanded="false" type="button"> menu </button> <ul class="nav__wrapper"> <li class="nav__item"><a href="#">Home</a></li> <li class="nav__item"><a href="#">About</a></li> <li class="nav__item"><a href="#">Services</a></li> <li class="nav__item"><a href="#">Hire us</a></li> <li class="nav__item"><a href="#">Contact</a></li> </ul> </nav>
特設ボタン部分
最後のsite-header__end部分に特設ボタンを設置します。
ここは会員ログインだったりカート内情報だったりを表示するボタンなので他とデザインを変更しています。
<a class="button" href="#">Sign in</a>
特設ボタン部分CSS
.button { -webkit-appearance: none; -moz-appearance: none; appearance: none; color: #fff; background-color: #2fa0f6; min-width: 120px; padding: 0.5rem 1rem; border-radius: 5px; text-align: center; }
この部分はスマホ表示時も、MENUボタンと同様に表示されたままになります。
位置を調整したい場合はメディアクエリでMENUボタンと被らない様に設定しましょう。
ボタン位置調整例
@media (max-width: 629px) { .button { margin-right: 2.5rem; } }
JSファイル作成
次にjavascriptのコードを作成します。
主な目的はスマホ版時にモバイル用のナビゲーションメニューを表示するため、トグルボタンを機能させる事です。
メニューが表示されていない時はボタンを押す事でメニューが縦に展開し、逆に表示されている時はメニューをしまう動作をおこないます。
まずはそれぞれのクラス値を変数に代入しています。
let navToggle = document.querySelector(".nav__toggle"); let navWrapper = document.querySelector(".nav__wrapper");
次にトグルボタンを押した時の動作です。中にそれぞれの動作が書き込まれます。
navToggle.addEventListener("click", function () { });
if条件構文
メニューが開いている場合
まずは開いている時のギミックです。
nav__wrapperにactiveクラスが付いているかどうかをifのところで確かめています。
activeクラスが付いている=メニューが開いた状態である事を示しています。
if (navWrapper.classList.contains("active")) { this.setAttribute("aria-expanded", "false"); this.setAttribute("aria-label", "menu"); navWrapper.classList.remove("active"); }
最後のところで、activeクラスを削除しています。
activeクラスが消える事で、メニューが上に折りたたまれる様に消えていきます。
メニューが閉じている場合
次に閉じている時のギミックです。
これはifのところでactiveクラスが含まれていない場合の動作(else)です。
activeクラスが付いていない=メニューが閉じた状態である事を示しています。
activeクラスを追記する事で、閉じていたメニューが展開される訳です。
else { navWrapper.classList.add("active"); this.setAttribute("aria-label", "close menu"); this.setAttribute("aria-expanded", "true"); }
activeクラスの付け外し
既にお分かりと思いますが、ボタンを押すたびに毎回activeクラスを付け外しています。
ついている時は外し、外れている時はつける様にしています。
外す動作(ifのところ)
navWrapper.classList.remove("active");
つける動作(elseのところ)
navWrapper.classList.add("active");
それにより同じボタンを押す事で、メニューが開いたり閉じたりする事を実現しているのです。
setAttribue()による属性追加
this.setAttribute("aria-expanded", "false");
this.setAttribute("aria-expanded", "true");
「aria-expanded」は、要素が展開されているかどうかを示しています。
falseなら展開されていない事を示し、trueなら展開されている事を示しています。
falseと判断されればtrueへ、trueと判断されればfalseへと属性を交互に付け替えています。
初期状態の動作について
一番初めの状態
スマホ版でのメニューリストはflexboxが解除されているので、全て縦に並んだ状態が初期設定です。
しかし初期状態では.nav__wrapperにはactiveクラスが付いていません。
そしてhtmlタグ上でもaria-expanded="false" となっていて、閉じているのが初期状態です。
ですのでメニューは見えない状態から始まります。
メニュー部分に適用されているCSS
・visibility: hidden;
・opacity: 0;
・transform: translateY(-100%);
ボタンを初めて押された時
一番最初にボタンをクリックされた時は、activeクラスが付いていない場合の動作が実行されます。
※JSコード上で言うと、一番最初はelse()の方から実行される事になりますね。
ここでactiveクラスが追記される事で、メニューが全て展開され、かつ見えている状態になります。
メニュー部分に適用されるCSS
・visibility: visible;
・opacity: 1;
・transform: translateY(0);
2回目にボタンを押す時
次にボタンを押す時点では既にactiveクラスがついているので、if()の方が実行されます。
activeクラスが削除される事で、上の方へ移動しつつ消えていき元の状態へ戻る訳ですね。
メニュー部分に適用されるCSS
・visibility: hidden;
・opacity: 0;
・transform: translateY(-100%);
ボタン開閉に関するCSS
以下が要素が閉じている時と展開されている時のスタイル設定です。
@media (max-width: 629px) { //activeクラスを外した時(開いている時に閉じる場合) .nav__wrapper { position: absolute; top: 100%; right: 0; left: 0; z-index: -1; background-color: #d9f0f7; visibility: hidden; opacity: 0; transform: translateY(-100%); transition: transform 0.3s ease-out, opacity 0.3s ease-out; } //activeクラスを付けた時(閉じている時に展開する場合) .nav__wrapper.active { visibility: visible; opacity: 1; transform: translateY(0); } }
メニューの開閉動作をCSSでコントロール
それぞれのtransform: translateY();で要素を上下に移動させています。(-100%と0の間を行き来しています)
「easing」の効果でこの上下動作を滑らかにしています。
-100%にしている理由
通常メニューリストは全て見えているのが初期状態なので、そこからメニューリストの高さ分だけ上に移動させるために-100%にしています。
上に移動つつopacityで徐々に透明にしていく事で、収納されていくようなギミックを実現しています。
展開時は「-100」から「0」へ移動する動きで、メニューを下方向に広げて表現しています。
同時にopacityが1になり、透明な状態から徐々に見える様にしています。
最終的な完成コード
htmlタグ
<header class="site-header"> <div class="wrapper site-header__wrapper"> <div class="site-header__start"> <a href="#" class="brand">Logo</a> </div> <div class="site-header__middle"> <nav class="nav"> <button class="nav__toggle" aria-expanded="false" type="button"> menu </button> <ul class="nav__wrapper"> <li class="nav__item"><a href="#">Home</a></li> <li class="nav__item"><a href="#">About</a></li> <li class="nav__item"><a href="#">Services</a></li> <li class="nav__item"><a href="#">Example</a></li> <li class="nav__item"><a href="#">Contact</a></li> </ul> </nav> </div> <div class="site-header__end"> <a class="button" href="#">Sign in</a> </div> </div> </header> <script src="js/header-9.js"></script>
CSSコード
.button { -webkit-appearance: none; -moz-appearance: none; appearance: none; color: #fff; background-color: #2fa0f6; min-width: 120px; padding: 0.5rem 1rem; border-radius: 5px; text-align: center; } @media (max-width: 629px) { .button { margin-right: 2.5rem; } } .brand { font-weight: bold; font-size: 20px; } .site-header { position: relative; background-color: #def7ff; } .site-header__wrapper { display: flex; align-items: center; justify-content: space-between; padding: 1rem; } @media (min-width: 630px) { .site-header__wrapper { justify-content: initial; } } @media (min-width: 630px) { .site-header__middle { margin-left: auto; } } @media (max-width: 629px) { .site-header__end { padding-right: 4rem; } } @media (min-width: 630px) { .nav__wrapper { display: flex; } } @media (max-width: 629px) { .nav__wrapper { position: absolute; top: 100%; right: 0; left: 0; z-index: -1; background-color: #d9f0f7; visibility: hidden; opacity: 0; transform: translateY(-100%); transition: transform 0.3s ease-out, opacity 0.3s ease-out; } .nav__wrapper.active { visibility: visible; opacity: 1; transform: translateY(0); } } li.nav__item { list-style-type: none; } .nav__item a { display: block; padding: 1rem; } .nav__toggle { display: none; } @media (max-width: 629px) { .nav__toggle { display: block; position: absolute; right: 1rem; top: 1rem; } }
JSコード
let navToggle = document.querySelector(".nav__toggle"); let navWrapper = document.querySelector(".nav__wrapper"); navToggle.addEventListener("click", function () { if (navWrapper.classList.contains("active")) { this.setAttribute("aria-expanded", "false"); this.setAttribute("aria-label", "menu"); navWrapper.classList.remove("active"); } else { navWrapper.classList.add("active"); this.setAttribute("aria-label", "close menu"); this.setAttribute("aria-expanded", "true"); } });
まとめ
これで基本的なナビゲーションメニューが作れたと思います。
ポイントは、メニューが開閉する動き自体はCSSプロパティが担当しているという事です。
動作を担当するCSSプロパティ
・visibility: visible;
・opacity: 1;
・transform: translateY(0);
↓↑
【activeクラス】
・visibility: hidden;
・opacity: 0;
・transform: translateY(-100%);
javascriptはhtmlタグ上でactiveクラスを付け外しする役目を担当しており、メニューの動きにはタッチしていません。
今回は基本中の基本です。
実際にはもっと複雑なナビが必要になると思いますが、基本的な仕組みは変わりません。
何より他の人が作ったナビをいじる時は、それぞれの仕組みが理解できていないとカスタマイズができません。
今回はほんの基礎部分に過ぎませんが、押さえておくべき基本になると思います。