CSSだけで作る「多階層」なドロップダウンメニュー
以前書いた「CSSだけで作る動きのあるドロップダウンメニュー|Webpark」という記事が検索エンジン経由でそこそこアクセスがあり、よく質問をいただきます。そのなかで多階層にできないのかという質問があったので作ってみました。

まずはサンプルをご覧ください。
IE7の場合擬似要素が使えませんので、次の階層があることを示す矢印が表示されません。ドロップダウンの機能はIE7でも問題ありませんので、IE7に対応させる場合は矢印を画像にするなどすればよいと思います。
それではHTMLからいきます。
HTML
かなり長くなるのでメニュー1の部分だけ載せています。
<ul id="dropmenu"> <li><a href="#">メニュー1</a> <ul> <li><a href="#">子メニュー</a> <ul> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a> <ul> <li><a href="#">ひ孫メニュー</a></li> <li><a href="#">ひ孫メニュー</a></li> <li><a href="#">ひ孫メニュー</a></li> <li><a href="#">ひ孫メニュー</a></li> </ul> </li> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a></li> </ul> </li> <li><a href="#">子メニュー</a> <ul> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a></li> </ul> </li> <li><a href="#">子メニュー</a> <ul> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a> <ul> <li><a href="#">ひ孫メニュー</a></li> <li><a href="#">ひ孫メニュー</a></li> <li><a href="#">ひ孫メニュー</a></li> <li><a href="#">ひ孫メニュー</a></li> </ul> </li> <li><a href="#">孫メニュー</a></li> </ul> </li> <li><a href="#">子メニュー</a> <ul> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a></li> <li><a href="#">孫メニュー</a></li> </ul> </li> </ul> </li>
かなり長いですが、要するに次の階層がある場合はliの中にリストを入れているだけです。
ということで以下のようになるのは間違いです。
<li><a href="#">メニュー</a></li> <ul> <li><a href="#">子メニュー</a></li> <li><a href="#">子メニュー</a></li> </ul>
CSS
前回は角丸にしたり、ボーダーを重ねたりしましたが、今回それをするとコードが膨大になりますのでシンプルにしました。逆にこちらの方が今っぽいかも。
ではまず全体を載せます。
#dropmenu { list-style-type: none; width: 960px; margin: 30px auto 500px; padding: 0; } #dropmenu li { position: relative; width: 20%; float: left; margin: 0; padding: 0; text-align: center; } #dropmenu li a { display: block; margin: 0; padding: 16px 0; background: #8a9b0f; color: #fff; font-size: 14px; font-weight: bold; line-height: 1; text-decoration: none; } #dropmenu li ul { list-style: none; position: absolute; z-index: 100; top: 100%; left: 0; width: 100%; margin: 0; padding: 0; } #dropmenu li ul li { overflow: hidden; width: 100%; height: 0; color: #fff; transition: .2s; } #dropmenu li ul li a { padding: 13px 15px; background: #7c8c0e; text-align: left; font-size: 12px; font-weight: normal; } #dropmenu > li:hover > a { background: #7c8c0e } #dropmenu > li:hover li:hover > a { background: #6e7c0c } #dropmenu li:hover > ul > li { overflow: visible; height: 38px; } #dropmenu li ul li ul { top: 0; left: 100%; } #dropmenu li:last-child ul li ul { left: -100%; width: 100%; } #dropmenu li ul li ul li a { background: #616d0b } #dropmenu li:hover ul li ul li:hover > a { background: #535d09 } #dropmenu li ul li ul:before { position: absolute; content: ""; top: 13px; left: -20px; width: 0; height: 0; border: 5px solid transparent; border-left-color: #454e08; } #dropmenu li:last-child ul li ul:before { position: absolute; content: ""; top: 13px; left: 200%; margin-left: -20px; border: 5px solid transparent; border-right-color: #454e08; }
長くて嫌になるかもしれませんが順を追って考えればそんなに難しくはないと思います。
では、次からは個別に解説します。勉強になると思うのでお付き合いいただければと思います。
親メニュー
#dropmenu { list-style-type: none; width: 960px; margin: 30px auto 500px; padding: 0; } #dropmenu li { position: relative; width: 20%; float: left; margin: 0; padding: 0; text-align: center; } #dropmenu li a { display: block; margin: 0; padding: 16px 0; background: #8a9b0f; color: #fff; font-size: 14px; font-weight: bold; line-height: 1; text-decoration: none; }
最初から表示されている親メニューの部分です。全体の長さは今回960pxにしています。ここ以外の横幅は全てパーセントで指定していますので、全体の幅を変えるときはここだけで大丈夫です。全体の幅はパーセントで指定するとリキッドなメニューになります。
「#dropmenu li」は子メニューの基準となりますので「position: relative;」を指定します。
他の部分については横並びメニューの基本ですので問題ないと思います。
子以下のメニュー
#dropmenu li ul { list-style: none; position: absolute; z-index: 100; top: 100%; left: 0; width: 100%; margin: 0; padding: 0; } #dropmenu li ul li { overflow: hidden; width: 100%; height: 0; color: #fff; transition: .2s; } #dropmenu li ul li a { padding: 13px 15px; background: #7c8c0e; text-align: left; font-size: 12px; font-weight: normal; }
子、孫、ひ孫メニューに共通する設定です。例えば「#dropmenu li ul」は子メニューだけに適用されると思いがちですが、「#dropmenu li ul li ul」の孫メニュー、「#dropmenu li ul li ul li ul」のひ孫メニューにも適用されます。子メニューだけに適用させようと思うと「#dropmenu > li > ul」という風に間に子要素のみに適用させる「>」が必要になります。
で、それぞれ親要素を基準に絶対値で配置します。「top: 100%」と「left: 0;」は子メニュー用で、孫とひ孫は横に展開するので後で別に指定します。
liは元を「height: 0;」としているので表示されません。で、マウスを乗せたときに高さを持たせることで表示されるようになります。さらにtransitionを指定することで、子メニューが上から降りて現れます。
元が「display: none;」でマウスを乗せたときに「display: block;」でもいいと思います。そのときheightは必要ありません。
マウスを乗せたとき
#dropmenu > li:hover > a { background: #7c8c0e }
マウスを乗せたときの親メニュー色の指定です。「a:hover」とすると子メニュー以下にマウスが移ったときに親メニューの色が元に戻ります。「li:hover a」とすることで、子メニュー以下にマウスがあるときも親メニューの色は変わったままになります。
#dropmenu > li:hover li:hover > a { background: #6e7c0c }
先ほどと同じで、子メニューから孫メニューにマウスが移ったときの子メニューの色、孫メニューからひ孫メニューにマウスが移ったときの孫メニューの色が元の色に戻らないようにしています。
この辺は「>」が抜けると意図しないところに影響を与えることもあるので注意しましょう。
#dropmenu li:hover > ul > li { overflow: visible; height: 38px; }
親メニューにマウスを乗せたときの子メニューの設定です。先ほど説明したように、元が「height:0」で表示されていなかったものを「height: 38px;」とすることで表示されるようにします。
「li:hover > ul > li」の「>」が抜けると孫メニューもひ孫メニューも一気に開いてしまいます。
孫、ひ孫メニュー
#dropmenu li ul li ul { top: 0; left: 100%; } #dropmenu li:last-child ul li ul { left: -100%; width: 100%; } #dropmenu li ul li ul li a { background: #616d0b } #dropmenu li:hover ul li ul li:hover > a { background: #535d09 }
子以下のメニューのところの「#dropmenu li ul」で指定しましたが、これだと子メニュー用のように下にメニューが現れますので、孫、ひ孫メニュー用に横に現れるようにします。
高さは親メニュー(孫だと子)と同じにして、「left: 100%;」で右に隣り合うように配置されます。
で、一番右のメニュー5は孫メニューが右に現れるとディスプレイからはみ出ることも考えられるので左に配置されるようにします。
「:last-child」はIE8以下は非対応ですので、気になる場合はクラスを使うのがよいと思います。
下の階層があることを示す矢印
#dropmenu li ul li ul:before { position: absolute; content: ""; top: 13px; left: -20px; width: 0; height: 0; border: 5px solid transparent; border-left-color: #454e08; } #dropmenu li:last-child ul li ul:before { position: absolute; content: ""; top: 13px; left: 200%; margin-left: -20px; border: 5px solid transparent; border-right-color: #454e08; }
下の階層にメニューがあることを示す矢印をつけます。多階層になる場合は、これがないとかなり使い勝手が悪いと思います。
度々紹介している擬似要素を使った三角形です。下の方は右端のメニュー用の矢印です。
ここのポイントは、子メニューに表示されている矢印は、孫メニューの外側にある矢印ということです。孫メニューがある場合の子メニューに矢印を付けるなんて指定ができればいいのですが、今のところそのようなセレクタはないと思います。
まとめ
多階層になるとセレクタも複雑になるので疲れました。結構長くなりましたが、最初は角丸やボーダーもあったのでもっと長くなっていました。
で、必要な部分だけ効率的に書こうと色々考えましたが、もっと効率的な方法もあるかもしれません。
今回のサンプルはひ孫までですが、追加すれば玄孫でも大丈夫なはずです。ここまで必要なサイトはなかなかないと思いますが。。
個人的にはあまり使うことがなさそうですが、今回作ってみていい頭の体操になりました。皆さんのお役に立てたならうれしいです。
