ハンバーガーメニューを展開してからのメニューをさらに展開させるメニューをjQueryで作ってみた。2階層パターン対応(備忘録)
今回は、タイトル通り、ハンバーガメニューを展開してからのメニューから、さらに詳細のメニューが展開されるイメージで作って見た。
よく企業のホームページなどで目にすることが多いと思う。これまでのランディングページと違って、いろんな階層にページが存在するため、ヘッダーはインクルード化して、共通で読み込めるように対応した。
本記事ではトップページだけしかデモとして用意していないので、中身のページを作りたい場合は、トップページを上手く活用して作り込んでくといいかと思う。
PC幅ではメニューをマウスホバーした時に、サブメニューが展開され、SP幅では、タップした時にサブメニューが展開されるように対応している。
では、実装に入っていきたいと思う。
htmlの記述について
index.htmlの記述
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="robots" content="index, follow" />
<title>2階層ハンバーガーメニュー</title>
<link rel="stylesheet" href="../reset.css">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header></header>
<main>
<section>
<div class="ly_mainvisual">
<img src="img_mainvisual.jpg" alt="MV">
</div>
</section>
<section>
<div class="ly_inner" id="menu1">
<h2>menu1</h2>
<div class="md_textblock">
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
<p>テキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入りますテキストが入ります</p>
</div>
</div>
</section>
</main>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="style.js"></script>
</body>
</html>
今回変更したのは、の部分だけだ。後述するが、後ほどインクルードして読み込ませるため、これだけの記述にしている。
その他の部分は、これまでの記事と同じ記述であるので省略する。
header.htmlの記述
<header>
<div class="bl_hamburgerMenu">
<!-- ハンバーガーメニュー線 -->
<p class="bl_hamburgerLine"><span></span></p>
<!-- ハンバーガーメニュー中身 -->
<nav>
<div class="bl_menuBox">
<ul>
<li>
<p>MENU01</p>
<div class="bl_menuSub">
<p class="bl_mainMenu">メインメニュー</p>
<p class="bl_title">MENU01</p>
<ul>
<li><a href="">MENU01_01</a></li>
<li><a href="">MENU01_02</a></li>
</ul>
</div>
</li>
<li>
<p>MENU02</p>
<div class="bl_menuSub">
<p class="bl_mainMenu">メインメニュー</p>
<p class="bl_title">MENU02</p>
<ul>
<li><a href="">MENU02_01</a></li>
<li><a href="">MENU02_02</a></li>
<li><a href="">MENU02_03</a></li>
<li><a href="">MENU02_04</a></li>
<li><a href="">MENU02_05</a></li>
</ul>
</div>
</li>
<li>
<p>MENU03</p>
<div class="bl_menuSub">
<p class="bl_mainMenu">メインメニュー</p>
<p class="bl_title">MENU03</p>
<ul>
<li><a href="">MENU03_01</a></li>
<li><a href="">MENU03_02</a></li>
<li><a href="">MENU03_03</a></li>
</ul>
</div>
</li>
<li>
<p>MENU04</p>
<div class="bl_menuSub">
<p class="bl_mainMenu">メインメニュー</p>
<p class="bl_title">MENU04</p>
<ul>
<li><a href="">MENU04_01</a></li>
<li><a href="">MENU04_02</a></li>
<li><a href="">MENU04_03</a></li>
<li><a href="">MENU04_04</a></li>
</ul>
</div>
</li>
<li>
<p>MENU05</p>
<div class="bl_menuSub">
<p class="bl_mainMenu">メインメニュー</p>
<p class="bl_title">MENU05</p>
<ul>
<li><a href="">MENU05_01</a></li>
<li><a href="">MENU05_02</a></li>
<li><a href="">MENU05_03</a></li>
</ul>
</div>
</li>
</ul>
</div>
</nav>
</div>
</header>
ハンバーガーメニューの線の部分は、span1つとspanに対して擬似要素beforeとafterを用いて対応している。
バンバーガーメニューの中身の部分は、メインメニューとサブメニューがそれぞれ展開されている。
<li>
<p>MENU01</p>
<div class="bl_menuSub">
<p class="bl_mainMenu">メインメニュー</p>
<p class="bl_title">MENU01</p>
<ul>
<li><a href="">MENU01_01</a></li>
<li><a href="">MENU01_02</a></li>
</ul>
</div>
</li>
ソースに「MENU01」が2つあるのは、PC幅とスマホ幅で出しわけして使用するためである。今回の対応以外のやり方もあるので、色々探してみると面白いと思う。
サブメニューにはリンクを設定していないが、実際に使用するときは該当リンクを当てはめていくイメージだ。
cssの記述について
@charset "utf-8";
/* ==========================
初期設定
========================== */
*,
*::before,
*::after {
box-sizing: border-box;
}
img {
width: 100%;
}
/* ==========================
コンテンツの中身
========================== */
.ly_inner {
width: 100%;
max-width: 1080px;
margin: 100px auto;
padding: 20px;
background-color: #ccc;
}
.ly_inner h2 {
font-size: 150%;
font-weight: bold;
margin-bottom: 30px;
}
.md_textblock > * + * {
margin-top: 10px;
}
/* ナビゲーション */
header {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 60px;
background-color: #222;
}
/* ==========================
ハンバーガーメニュー
========================== */
/* 線 */
.bl_hamburgerLine {
position: absolute;
right: 0;
top: 0;
width: 60px;
height: 60px;
overflow: hidden;
margin: 0 10px;
cursor: pointer;
}
.bl_hamburgerLine span {
display: block;
position: absolute;
right: 0;
top: 30px;
background-color: #fff;
height: 2px;
width: 100%;
transition: 0.3s;
}
.bl_hamburgerLine span::before,
.bl_hamburgerLine span::after {
content: "";
display: block;
width: 100%;
height: 2px;
background: #fff;
position: absolute;
transition: 0.3s;
}
.bl_hamburgerLine span::before {
top: -20px;
}
.bl_hamburgerLine span::after {
top: 20px;
}
.bl_hamburgerLine + nav {
display: none;
}
/* クリックした時 */
.bl_hamburgerLine.active span {
transform: rotate(45deg);
}
.bl_hamburgerLine.active span::before {
transform: rotate(-90deg);
top: 0;
}
.bl_hamburgerLine.active span::after {
content: none;
}
/* ハンバーガー展開時の中身 */
.bl_hamburgerLine + nav {
position: absolute;
width: 100%;
top: 100%;
left: 0;
}
.bl_menuBox {
background: #222;
position: relative;
}
.bl_menuBox > ul {
padding: 50px 40px 0;
height: 100%;
overflow-y: auto;
}
.bl_menuBox > ul > li {
padding: 10px 0;
overflow-wrap: break-word;
}
.bl_menuBox > ul > li + li {
margin-top: 20px;
}
.bl_menuBox > ul > li > p {
font-weight: bold;
font-size: 15px;
padding-right: 50px;
position: relative;
cursor: pointer;
transition: 0.3s;
color: #fff;
}
.bl_menuBox > ul > li > p::after {
content: "";
position: absolute;
top: 0;
bottom: 0;
right: 0;
margin: auto;
width: 12px;
height: 12px;
border-top: 3px solid #fff;
border-right: 3px solid #fff;
transform: rotate(45deg);
transition: 0.3s;
}
.bl_menuBox ul > li > p:hover::after {
border-top: 3px solid #ddc50d;
border-right: 3px solid #ddc50d;
}
.bl_menuBox ul > li > a {
transition: all 0.3s;
color: #fff;
font-weight: 500;
font-size: 90%;
}
.bl_menuBox ul > li > p:hover,
.bl_menuBox ul > li > a:hover {
color: #ddc50d;
}
.bl_menuSub {
overflow-wrap: break-word;
display: none;
}
.bl_menuSub ul > li {
text-align: left;
}
.bl_hamburgerMenu_list li + li {
margin-top: 20px;
}
/* ====================================
ここからPC幅
==================================== */
@media screen and (min-width: 768px) {
.hp_displaySP {
display: none !important;
}
/* ==========================
ハンバーガーメニュー
========================== */
.bl_menuBox {
height: calc(100vh - 60px);
width: 50%;
}
.bl_menuBox::before {
content: "";
position: absolute;
top: 0;
left: 100%;
height: 100%;
width: 100%;
background: url(img_hamburger01.jpg) no-repeat center;
background-size: cover;
}
.bl_menuBox > ul > li {
text-align: right;
width: 60%;
}
.bl_menuBox ul > li > a {
padding-left: 10px;
}
.bl_menuSub {
position: absolute;
left: 60%;
top: 30px;
width: 40%;
height: 100%;
padding: 30px 10px;
}
.bl_menuSub ul > li + li {
margin-top: 10px;
}
.bl_mainMenu,
.bl_title {
display: none;
}
}
/* ====================================
ここからスマホ幅
==================================== */
@media screen and (max-width: 767px) {
.hp_displayPC {
display: none !important;
}
/* ==========================
ハンバーガーメニュー
========================== */
.bl_menuBox {
height: calc(100vh - 60px);
}
.bl_menuBox::before {
content: "";
position: absolute;
top: 0;
left: 0;
height: 14vh;
width: 100%;
background: url(img_hamburger01_sp.jpg) no-repeat center;
background-size: cover;
}
.bl_menuBox > ul {
padding: 40px;
padding-top: calc(14vh + 40px);
}
.bl_menuBox > ul > li {
text-align: right;
}
.bl_menuSub {
position: fixed;
width: 100%;
left: 0;
right: 0;
top: 60px;
height: calc(100% - 60px);
background: #222;
z-index: 1;
font-size: 16px;
font-weight: bold;
text-align: left;
}
.bl_mainMenu {
background-color: #ccc;
padding: 20px;
position: relative;
cursor: pointer;
}
.bl_mainMenu::before {
content: "";
display: inline-block;
width: 12px;
height: 12px;
border-bottom: 3px solid #222;
border-left: 3px solid #222;
transform: rotate(45deg);
transition: all 0.3s;
margin: 0 20px 0 5px;
}
.bl_title {
color: #fff;
padding: 30px 20px;
}
.bl_title + ul {
padding: 0 40px;
list-style: disc;
color: #fff;
}
.bl_title + ul > li {
padding: 10px 0;
list-style: disc;
color: #fff;
}
}
まあまあ長めになっているが、前回の記事と重なる部分も多いので、ハンバーガーメニューの3本線の作り方の解説は省略する。
ヘッダーの高さは60pxにして、黒色の帯が上部に固定した。
ハンバーガーメニューの中身について
中身の部分の位置を調整する。
/* 共通ソース */
.bl_hamburgerLine + nav {
position: absolute;
width: 100%;
top: 100%;
left: 0;
}
ここでは中身の位置の調整をしているのだが、特に重要なのが、 top:100%;の部分だ。ここの値は60pxにしてもいいのだが、これだとヘッダーの高さが変わった時に、ヘッダーの数値とこのtopの数値も変更しなくてはならず手間になってしまう。
100%にすることで、ヘッダーの数値が変わっても影響を受けなくて済むので、他のところにも応用できると思う。
展開された時は、PC幅は左側にメニュー・右側に画像、スマホ幅は上部に画像、下部にメニューとなるよう調整した
PC幅の対応
/* PC幅 */
.bl_menuBox {
height: calc(100vh - 60px);
width: 50%;
}
.bl_menuBox::before {
content: "";
position: absolute;
top: 0;
left: 100%;
height: 100%;
width: 100%;
background: url(img_hamburger01.jpg) no-repeat center;
background-size: cover;
}
ヘッダーの高さがあるため、高さは60px分除いた形で対応している。また、幅は50%の領域に表示させている。
画像部分は擬似要素で対応し、先述したtop:100%;の応用で、left:100%;を割り当てている。backgroundはショートハンドプロパティで指定している。詳しくはMDNなどで使い方を見てみるといいと思う。いろんな使い方が紹介されていると思う。
/* 共通ソース */
.bl_menuSub {
overflow-wrap: break-word;
display: none;
}
/* PC幅 */
.bl_menuBox > ul > li {
text-align: right;
width: 60%;
}
.bl_menuSub {
position: absolute;
left: 60%;
top: 30px;
width: 40%;
height: 100%;
padding: 30px 10px;
}
.bl_mainMenu,
.bl_title {
display: none;
}
ここの記述については、メインメニューはハンバーガーメニューを展開したあとは最初から表示された状態にしたい。
そして、メインメニューにマウスを当てると、右側にサブメニューがふわっと表示されるようにしたいという意図があったため、上述のようなcssで対応した。
overflow-wrap: break-word;については、タイトルが長くなった場合でも折り返しされるように設定している。
また、bl_mainMenu、bl_titleはPC幅では使わないため、非表示にしている。
スマホ幅の対応
.bl_menuBox {
height: calc(100vh - 60px);
}
.bl_menuBox::before {
content: "";
position: absolute;
top: 0;
left: 0;
height: 14vh;
width: 100%;
background: url(img_hamburger01_sp.jpg) no-repeat center;
background-size: cover;
}
.bl_menuBox > ul {
padding: 40px;
padding-top: calc(14vh + 40px);
}
スマホ幅では、ハンバーガーメニューを展開した時に、上部に画像、その下部にメニューを並べるようにしている。
このため、中身全体の高さをヘッダーの高さ分差し引いた高さに設定し、擬似要素で画像を表示させている。今回は画像の領域の高さは14vhとした。vhやpx、%、vwなどさまざまな値があるが、これも使い方は調べて見て欲しい。
そして、画像下の領域にメニューを表示させる必要があるので、paddingで「画像の高さ分+空けたい任意の余白」分の高さを指定して、メニューが表示される領域を確保している。
.bl_menuSub {
position: fixed;
width: 100%;
left: 0;
right: 0;
top: 60px;
height: calc(100% - 60px);
background: #222;
z-index: 1;
font-size: 16px;
font-weight: bold;
text-align: left;
}
ここでは、メインメニューを展開した時に、下記の理由から設定している。
- PCで表示していたメニューのテキストを見えないようにする
- スマホ幅で展開前に表示されていたものを見えないようにする
そのほかは特に難しいことはしていないため、省略する。
javascriptの記述について
'use strict';
// PC、スマホ幅判別
const mq = function mq(device) {
if (window.matchMedia('(max-width:767px)').matches && device === 'sp') {
return true;
}
if (window.matchMedia('(min-width:768px)').matches && device === 'pc') {
return true;
}
return false;
};
// ハンバーガーメニュー
$('header').load('inc/header.html', function () {
$('.bl_hamburgerLine').on('click', function () {
$(this).toggleClass('active');
$(this).next().fadeToggle();
});
$('.bl_menuSub ul li a').on('click', function () {
$('.bl_hamburgerLine').toggleClass('active');
$('nav').fadeToggle();
});
if (mq('sp')) {
$('.bl_menuBox > ul > li').on('click', function () {
$(this).children('p').next().fadeToggle();
});
$('.bl_mainMenu').on('click', function () {
$(this).prev().fadeOut();
});
} else {
$('.bl_menuBox > ul > li').on({
mouseenter: function mouseenter() {
$(this).children('p').next().fadeIn();
},
mouseleave: function mouseleave() {
$(this).children('p').next().fadeOut();
}
});
}
});
// スムーススクロール
window.addEventListener('DOMContentLoaded', function () {
const anchorLinks = document.querySelectorAll('a[href^="#"]');
const anchorLinksArr = Array.prototype.slice.call(anchorLinks);
anchorLinksArr.forEach(function (link) {
link.addEventListener('click', function (e) {
e.preventDefault();
const targetId = link.hash;
const targetElement = document.querySelector(targetId);
if (window.matchMedia('(max-width: 767px)').matches) {
const targetOffsetTopSP = window.pageYOffset + targetElement.getBoundingClientRect().top - 60; //ここに- 50 などと数値を入れるとヘッダー固定のスクロールが実現できる
window.scrollTo({
top: targetOffsetTopSP,
behavior: "smooth"
});
} else if (window.matchMedia('(min-width:768px)').matches) {
const targetOffsetTopPC = window.pageYOffset + targetElement.getBoundingClientRect().top - 60; //ここに- 50 などと数値を入れるとヘッダー固定のスクロールが実現できる
window.scrollTo({
top: targetOffsetTopPC,
behavior: "smooth"
});
}
});
});
});
PC・スマホ幅を判別するために、いつでもそこから取り出せるよう変数を定義しているが、前回も解説しているので省略したいと思う。
ヘッダーのインクルード化
$('header').load('inc/header.html', function () {
ここに処理内容を記載していく
});
インクルードという用語に聞きなれない人もいるかもしれない。ようはいろんなページでヘッダーのよう同じレイアウトを流用したい場合があると思う。
そんな時に、同じソースを何度も記述するのが面倒になるので、なんかもっと効率良くなる方法ないかなということで、生まれたのがインクルード対応である。
別のファイルでインクルード用のhtmlファイルを作っておけば(今回はincフォルダの中にheader.htmlを作成)、それを読み込むことで他のhtmlファイルにも引っ張ってくることができる。
実際の対応方法だが、シングルクォーテーションで囲われているheaderは任意のものを指定すればよい。今回はheaderだが、classを指定したいなら「.class名」、idを指定したいなら「#id名」を指定すればよい。
よく使われるのは、headerやfooterである。その他には、タブ切り替えでカレンダーなどを掲載している場合、ソースが異常に長くなるため、月ごとにインクルード化して対応することが多い。
なお、今回は、インクルード化した上で、さらに処理内容を重ねて書いているので、上記のソースになるが、本来であれば、functionの処理を書かずに短く対応することが多い。下記のような感じだ。
$('header').load('inc/header.html');
これだけでも、読み込みが可能だ。
ハンバーガーメニューの3本線をクリックした時の動きを調整
$('.bl_hamburgerLine').on('click', function () {
$(this).toggleClass('active');
$(this).next().fadeToggle();
});
$('.bl_menuSub ul li a').on('click', function () {
$('.bl_hamburgerLine').toggleClass('active');
$('nav').fadeToggle();
});
ここは前回も解説しているので、簡単に説明する。
.bl_hamburgerLineをクリックすると、activeのクラスがついて、同時にnav部分がfadeしながら表示される。もう一度クリックするとactiveのクラスが外れ、同時にnav部分がfadeしながら非表示になる。
サブメニューをクリックすると、.bl_hamburgerLineについていたactiveのクラスが外れ、同時にnav部分がfadeしながら非表示になる。
デバイス幅ごとに動きを調整
if (mq('sp')) {
$('.bl_menuBox > ul > li').on('click', function () {
$(this).children('p').next().fadeToggle();
});
$('.bl_mainMenu').on('click', function () {
$(this).prev().fadeOut();
});
} else {
$('.bl_menuBox > ul > li').on({
mouseenter: function mouseenter() {
$(this).children('p').next().fadeIn();
},
mouseleave: function mouseleave() {
$(this).children('p').next().fadeOut();
}
});
}
スマホ幅の時は、メインメニューをタップした時に、「.bl_menuBox > ul > li」の子要素のpタグ(メインメニュー)の次の兄弟要素.bl_menuSub(サブメニュー)がfadeInされて展開される。さらに、「.bl_mainMenu」をタップしたときに、「.bl_menuSub」がfadeOutされるように設定している。
PC幅の時は、メインメニューをマウスホバーした時に、「.bl_menuBox > ul > li」の子要素のpタグの次の兄弟要素.bl_menuSub(サブメニュー)がfadeInされて展開され、マウスがメインメニューから離れた時は、fadeOutされるように設定している。
これで実装完了だ。スムーススクロールについては、今回は使用しないが、念のため前回のものを流用して掲載している。
これで複雑なハンバーガーメニューについても、ある程度は実装ができるようになるかと思う。
トラックバック & ピングバック
[…] ハンバーガーメニューを展開してからのメニューをさらに展開させるメニューをjQueryで作ってみた。2階層パターン対応(備忘録) […]
コメントはクローズされています。