[css+jQuery]画面を暗くしつつ横から生えてくるメニュー作成β版

Amazonとかみたいな。

完成イメージ

AmazonとかGoogleとかの、スマホで見たときの、ハンバーガーメニューをクリックしたら

  1. 背景が暗くなる
  2. メニューが横から生えてくる

を作りたい。

https://www.amazon.co.jp/
Browse Fonts - Google Fonts
Making the web more beautiful, fast, and open through great typography

パクったら早いはずなんだけどこういうのは込み入りすぎてて全部を真似きるのが非常にめんどくさいというか無理というか。似たものを作る。

作る

とりあえずコード。

<header>
    <div class="wrap">
        <div class="hnav_btn"><a id="hnav_btn"><span>menu</span></a></div>
        <div class="hnav">
            <div class="hnav_bg"></div>
            <div class="inner">
                <nav>
                    <ul class="hnav_list">
                        <li><a href="">ああああああああああ</a></li>
                        <li><a href="">ああああああああああ</a></li>
                        <li><a href="">ああああああああああ</a></li>
                        <li><a href="">ああああああああああ</a></li>
                        <li><a href="">ああああああああああ</a></li>
                    </ul>
                </nav>
            </div>
        </div>
    </div>
</header>
@import url(https://fonts.googleapis.com/css2?family=Material+Icons&family=Material+Icons+Outlined&family=Material+Icons+Round&family=Material+Icons+Sharp&family=Material+Icons+Two+Tone&display=swap);

header{
  width: 100%;
  height: 56px;
  box-shadow: 0 0 10px rgba(0,0,0,0.1);
  position: fixed;
  top: 0;
  left: 0;
  background: #fafafa;
  z-index: 100;
}
header .wrap{
  height: 100%;
  display: flex;
  justify-content: flex-start;
  align-items: center;
}
header .wrap .hnav_btn{
  padding: 0 20px;
  display: block;
}
header .wrap .hnav_btn a{
  width: 30px;
  height: 30px;
  text-decoration: none;
  color: #666;
  display: block;
  position: relative;
  cursor: pointer;
}
header .wrap .hnav_btn a::before{
  content: "\e5d2";
  font-family: "material icons";
  font-size: 2.4rem;
  text-align: center;
  line-height: 24px;
  width: 24px;
  height: 24px;
  display: block;
  position: absolute;
  top: calc(50% - 12px);
  left: calc(50% - 12px);
}
header .wrap .hnav_btn a.active::before{
  content: "\e5cd";
}
header .wrap .hnav_btn a span{
  display: block;
  text-indent: -9999px;
}
header .wrap .hnav{
  width: 100%;
  height: 100%;
  position: fixed;
  left: 0;
  top: 56px;
  z-index: -1;
  visibility: hidden;
}
header .wrap .hnav .hnav_bg{
  width: 100%;
  height: 100%;
  display: block;
  background: rgba(0,0,0,0.8);
  position: absolute;
  top: 0;
  left: 0;
  opacity: 0;
}
header .wrap .hnav .inner{
  min-width: 265px;
  max-width: 320px;
  width: calc(100% - 56px);
  height: 100%;
  background: #fafafa;
  display: block;
  box-shadow: 0 0 10px rgba(0,0,0,0.1);
  position: relative;
  z-index: 1;
  transform: translateX(-100%);
}
header .wrap .hnav nav{
  width: 100%;
  height: 100%;
  overflow-y: auto;
  position: relative;
}
header .wrap .hnav nav ul.hnav_list{
  width: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  border-top: 1px solid #eee;
  border-bottom: 1px solid #eee;
  position: absolute;
  left: 0;
  top: 0;
}
header .wrap .hnav nav ul.hnav_list > li{
  border-bottom: 1px solid #f5f5f5;
}
header .wrap .hnav nav ul.hnav_list > li:last-child{
  border: none;
}
header .wrap .hnav nav ul.hnav_list > li > a{
  padding: 16px 30px;
  font-weight: 500;
  display: block;
  position: relative;
}
header .wrap .hnav nav ul.hnav_list > li > a::after{
  content: "\e5cc";
  font-family: "material icons";
  text-align: center;
  line-height: 20px;
  width: 20px;
  height: 20px;
  display: block;
  position: absolute;
  right: 10px;
  top: calc(50% - 10px);
  transition: right 0.2s ease;
}
$(function(){
    function hnavOpen(){
        $('header .wrap .hnav').css("visibility","visible").dequeue();
        $('header .wrap .hnav .inner').css({transform:"translateX(0)", transition:"transform 0.5s"});
        $('header .wrap .hnav .hnav_bg').css({opacity:"1", transition:"opacity 0.5s ease"});
    }
    function hnavClose(){
        $('header .wrap .hnav').delay(500).queue(function(){$(this).css("visibility","hidden")});
        $('header .wrap .hnav .inner').css({transform:"translateX(-100%)", transition:"transform 0.5s"});
        $('header .wrap .hnav .hnav_bg').css({opacity:"0", transition:"opacity 0.5s ease"});
    }
    $('#hnav_btn').click(function(){
        $(this).toggleClass('active');
        $('header .wrap .hnav').toggleClass('active');
        if($('header .wrap .hnav').hasClass('active')){
            hnavOpen();
        }else{
            hnavClose();
        }
    })
    $('header .wrap .hnav .hnav_bg').click(function(){
        $('#hnav_btn').removeClass('active');
        $('header .wrap .hnav').removeClass('active');
        hnavClose();
    })
})

サンプル。

Document

仕様について

動作自体はstyleの指定で行っているんだけど、各動作の切り替えがclassじゃなくてjsから直接ぶちこんである。というのは、classで管理しようにもよくわからんくなって混乱してしまった。落ち着いて見返せば整理できるはずだろうけど、とりあえず現段階の限界というかなんというか。classの切り替えだけじゃ対応しきれない部分もあったんで直接記述する箇所は残るんだけどね。ということでβ版。

表示するときの動作は難しい話じゃない。メニューの大枠(.hnav)を表示(visibility:visible)して、背景は不透明度を操作してフェードイン、メニュー本体はtransformの数値を変えて左からスライドさせる。フェードインもスライドもtransitionで動作時間を指定。

なんでtransformかっていえば、leftとかの絶対値指定だと不定形に対応できないから。
メニューの幅を実数で指定するなら絶対値でいいんだけどね、レスポンシブとか考えるとちょっとね。

//初期位置
transform:translateX(0);
//要素の幅分左にずれる
transform:translateX(-100%);

で、非表示(閉じる)ときが難点。フェードもスライドも終わってから大枠を非表示にしないと.hide()みたいに急に消えてしまう。ということで、非表示の際は大枠の非表示(visibility:hidden;)にフェードとスライドが終わったタイミングに合わせたディレイを掛けてある。

ディレイはそのままだと効かないので.queue()を噛ませて対応、ついでに.delay()は1回のみの動作になるので.dequeue()を効かせてリセット。

レスポンシブ対応

要するにPC/SP切り替え、通常はメニューを表示して一定の幅以下だと格納するようにしたい場合。こんな感じでやれる。

$(function(){
    $(window).on('load resize', function(){
        if(window.matchMedia('(max-width:767.98px)').matches){
            if(!$('header').hasClass('sp')){
                $('header').addClass('sp');
                $('header .wrap .hnav').removeClass('active').css("visibility","hidden").dequeue();
                $('header .wrap .hnav .inner').css("transform","translateX(-100%)");
                $('header .wrap .hnav .hnav_bg').css("opacity","0");
            }
        }else{
            if($('header').hasClass('sp')){
                $('header').removeClass('sp');
                $('header .wrap .hnav').removeClass('active').css("visibility","visible").dequeue();
                $('header .wrap .hnav .inner').css({transform:"translateX(0)", transition:""}); //transition解除
                $('#hnav_btn').removeClass('active');
            }
        }
    })
})

PC/SP切り替え判定時に初期値に戻すって内容。.hasClass()を噛ませることで、切替時以外は表示を継続できるって感じ。

SP→PC時、スライド部はtransitionを切る。切らないと移動にアニメーションが掛かってしまう。

総括

js上でcssを付与するのは本当はやりたくないんだけど、じゃあclassの付与で管理するってなっても、親要素にclassを付与して子要素のcssを切り替える方法じゃ効かないのが今回。子要素を先に動作させてから親要素を動作させる必要がある(「閉じる」のギミック)んで、各々にclassを振らなきゃいけない。それはいいとして、そうしたとしてもjsの記述はめんどくさいまんまっていう。.css()がaddClass()に置き換わるだけじゃんって。

親要素のclass付与で子要素を動作させて、親要素自体の表示・非表示の切り替えだけはjsで管理するって手もあるような気もする。そうすればjsもスッキリするかもしれない。多分。

でもなんていうか、親要素のディレイとフェードとかの動作時間を合わせることを考えたら、js上で一元管理したほうがいいような気もして。モヤモヤする。

コメント

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