(jQuery) 最も単純なサブメニュー開閉

(最終更新: 2016-10-14)


解説
下記のリスト要素を開閉式メニューにしたい場合。

<ul id="main_menu">
<li><a href="#">見出し1</a>
<ul>
<li><a href="#">サブメニュー1</a></li>
<li><a href="#">サブメニュー1</a></li>
<li><a href="#">サブメニュー1</a></li>
</ul>
</li>
<li><a href="#">見出し2</a>
<ul>
<li><a href="#">サブメニュー2</a></li>
<li><a href="#">サブメニュー2</a></li>
<li><a href="#">サブメニュー2</a></li>
</ul>
</li>
<li><a href="#">見出し3</a>
<ul>
<li><a href="#">サブメニュー3</a></li>
<li><a href="#">サブメニュー3</a></li>
<li><a href="#">サブメニュー3</a></li>
</ul>
</li>
</ul>


マウスホバーによる開閉。


$('#main_menu > li').hover(
function(){ $(this).children('ul').slideDown() },
function(){ $(this).children('ul').slideUp() }
);


クリックによる開閉。


$('#main_menu > li').click(function(ev){
var sub = $(this).children('ul');
if ($(sub).is(':hidden')) $(sub).slideDown();
else $(sub).slideUp();
});
ただ、このままでは他で開いているサブメニューを閉じることができないので、
下記のようにします。

$('#main_menu > li').click(function(ev){
var sub = $(this).children('ul');
if ($(sub).is(':hidden')) {
$('#main_menu > li > ul:visible').slideUp();
$(sub).slideDown();
} else {
$(sub).slideUp();
}
});
さらに、メニュー以外の、ページ内の全く関係のない場所をクリックしたら、全てのサブメニューを閉じるようにします。

//基本的に、ページ内のどこをクリックしても全てのサブメニューを閉じるようにしておく。
$(document).click(function() { $('#main_menu > li > ul').slideUp() });

$('#main_menu > li').click(function(ev){
var sub = $(this).children('ul');
if ($(sub).is(':hidden')) {
// 今回はこれからサブメニューを開きたい項目をクリックしているので、
// 上記の全てのサブメニューを閉じるイベントを発火させてはならない。
// よって、イベントのバブリングを中止する。
ev.stopPropagation();

$('#main_menu > li > ul:visible').slideUp();
$(sub).slideDown();
} else {
$(sub).slideUp();
}
});

そうなると、"else"以降の記述は余計なので削除します。
最終形は下記。

$(document).click(function() { $('#main_menu > li > ul').slideUp() });
$('#main_menu > li').click(function(ev){
var sub = $(this).children('ul');
if ($(sub).is(':hidden')) {
ev.stopPropagation();
$('#main_menu > li > ul:visible').slideUp();
$(sub).slideDown();
}
});


余談
ある要素のグループで、イベントが起きた要素以外の要素を取得するにはどうしたらいいのかな、と悩みました。
イベントハンドラの内部で、

$('あるグループ:not(this)').css('color', 'red');
のような書き方ができれば簡単なのですが。
残念ながらそういうセレクタの指定はできなので、今回のように
イベントが起きた要素の状態で分岐を作り、その一方の内部で、
イベントが起きた要素には絶対に当てはまらないセレクタを指定することで
"その他"を選り分けることにしました。


他に考えられる方法は、イベントが起きた要素にクラス名を与える方法でしょうか。
これなら、


$('あるグループ').removeClass('selected');
$(this).addClass('selected');
$('あるグループ:not(.selected)').css('color', 'red');
という書き方ができます。


もうひとつ。filter()を使う方法。


$('あるグループ').click(function(ev) {
$('あるグループ').filter(function() {
return !$(this).is(ev.target);
}).css('color', 'red');
});
うーん…、もっと賢い方法があるのかもしれません。





※ 当ブログ内の"サブメニュー"に関する記事