こんにちは、SIMです。今日はjQueryで作る最も一般的なハンバーガーメニューの作り方を紹介します。
なお、この記事はコードの構造を分かりやすくするために、メニューボタンとメニュー本体部分に分けて説明しています。実際のjQureryは一つにまとまっているので、そういうイメージで読み進めて下さい。
今回、ご紹介するハンバーガーメニューの機能をまとめたのが、下のリストです。
- ハンバーガー型ボタンを押すとドロワーメニューが上からスライドインで現れる
- ハンバーガー型ボタンはクリックするとX印になる
- ドロワーメニューが展開中は背景が暗くなる
- ドロワーメニューが展開中は背景のスクロールが禁止される
- ハンバーガー型ボタン(X印)をクリックするとドロワーメニューがスライドアップして上に消える
- 暗い背景をクリックしても、ドロワーメニューはスライドアップで消える
- キーボードのESCキーを押してもドロワーメニューはスライドアップで消える
まずは完成品のハンバーガーメニューの動きとコードをご確認下さい。
(スマホなどの小さい画面の場合はResultボタンをタップすると実際の表示が見れますよ)。
See the Pen ハンバーガーメニュー by s sim (@s-sim) on CodePen.
メニューボタンのアニメーション
はじめにハンバーガーメニューのボタン(三本線の部分)の作り方を紹介します。
HTMLの構造
HTMLは、ハンバーガーのマークだけなら3行、「MENU」の文字を入れても4行という短いコードで作れちゃいます。
<!-- ハンバーガーメニューのボタン -->
<div class="l-sp-header_ham-menu">
<div class="menu-txt">MENU</div>
<div class="openbtn1"><span></span><span></span><span></span></div>
</div>
このうちjQueryと連動しているのは、spanタグが連続で並んでいる「class=”openbtn1″」のdivダグだけです。
CSSの構造 | 三本線がX印になるアニメーション
三本線とそのアニメーションに関係するCSSは長いです。
ただし、この記事で長いコードはココだけで、後は全部短いです。
/* ---------------------------- */
/* ハンバーガーメニューのボタン */
/* ---------------------------- */
.l-sp-header_ham-menu {
width: 60px;
height: 60px;
border-radius: 3px;
background-color: #023047;
color: #fff;
font-size: 13px;
font-weight: 400;
text-align: center;
padding-top: 6px;
}
/* ---------------------------- */
.open-btn1 {
position: relative; /*ボタン内側の基点となるためrelativeを指定*/
cursor: pointer;
width: 60px;
height: 40px;
margin-top: 2px;
}
/*ボタン内側:三本線のスタイル*/
.open-btn1 span {
display: inline-block;
transition: all 0.4s; /*アニメーションの設定*/
position: absolute;
left: 12px;
height: 2px; /*Android端末で線の太さの違い出る対策*/
transform: scaleY(0.5) translateY(1px); /*Android端末で線の太さの違い出る対策*/
border-radius: 2px;
background: #fff;
width: 62%;
}
/* 上の線 */
.open-btn1 span:nth-of-type(1) {
top: 4px;
}
/* 真ん中の線 */
.open-btn1 span:nth-of-type(2) {
top: 14px;
}
/* 下の線 */
.open-btn1 span:nth-of-type(3) {
top: 24px;
}
/* ---------------------------- */
/*activeクラスが付与されると線が回転して×に*/
.open-btn1.active span:nth-of-type(1) {
top: 8px;
left: 16px;
transform: translateY(6px) rotate(-45deg);
width: 50%;
height: 1px; /*Android端末で線の太さの違い出る対策*/
}
.open-btn1.active span:nth-of-type(2) {
opacity: 0; /*真ん中の線は透過*/
}
.open-btn1.active span:nth-of-type(3) {
top: 20px;
left: 16px;
transform: translateY(-6px) rotate(45deg);
width: 50%;
height: 1px; /*Android端末で線の太さの違い出る対策*/
}
- ボタンのカスタマイズ:spanタグの数字を変えることで、サイズや線の感覚をカスタマイズできます。
- ボタンアニメーションについて:spanタグにactiveクラスが付与されると、上の線が-45度回転、真ん中の線が消えて、下の線が45度回転する仕組みになっています。
- activeクラスの付与と剥奪:ハンバーガーメニューのボタンクリックでアクティブクラスが付いたり、取れたりする仕組みになっています(制御はjQuery)。
上のCSSに関する補足説明|2024.7.17追記
上のCSSでは、下記の表示バグに関する対策も施しています。
その表示バグというのは、Android端末などで、ハンバーガーメニューの3本線の太さがそれぞれ違って見えるというものです。
例えば、私の経験したケースでは、EdgeとFirefoxでは真ん中の線がほかより太く、Chromeでは下の線が細く見えるとうバグです。
このバグの対策として、上のコードに下記の記述が加えられています。
なお、ここでは線の太さは1pxとしています。
29行目と30行目
height: 2px; /*Android端末で線の太さの違い出る対策*/
transform: scaleY(0.5) translateY(1px); /*Android端末で線の太さの違い出る対策*/
heightを2倍の2pxに設定し、transformで50%の1pxに戻すという方法です。
54行目と64行目
height: 1px; /*Android端末で線の太さの違い出る対策*/
これはバツ印となる2本線ですが上で太さ2pxとしたため、このままではバツ印のみ線が太くなってしまいます。
そこで、新たにバツ印のCSSにも高さ1pxを記述することで、線の太さが変わらないようにしました。
詳しくはこちらを参照願います。
この表示バグの原因はディスプレイの表示倍率(Device Pixel Ratio)でして、詳しくは下記のteratailのQ&Aを参照願います。
teratail「CSS ハンバーガーメニューの3本線の内1つの線だけ太さが異なって表示されてしまう。」
jQuery | 三本線がX印になるアニメーション
基本的な仕組みは、三本線のメニューボタンがクリックされたらactiveクラスの脱着が起きるようになっています。
三本線のボタン(open-btn1)がクリックされると:$(‘.open-btn1’).click(function() {…
- open-btn1クラスにactiveクラスの脱着が起こり、三本線がX印になったり、X印から三本線に戻ったりする
$(this).toggleClass(‘active’);
ドロワーメニュー以外の背景部分をクリックした時は:$(‘.sp-nav-close’).click(function() {…
- ボタン(X印)からactiveクラスを剥奪し、三本線に戻る
$(‘.open-btn1’).removeClass(‘active’);
キーボードのESCキーを押した時は:$(window).keyup(function(e){if(e.keyCode == 27){…
- ボタン(X印)からactiveクラスを剥奪し、三本線に戻る
$(‘.open-btn1’).removeClass(‘active’);
jQueryのすべてのコードは下の方に載せておきます。
メニュー本体がスライドインするアニメーション
続いて、ハンバーガーメニューのボタンをクリックしたらドロワーメニューが上からスライドインしてくるコードを紹介します。
HTMLの構造
HTML構造はシンプルで、上で解説した三本線ボタンを含むヘッダーバーの下に、①暗い背景と、②ドロワーメニュー本体を同じ階層に書くだけです。
下記の要素はすべて同じ階層に置きます。
- ヘッダーバー:この中に三本線(ハンバーガー型)ボタンを置く(class=”open-btn1″)
- ドロワーメニュー用の暗い背景:class=”p-sp-nav-bg sp-nav-close”
- ドロワーメニュー本体:class=”p-sp-nav” id=”sp-g-nav”
上の各要素の中身のHTML構造は自由に作ってもjQueryの動作に影響はありません。
<body>
<header class="l-header">
<!-- スマートフォン専用ヘッダー -->
<div class="l-sp-header">
<!-- ページタイトル -->
<div class="l-sp-header_headline">
<h1 class="l-sp-header_h1">SIMS-CODE</h1>
</div>
<!-- ハンバーガーメニューのボタン -->
<div class="l-sp-header_ham-menu">
<div class="menu-txt">MENU</div>
<div class="open-btn1"><span></span><span></span><span></span></div>
</div>
</div>
<!-- ナビの背景 -->
<div class="p-sp-nav-bg sp-nav-close"></div>
<!-- スマホ用グローバルナビ -->
<nav class="p-sp-nav" id="sp-g-nav">
<ul class="p-sp-nav_ul">
<li class="p-sp-nav_li">
<a href="#" class="p-sp-nav_a">HOME</a>
</li>
<li class="p-sp-nav_li">
<a href="#" class="p-sp-nav_a">唐揚げ定食 500円</a>
</li>
<li class="p-sp-nav_li">
<a href="#" class="p-sp-nav_a">焼き肉定食 2,000円</a>
</li>
<li class="p-sp-nav_li">
<a href="#" class="p-sp-nav_a">カツカレー 1,200円</a>
</li>
<li class="p-sp-nav_li">
<a href="#" class="p-sp-nav_a">ロースカツ定食 1,200円</a>
</li>
<li class="p-sp-nav_li">
<a href="#" class="p-sp-nav_a">ハンバーグ定食 1,000円</a>
</li>
<li class="p-sp-nav_li">
<a href="#" class="p-sp-nav_a">メンチカツ定食 1,000円</a>
</li>
</ul>
</nav>
</header>
<main></main>
<footer></footer>
<!-- jqueryのスクリプト -->
<script src="https://code.jquery.com/jquery-3.6.1.min.js"
integrity="sha256-o88AwQnZB+VDvE9tvIXrMQaPlFFSUTR+nldQm1LuPXQ=" crossorigin="anonymous"></script>
</body>
- body:ドロワーメニュー展開時にbodyにfixedクラスを付与して背景スクロールを禁止する
- div class=”openbtn1″:activeクラスが脱着されることで三本線ボタン←→X印に変化
- div class=”p-sp-nav-bg sp-nav-close”:ドロワーメニューの背景部分
- nav class=”p-sp-nav” id=”sp-g-nav”:ドロワーメニュー本体
CSSの構造 | メニュー本体上からスライドインするアニメーション
メニュー本体のCSSは次のとおりで、z-indexの説明は上のHTMLでしたとおりです。
body.fixed {
position: fixed;
width: 100%;
height: 100%;
}
/* ---------------------------- */
/* スマートフォン用ヘッダー */
/* ---------------------------- */
.l-sp-header {
width: 100%;
z-index: 10;
height: 80px;
position: fixed;
top: 0;
left: 0;
display: flex;
justify-content: space-between;
color: #023047;
padding: 9px 20px;
background-color: #8ecae6;
}
/* ---------------------------- */
/* メニュー本体 */
/* ---------------------------- */
/* 暗い背景 */
.p-sp-nav-bg{
display: none;
position: fixed;
width: 100%;
height: 100%;
background-color: rgba(2,48,71, 0.6);
top: 0;
left: 0;
/* z-index: 5; */
transition: all 0.3s;
}
/* .bg-activeが付与された時 */
.p-sp-nav-bg.bg-active{
display: block;
transition: all 0.3s;
}
/* ナビゲーション全体を囲うタグ */
#sp-g-nav {
position: fixed;
z-index: 7;
color: #fff;
background-color: #023047;
font-size: 16px;
font-weight: 300;
line-height: 1.3;
width: 100%;
top: -120%;
transition: all 0.3s;
}
/*アクティブクラスがついたら表示位置変更*/
#sp-g-nav.panel-active {
width: 100%;
top: 80px;
transition: all 0.3s;
}
/* ナビゲーション本体 */
.p-sp-nav_ul{
width: 100%;
}
.p-sp-nav_li {
height: 38px;
border-bottom: 1px solid #fff;
}
.p-sp-nav_li:last-of-type {
border-bottom: 0;
}
.p-sp-nav_a{
width: 100%;
height: 100%;
display: flex;
align-items: center;
padding-left: 2em;
}
/* ---------------------------- */
/* メインエリアのスタイル */
/* ---------------------------- */
main{
padding-top: 80px;
text-align: center;
color: #219ebc;
font-size: 20px;
font-weight: 600;
line-height: 2;
}
.main-p{
margin-top: 50px;
}
ドロワーメニューCSSの基本的な仕組みは、通常は非表示しておいてjQueryで特定のクラスが付与されたら表示位置に移動するようになっています。
その辺りを要約すると次のようになります。
ドロワーメニュー本体
- 通常:「top: -120%」で端末画面のはるか上に配置して画面上では見えないようにしておく。
- 特定のクラスが付与された時:panel-activeクラスがメニュー本体(id=”sp-g-nav”)に付与されると、ちょうど見える位置(この場合はtop: 80px)までメニューが下がってくる仕組みになっている。
ドロワーメニューの背景
- 通常:diplay:noneで非表示
- 特定のクラスが付与された時:bg-activeクラスが付与されるとdisplay: blockとなって表示される
body
- 通常:スクロールできる
- 特定のクラスが付与された時:fixedクラスが付与されるとposition: fixed;になりスクロールが禁止される
jQuery | メニュー本体が上からスライドインするアニメーション
本来、ドロワーメニューのjQueryはシンプルですが、今回は様々なオプション機能を盛り込んだため少し複雑になっています。
jQueryのコードの全容は以下のとおりです。
各コードの意味はコメントアウトとしてコード横に書きました。
$(function() {
var scrollPos;//topからのスクロール位置
$('.open-btn1').click(function() { //ボタンをクリックしたら
$(this).toggleClass('active');//ボタン自身に activeクラスを付与し
$('.p-sp-nav-bg').toggleClass('bg-active');//背景にbg-activeクラスを付与
$('#sp-g-nav').toggleClass('panel-active');//ナビにpanel-activeクラスを付与
if($(this).hasClass('active')){//もしボタンにactiveクラスが付いていれば
scrollPos = $(window).scrollTop();//topからのスクロール位置を取得
$('body').addClass('fixed').css({ top: -scrollPos });//背景固定
}else{//もしボタンにactiveクラスが無ければ
$('body').removeClass('fixed').css({ top: 0 });//背景固定を解除
$(window).scrollTop(scrollPos);//元の位置までスクロール
}
});
$('.sp-nav-close').click(function() { //背景をクリックしたら
$('.open-btn1').removeClass('active');//ボタンからactiveクラスを剥奪
$('#sp-g-nav').removeClass('panel-active');//ナビからpanel-activeクラスを剥奪
$('.p-sp-nav-bg').removeClass('bg-active');//背景からbg-activeクラスを剥奪
$('body').removeClass('fixed').css({ top: 0 });//背景固定を解除
$(window).scrollTop(scrollPos);//元の位置までスクロール
});
$(window).keyup(function(e){//キーをクリックして
if(e.keyCode == 27){//ESCキーだったら
$('.open-btn1').removeClass('active');//ボタンからactiveクラスを剥奪
$('#sp-g-nav').removeClass('panel-active');//ナビからpanel-activeクラスを剥奪
$('.p-sp-nav-bg').removeClass('bg-active');//背景からbg-activeクラスを剥奪
$('body').removeClass('fixed').css({ top: 0 });//背景固定を解除
$(window).scrollTop(scrollPos);//元の位置までスクロール
}
});
});
一応、jQueryについても簡単にコード解説をしておきます。
上のコードは大きく3つのパートに分かれていますので、一つずつ説明します。
- ハンバーガー型(三本線)ボタンをクリックした時の動き
- ドロワーメニュー展開時に背景部分をクリックした時の動き
- キーボードのESCキーを押した時の動き
そして、大前提としてドロワーメニュー展開時に背景スクロールを禁止するために、Topからのスクロール量を取得するための変数について変数宣言をjQueryの最初にします。
$(function() {
var scrollPos;//変数宣言:topからのスクロール位置
ハンバーガー型(三本線)ボタンをクリックした時のjQuery
ハンバーガー型(三本線)ボタンをクリックしたら:$(‘.open-btn1’).click(function() {
- ボタン自身に activeクラスを脱着し、形状変化(三本線 <=> X印)
$(this).toggleClass(‘active’); - ドロワーメニューの背景にbg-activeクラスを脱着し、表示変化(非表示 <=> 表示)
$(‘.p-sp-nav-bg’).toggleClass(‘bg-active’); - ドロワーメニューにpanel-activeクラスを脱着し、表示変化(非表示 <=> 表示)
$(‘#sp-g-nav’).toggleClass(‘panel-active’);
このとき、もしボタンにactiveクラスが付いていれば
if($(this).hasClass(‘active’)){
- topからのスクロール位置を取得:
scrollPos = $(window).scrollTop(); 予めこの上の階層で変数宣言(var scrollPos;)の必要あり - bodyにfixedクラスを付与:背景のスクロールを禁止し、現在のスクロール位置で固定
$(‘body’).addClass(‘fixed’).css({ top: -scrollPos });
ボタンにactiveクラスが無ければ
}else{
- bodyからfixedクラスを剥奪:スクロールできるようにし、一旦スクロール量をtopから0に変更
$(‘body’).removeClass(‘fixed’).css({ top: 0 }); - 背景を元のスクロール位置に合わせる:元のスクロール量=scrollPos
$(window).scrollTop(scrollPos);
ここで難しく感じる箇所は背景のスクロール禁止命令だと思います。
背景スクロールの禁止jQueryはモーダルウィンドウのページでも解説しましたが、ハンバーガーメニューの場合にはさらに複雑になってif文にしないと動作しません。
ハンバーガー型ボタンがactiveクラスを持っていれば背景スクロールを禁止して、持っていなければ背景スクロールを解禁するようなif文で作ってあります。
ドロワーメニュー展開時に背景部分をクリックした時のjQueryの動き
ドロワーメニューの暗い背景部分をクリックした時にもドロワーメニューが閉じる命令をjQueryで書いています。
ドロワーメニューの背景をクリックしたらボタンをクリックしたら:$(‘.sp-nav-close’).click(function() {
- ボタンからactiveクラスを剥奪し、形状変化(X印 <=> 三本線)
$(‘.open-btn1’).removeClass(‘active’); - ドロワーメニューからpanel-activeクラスを剥奪し、スライドアップして非表示にする
$(‘#sp-g-nav’).removeClass(‘panel-active’); - ドロワーメニューの背景からbg-activeクラスを剥奪し、非表示にする
$(‘.p-sp-nav-bg’).removeClass(‘bg-active’); - bodyからfixedクラスを剥奪:スクロールできるようにし、一旦スクロール量をtopから0に変更
$(‘body’).removeClass(‘fixed’).css({ top: 0 }); - 背景を元のスクロール位置に合わせる:元のスクロール量=scrollPos
$(window).scrollTop(scrollPos);
キーボードのESCキーを押した時のjQuery
キーボードのESCキーを押したときにもドロワーメニューが閉じる命令をjQueryで書いています。
ESCキーをクリックしたら
$(window).keyup(function(e){
if(e.key === “Escape”){
- ボタンからactiveクラスを剥奪し、形状変化(X印 <=> 三本線)
$(‘.open-btn1’).removeClass(‘active’); - ドロワーメニューからpanel-activeクラスを剥奪し、スライドアップして非表示にする
$(‘#sp-g-nav’).removeClass(‘panel-active’); - ドロワーメニューの背景からbg-activeクラスを剥奪し、非表示にする
$(‘.p-sp-nav-bg’).removeClass(‘bg-active’); - bodyからfixedクラスを剥奪:スクロールできるようにし、一旦スクロール量をtopから0に変更
$(‘body’).removeClass(‘fixed’).css({ top: 0 }); - 背景を元のスクロール位置に合わせる:元のスクロール量=scrollPos
$(window).scrollTop(scrollPos);
最後にCodePenをもう一度貼っておくので、動きやコードを確認してみて下さいね。
See the Pen ハンバーガーメニュー by s sim (@s-sim) on CodePen.
まとめ
jQueryで作るハンバーガーメニューを紹介しました。
基本、コピペで真似できると思うので少しでもこの記事を読んでくれた皆様のお役に立てれば幸いです。
ハンバーガーメニュー(ドロワーメニュー)に幾つかのパターンがあって、今回紹介したのは上からスライドインする最もオーソドックスなタイプです。この他によく見かける横からスライドインしていくるタイプもドロワーメニューの初期の座標を変更するだけで実装が可能です。