[css+js]現行Amazonっぽいメニューを作る

Amazonにあるハンバーガーメニュークリックからブラウザ左側に表示されるやつ。

動作と導入メリット

https://www.amazon.co.jp/

まずメニュー一覧が表示され、クリックすると横にスライドして下層メニューが表示される(2022.01時点)。

基本的にこういうタイプのメニューはサブメニューがある場合、クリックすると対象項目の下に下層メニューが表示される。通常は、というか、普通のWEBサイトならそれでいいんだけど、ECサイトの商品カテゴリー分けとかメニューの中身が膨大になるときは、メニューが縦長になって恐ろしく見づらいことになってしまう。

このようにサブメニューだけの表示に切り替わるギミックは視覚的にも分かりやすくてとてもよろしいですね。Amazonは見た限りサブメニューは1層だけっぽいけど、複数設置できたらいいよねって。

構築

cssやらjsの前にhtmlから考えないといけない。
ぶっちゃけすごいめんどくさい。

  • メニューをクリックしたらサブメニューを表示させる
  • クリックしたメニューにより表示するサブメニューの内容を切り替える

この2つを組み合わせ、更に「他階層メニューへの遷移時、スライド効果をもたせる」をやる。

<div class="wrap">
    <div class="bg"></div>
    <div class="set">
        <ul class="main">
            <li id="nav_first"><!-- 1層 -->
                <ul>
                    <li><a class="sub" data-change="sub01">***</a></li>
                    <li><a class="sub" data-change="sub02">***</a></li>
                    <li><a class="sub" data-change="sub03">***</a></li>
                </ul>
            </li>
            <li id="nav01"><!-- 2層 -->
                <ul id="sub01">
                    <li><a class="sub" data-change="sub0101">***</a></li>
                    <li><a class="sub" data-change="sub0102">***</a></li>
                    <li><a class="sub" data-change="sub0103">***</a></li>
                </ul>
                <ul id="sub02">
                    <li><a class="sub" data-change="sub0201">***</a></li>
                    <li><a class="sub" data-change="sub0202">***</a></li>
                    <li><a class="sub" data-change="sub0203">***</a></li>
                </ul>
                <ul id="sub03">
                    <li><a class="sub" data-change="sub0301">***</a></li>
                    <li><a class="sub" data-change="sub0302">***</a></li>
                    <li><a class="sub" data-change="sub0303">***</a></li>
                </ul>
            </li>
            <li id="nav02"><!-- 3層 -->
                <ul id="sub0101">
                    <li><a class="back">戻る</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                </ul>
                <ul id="sub0102">
                    <li><a class="back">戻る</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                </ul>
                <ul id="sub0103">
                    <li><a class="back">戻る</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                </ul>
                <ul id="sub0201">
                    <li><a class="back">戻る</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                </ul>
                <ul id="sub0202">
                    <li><a class="back">戻る</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                </ul>
                <ul id="sub0203">
                    <li><a class="back">戻る</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                </ul>
            </li>
            <li id="nav03"><!-- 4層 -->
                <ul id="sub0301">
                    <li><a class="back">戻る</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                </ul>
                <ul id="sub0302">
                    <li><a class="back">戻る</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                </ul>
                <ul id="sub0303">
                    <li><a class="back">戻る</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                    <li><a>***</a></li>
                </ul>
            </li>
        </ul>
    </div>
</div>

動作としては以下の2つを組み込む。

  • 階層ごとにスライドする
  • 階層スライド時に該当メニューを表示する

<li>の中に<ul>を書く、一般的なサブメニューの組み方はしなかった。表示・非表示を親子関係下で切り替えるのは恐ろしく厄介なことになるんで、階層別にグループに分けることにした。このへんはAmazonに倣った感じがある。

注意点というか仕様として、見ての通りサブメニューの階層は動的に増やせない(増やしにくい)構造なんで、「3層までで組んでるから4層以降を作っても動かない」みたいな制限がかかる。これは今回組んだ内容をバージョンアップすれば解消できる気もするけど、あまりにも大掛かりだから課題というか今回は手を付けないことにする。

data属性と合致するidのメニューを表示する、って動作を組み込むことになるので遷移先の指定をするためにidとかdata属性をきちんと設定しなければならない。ミスっちゃいけないところだし数が増えると連番が膨大になるんで非常にめんどくさい。<ul>を入れ子にできたらどんなに楽かと、悩んだところ。

.wrap{
    width: 100%;
    height: 100%;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1;
    display: none;
}
.wrap .bg{
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.8);
    position: absolute;
    top: 0;
    left: 0;
}
.wrap .set{
    width: 320px;
    height: 100%;
    background: #fff;
    position: relative;
    z-index: 1;
    overflow-x: hidden;
    transform: translate(0);
}
ul.main{
    width: 960px;
    height: 100%;
    white-space: nowrap;
    transform: translate(0);
    transition: transform 0.2s ease;
}
ul.main.show_sub01{ transform: translate(-320px,0);}
ul.main.show_sub02{ transform: translate(-640px,0);}
ul.main.show_sub03{ transform: translate(-960px,0);}
ul.main > li{
    width: 320px;
    height: 100%;
    float: left;
    position: relative;
    overflow-y: auto;
}
ul.main ul{
    width: 100%;
    margin: 0;
    padding: 0;
    list-style: none;
    display: none;
    position: absolute;
    left: 0;
    top: 0;
}
ul.main ul:first-child{ display: block;}
ul.main ul li a{
    width: 100%;
    padding: 16px 20px;
    text-decoration: none;
    display: block;
    position: relative;
    cursor: pointer;
    color: #333;
}
ul.main ul li.sub a::before{
    content: "\f054";
    font-family: "font awesome 5 free";
    font-weight: 600;
    font-size: 1rem;
    text-align: center;
    line-height: 30px;
    color: #999;
    width: 30px;
    height: 30px;
    display: block;
    position: absolute;
    top: calc(50% - 15px);
    right: 0;
}

amazonに倣って、下層リンクのやつにアイコンを付けるパターン。

前述の通りカスタマイズ性に難があるというのがまずここにあって、メニューの幅は実数で設定する必要がある。大枠に当たるul.mainがそうで、実数を当てておかないとスクロールバーがメニューに被ってしまって都合が悪い。その前提からいくと、層の数にあった幅を入れなきゃいけないんで、何層作るかを決めておかなきゃいけないし表示する幅も決めておかなきゃいけない。

表示幅さえ決めておけばjsでliの数を拾って指定しちゃえばいいんだけどね。でも数値の指定がcssとjsそれぞれにあるとめんどくさくなるんで、cssだけで苦労できれば御の字。

下層メニューへのスライドはtransformで指定。position:absolute;でやっておけばleftでもいけると思う。

$(function(){
    $('.bg').click(function(){
        $('.wrap').fadeOut(function(){
            $('ul.main').removeClass('show_sub01 show_sub02 show_sub03');
        });
    })
    $('#nav_first a.sub').click(function(){
        $('ul.main').addClass('show_sub01');
        targetNext = $(this).data('change');
        $('#nav01 ul').hide();
        $('#nav01 ul#'+targetNext).show();
    })
    $('#nav01 a').click(function(){
        if($(this).hasClass('sub')){
            $('ul.main').addClass('show_sub02');
            targetNext = $(this).data('change');
            $('#nav02 ul').hide();
            $('#nav02 ul#'+targetNext).show();
        }else if($(this).hasClass('back')){
            $('ul.main').removeClass('show_sub01');
        }
    })
    $('#nav02 a').click(function(){
        if($(this).hasClass('sub')){
            $('ul.main').addClass('show_sub03');
            targetNext = $(this).data('change');
            $('#nav03 ul').hide();
            $('#nav03 ul#'+targetNext).show();
        }else if($(this).hasClass('back')){
            $('ul.main').removeClass('show_sub02');
        }
    })
})

jQuery3系で動作確認済み。

該当class下において、クリックしたリンクのdata属性を拾って表示する対象を指定する。1層目(#nav_first)はともかく、それより下層のやつ(#nav01、#nav02)は個別に指定してるけど
連番が違うだけで内容は同じなのでうまくやればまとめられる気がする。

階層の移動はclassの付与・削除でやってる。「現状における最大値」もしくは「最新の値」が強いっぽいんで、この通りの操作できちんと動作してる。

まとめ

ある程度WEBを触ってきたおかげで何となく作れる感じはしたんだけど、いざ作るとなるとめちゃくちゃ困った。アプローチは複数あるし、それぞれがややこしいからどれがスマートなのか分からなかった。で、自分で一番混乱しない形がこれだった。

overflow-yを仕込む必要があるからスクロールバーを気にしなきゃいけない、というのがマジでめんどくさい。この部分だけでも手間がかかる。というかここが一番めんどくさい。PC/SPで、useragentで振り分けるならまだいいんだけどね。ブラウザ幅指定のレスポンシブにするとメニューの幅を実数でやらなきゃいけない手前、どんくらいにするのが最適なんだよとか悩みどころが多い。実数ありきだから320pxあたりに留めるのが無難だよな、ブラウザ幅いっぱいに取ることもできるけどjs使わなきゃいけないからめんどいよな、とか、よくわからない何かと戦ったり消去法的な選択が発生する。

htmlもcssもjsも全部一般的な構成とは異なる感じのやつだから、作った後に手を入れる際もかなり大変だと思います。

コメント

タイトルとURLをコピーしました