ナビゲーションにホバーすると、下層のナビゲーションが出てくる、いわゆるドロップダウンメニューを作ってみました。
調子に乗ってレスポンシブで、スマホのときはアコーディオンメニューにしようとか考えて、結構苦労しました・・・。
デモはこちらから
DEMO
以下、コードです。
HTML
<header></header> | |
<section> | |
<nav> | |
<ul> | |
<li><a href="">nav01</a> | |
<ul> | |
<li><a href="">list01</a></li> | |
<li><a href="">list02</a></li> | |
<li><a href="">list03</a></li> | |
</ul> | |
</li> | |
<li><a href="">nav02</a> | |
<ul> | |
<li><a href="">list01</a></li> | |
<li><a href="">list02</a></li> | |
<li><a href="">list03</a></li> | |
</ul> | |
</li> | |
<li><a href="">nav03</a> | |
<ul> | |
<li><a href="">list01</a></li> | |
<li><a href="">list02</a></li> | |
<li><a href="">list03</a></li> | |
</ul> | |
</li> | |
<li><a href="">nav04</a> | |
<ul> | |
<li><a href="">list01</a></li> | |
<li><a href="">list02</a></li> | |
<li><a href="">list03</a></li> | |
</ul> | |
</li> | |
<li><a href="">nav05</a> | |
<ul> | |
<li><a href="">list01</a></li> | |
<li><a href="">list02</a></li> | |
<li><a href="">list03</a></li> | |
</ul> | |
</li> | |
</ul> | |
</nav> | |
</section> |
liの中にulを入れる入れ子構造です。普通に打つとかなりめんどくさいですね。何かしら楽をする方法を使う方がいいでしょう。私の場合はemmetでやりました。
CSS
* { | |
margin: 0; | |
padding: 0; | |
list-style: none; | |
box-sizing: border-box; | |
} | |
a { | |
text-decoration: none; | |
} | |
header{ | |
width: 100%; | |
height: 50px; | |
background-color: #111; | |
} | |
nav { | |
width: 100%; | |
} | |
nav > ul > li { | |
width: 100%; | |
} | |
nav > ul > li > a { | |
display: block; | |
width: 100%; | |
padding: 10px 0; | |
border-bottom: 1px solid #ccc; | |
background-color: #fff; | |
color: #000; | |
text-align: center; | |
} | |
nav > ul > li:first-child > a{border-top: 1px solid #ccc;} | |
nav > ul > li > ul { | |
display: none; | |
} | |
nav > ul > li li > a { | |
display: block; | |
width: 100%; | |
padding: 10px 5%; | |
border-bottom: 1px solid #ccc; | |
background-color: #111; | |
color: #fff; | |
} | |
@media screen and (min-width: 700px) { | |
nav{margin-top: 50px;} | |
nav > ul { | |
width: 960px; | |
margin: 0 auto; | |
font-size: 0; | |
} | |
nav > ul > li { | |
display: inline-block; | |
width: calc(100% / 5); | |
font-size: 1rem; | |
vertical-align: top; | |
} | |
nav > ul > li > a { | |
border: none; | |
border-left: 1px solid #ccc; | |
} | |
nav > ul > li:first-child > a { | |
border-top: none; | |
border-left: none; | |
} | |
} |
動きに関わる部分は特にありません。ほぼ見た目を整えるために使ってます。「>」は子要素を指定するセレクタです。今回みたいな入れ子構造のものは子要素のみを指定してあげればclass・idなしでも実装できます。(メンテナンス性とかの良し悪しはさておき・・・)
jQuery
$('nav').prepend('<div class="toggle">menu</div>'); | |
$('.toggle').css({ | |
'width':'100%', | |
'height':'50px', | |
'background-color':'#999', | |
'color':'#fff', | |
'text-align':'center', | |
'line-height':'50px' | |
}); | |
$(window).on('load resize',function(){ | |
var winWidth = $(window).width(); | |
if(winWidth < 700){ | |
$('.toggle').show(); | |
$('nav>ul').hide(); | |
} else{ | |
$('.toggle').hide(); | |
$('nav>ul').show(); | |
} | |
}); | |
$('.toggle').on('click',function(){ | |
$(this).next('ul').slideToggle(); | |
$('.open').removeClass('open'); | |
$('nav>ul>li>a').next('ul').slideUp(); | |
operner =close; | |
}); | |
var opener = close;//flagでクリックしたときのアコーディオンの開くまでの時間を調節 | |
//ウィンドウサイズ640px以下の時の動作 | |
$('nav>ul>li>a').on('click',function(){ | |
var winWidth = $(window).width(); | |
if(winWidth < 700){//ウィンドウサイズ640px以上なら | |
if($(this).hasClass('open')){//クリックした要素がopenクラスを持っているなら | |
$(this).removeClass('open'); | |
$(this).next('ul').slideUp(); | |
opener = close; | |
} else{//クリックした要素がopenクラスを持っていないなら | |
var timer;//アコーディオンが開くまでの遅延時間 | |
console.log(opener); | |
if(opener !== close){ | |
timer = 500; | |
}else{ | |
timer = 0; | |
} | |
$('.open').removeClass('open'); | |
$('nav>ul>li>a').next('ul').slideUp(); | |
setTimeout(()=>{ | |
$(this).addClass('open'); | |
$(this).next('ul').slideDown(); | |
},timer); | |
opener = open; | |
} | |
event.preventDefault(); | |
}else{//ウィンドウサイズ640px以下なら | |
return false; | |
} | |
}); | |
//ウィンドウサイズ640px以上の時の動作 | |
$('nav>ul>li').on('mouseover',function(){//マウスをのせたとき | |
var winWidth = $(window).width(); | |
if(winWidth > 700){//ウィンドウサイズ640px以上なら | |
if(!$(this).children('a').hasClass('open')){ | |
$(this).children('a').addClass('open'); | |
$(this).children('ul').stop().slideDown(); | |
} | |
}else{//ウィンドウサイズ640px以下なら | |
return false; | |
} | |
}).on('mouseout',function(){ | |
var winWidth = $(window).width(); | |
if(winWidth > 700){//ウィンドウサイズ640px以上なら | |
$(this).children('a').removeClass('open'); | |
$(this).children('ul').stop().slideUp(); | |
}else{//ウィンドウサイズ640px以下なら | |
return false; | |
} | |
}); | |
$('nav>ul>li>a').on('click',function(){ | |
event.preventDefault(); | |
}); |
画面サイズによってイベントを切り替えています。700pxより大きい場合はhoverイベントが、小さい場合はclickイベントが動作します。さらに700px以下の時はmenuというボタンが現れ、ナビゲーションが格納されます。これは読み込み時と画面サイズが変わったときに読み込むようにしています。
ちなみに.on(‘load resize’)の中にクリックやホバーのイベントを入れると正常に動作しません。
jQueryのonイベントは入れ子にすると、外側のイベントの読み込みを内側のイベントに継承してしまいます。つまり、.on(‘load resize’)の関数の中に.on(‘click’)を入れると、リサイズが起こった回数だけクリックされたという判断がされてしまい、アコーディオンが何度も開いたり閉じたりしてしまうのです。
なので、イベントごとにウィンドウサイズを判断して、サイズによる出しわけをしています。
openerという変数は他のアコーディオンが開いているかどうかを判断しています。どのアコーディオンも閉じているときは、クリックしてすぐ開きますが、どれかのアコーディオンが開いているときは、閉じ終わってから開くようになっています。
=>・・・この記号はES2015から実装されたアロー関数というものです。setTImeoutなどの通常thisを継承できない場合でも、thisを継承してくれるというものです。(このあたり説明すると長いので、また別の機会にでも。)ちなみにES2015の仕様は、サポートしてないブラウザも結構あるので(iPhone5sではうごきませんでした)、使用時は注意が必要です。
今回は、イベントが重なるとだめなところとか、アロー関数とか、実際に試すことができたので面白かったです。
以下参考にしたサイトと書籍。大変勉強になりました。
参考サイト:
jQuery.on()は追加式なのでイベントの重複登録に注意しよう
参考書籍:
『フロントエンドエンジニアのための現在とこれからの必須知識』(マイナビ出版)