見出し画像

ハンバーガーメニューのJSをちょっと変わった形で実装してみた


こんにちは!いちくん(@ichikun0000)と申します。

この記事ではタイトルにあるように、ちょっと変わったJSでハンバーガーメニューを作っていこうと思います。

ポイントだけかいつまんで解説していくので初心者の方にはすこし難易度が高いかもしれません。ご了承ください。

まずは完成形をみてみよう!

さて、まずどんな感じのハンバーガーメニューなのかを見てみましょう↓


コーポレートサイトでありそうなハンバーガーメニューにしてみました。

案件とかで使えそうだったら使ってみてください笑

完成形がわかったところで次から具体的な解説をしていきます。

コード解説(HTML)

まずはHTMLからです。先ほどのJSFiddleを別タブで開きながらみたり、ご自身のエディタにコピペしてみるといいでしょう。

HTMLはざっくり以下のようなイメージで作ってます。

画像1

.js-sub-btnをabsoluteで配置することで、.js-sub-btnをクリックしてもaタグのhref遷移ができないようになっています。

こうしないと、.js-sub-btnをクリックしてアコーディオンさせたいのにaタグのhref属性の値に遷移してしまいます。

コード解説(CSS)

つづいてCSSです。CSSはアニメーションの部分だけ解説します。

まず、@keyframesを2つ作成しています。

@keyframes fadeInDown {
 0% {
   opacity: 0;
   transform: translateY(-20px);
 }
 100% {
   transform: translateY(0);
   opacity: 1;
 }
}

@keyframes fadeOutUp {
 0% {
   transform: translateY(0);
   opacity: 1;
 }
 100% {
   transform: translateY(-20px);
   opacity: 0;
 }
}

作成した2つのアニメーションについてざっくり解説します。

fadeOutUp・・・アニメーションの初めは元の位置より-20px上にいて(transform:translateY(-20px))、目に見えない状態(opacity:0)。アニメーションが終了したときは元の位置に戻って(transform:translateY(0))目に見える状態になる(opacity:1)。

fadeInDown・・・アニメーションの初めはもとの位置にいて(transform:translateY(0))、目に見える状態(opacity:1)。アニメーションが終了したときは元の位置より-20px上に移動して(transform:translateY(-20px))消える(opacity:0)。

文字で読んでわかりますかね?(汗)本当は絵を書いてわかりやすく説明したいのですが、今回はご了承ください。(笑)

さて、次は作成した@keyframesたちを実際に使っていきたいと思います。

以下の部分で使っています。

.nav-menu > li {
 animation: fadeOutUp 0.5s cubic-bezier(.17,.67,.57,.99) forwards;
}
.nav-menu.active > li {
 opacity: 0;
 animation: fadeInDown 0.5s cubic-bezier(.17,.67,.57,.99) forwards;
 pointer-events:auto;
}

animationになんかいっぱい書いてありますね。

ショートハンドを使わないで書いてみると以下のようになります。

.nav-menu > li {
   animation-name: fadeOutUp;
   animation-duration: .5s;
   animation-timing-function: cubic-bezier(.17,.67,.57,.99);
   animation-fill-mode:forwards;
}

ショートハンドを使わないとわかりやすくなりますね。

このanimationの意味は、

fadeOutUp(またはfadeInDown)というアニメーションを0.5sの時間かけておこない、動きのスピードはcubic-bezier(.17,.67,.57,.99)で、アニメーション終了時のスタイルをアニメーション終了後にも適用させたい(forwards)場合の記述です。

cubic-bezierとはなんぞや?と思うかもしれませんが、これは、たとえばよくあるtransition:.3s ease-in-out;とかのease-in-outをcubic-bezeirに置き換えるとcubic-bezier(.17,.67,.57,.99) になります。

cubic-bezeirを使う利点としてはより細かな動きを数値で指定して作れるという点です。

デメリットはぱっと見どんな動きをするのかわからない点ですかね笑

以下のサイトで自在にcubic-bezeirを作れます。この機会に知っておきましょう。

animationについては以下の記事が詳しいので参考にしてください。

お次はjQeuryです。

コード解説(jQuery)

CSSをみてわかると思いますが、activeクラスをつけるとハンバーガーメニューの棒がクロスして、ナビゲーションメニューが上から下にふわっと出てきてオーバーレイもふわっと出てくるように書いています。

反対に、activeクラスを取ればハンバーガーメニューの棒が元に戻ってナビゲーションメニューは下から上にふわっと消えてオーバーレイもふわっと消えます。

なので、jQueyでactiveクラスをつけたいはずしたりすればいいわけですね。

今回のjQueryの実装の流れを言語化しておきましょう。

1.ハンバーガーメニューをクリックしたときactiveクラスを持っていなかったら、ハンバーガーメニューとナビゲーションメニューとオーバーレイにactiveクラスをつけて表示させる。

2.ハンバーガーメニューをクリックしたとき、activeクラスを持っていたらハンバーガーメニューとナビゲーションメニューとオーバーレイのactiveクラスを外して元の状態に戻す。

3.オーバーレイをクリックしたとき、activeクラスを持っていたらオーバーレイ自身とハンバーガーメニューとナビゲーションメニューのactiveクラスを外して元の状態に戻す

・・・なんかif分書いて頑張ればいける気がするけど、

結局、activeクラスを持っているか持っていないかを監視してあげればうまく制御できそうですね!(ここ、重要です!!

そこで、今回書いたJS(jQuey)はこちらです!

全部解説しようとすると日が暮れそうなのでポイントだけ次で解説します!

(function ($) {
 'use strict';


 var navObject = (function () {
   var cls = 'active'; //付与するクラス
   var Nav = function () {
     var _this = this; //Nav
     this.$nav_menu = $('.nav-menu');
     this.$overlay = $('.overlay');
     this.$humberger = $('.js-humberger');
     this.active_flg = false;//初期フラグをfalseに

     //ハンバーガークリック
     this.$humberger.on('click', function () {
       //active_flgがfalseだったらonメソッドを実行してactive_flgをtrueに
       //active_flgがtrueだったらoffメソッドを実行してactive_flgをfalseに
       (!_this.active_flg) ? _this.on(): _this.off();
     });

     //overlayクリック
     this.$overlay.on('click', function () {
       (!_this.active_flg) ? _this.on(): _this.off();
     });

     this.listdelay();

   };


   Nav.prototype = {
     on: function () {
       this.$nav_menu.addClass(cls);
       this.$humberger.addClass(cls);
       this.$overlay.addClass(cls);
       this.active_flg = true;
     },
     off: function () {
       this.$nav_menu.removeClass(cls);
       this.$humberger.removeClass(cls);
       this.$overlay.removeClass(cls);
       this.active_flg = false;
     },
     listdelay: function () {
       var timer = 0;
       this.$nav_menu.children('li').each(function () {
         $(this).css('animation-delay', timer + 's');
         timer = timer + 0.1;
       });
     }
   };

   return Nav;
 }());

 var nav = new navObject();


 //js-sub-btn
 (function () {
   $('.js-sub-btn').on('click', function () {
     $(this).next().slideToggle();
     $(this).toggleClass('is-open');
   });
 }());

})(jQuery);

コード解説(jQuey-1)

まず、今回付与するクラスを文字列として変数clsに代入します。

var cls = 'active'; //付与するクラス

つづいてコンストラクタを作成します。

コンストラクタとは...?

コンストラクタは、オブジェクトを作成し、初期化する関数オブジェクト (Function オブジェクト) です。

よくわかないと思うので、基本のコンストラクタをみてみましょう。

●基本のコンストラクタの定義
function Member(name,age){
   //プロパティ
   this.name = name;
   this.age = age;
}

 //メソッド
Member.prototype = {
 getName:function(){
   console.log('名前は',this.name);
 }

//コンストラクタMemberからnew演算子をつかって、memberオブジェクトを生成
var member = new Member('太郎', 30);
console.log(member);

●出力結果
Member {name: "太郎", age: 30}

console.log(member.name);

●出力結果
太郎

member.getName();
 
●出力結果
名前は太郎


このような感じです。

this.hoge = hoge;のようにしてプロパティをセットし、コンストラクタ名.prototype = foo function(){}でメソッドをセットします。

で、今回は以下の部分でコンストラクタを作成しています。

var Nav = function () {//コンストラクタ
     var _this = this; //Nav自身
     this.$nav_menu = $('.nav-menu');
     this.$overlay = $('.overlay');
     this.$humberger = $('.js-humberger');
     this.active_flg = false;//初期フラグをfalseに

     //ハンバーガークリック
     this.$humberger.on('click', function () {
       //active_flgがfalseだったらonメソッドを実行してactive_flgをtrueに
       //active_flgがtrueだったらoffメソッドを実行してactive_flgをfalseに
       (!_this.active_flg) ? _this.on(): _this.off();
     });

     //overlayクリック
     this.$overlay.on('click', function () {
       (!_this.active_flg) ? _this.on(): _this.off();
     });

     this.listdelay();

   };

プロパティにイベントを発生させたい要素のjQueryオブジェクト達と、監視したいactive_flgを用意します。

また、以下の部分に関しては次の章で解説するときに使うので一旦スルーしてください。

var _this = this;//Nav自身
ちなみに、jQueryオブジェクトだとわかるものは、わかりやすいように$マークをつけるので覚えておきましょう。active_flgはjQueryオブジェクトではないので$はつけません。

コード解説(jQuey-2)

続いてこの部分について解説します。

   //ハンバーガークリック
     this.$humberger.on('click', function () {
       //active_flgがfalseだったらonメソッドを実行してactive_flgをtrueに
       //active_flgがtrueだったらoffメソッドを実行してactive_flgをfalseに
       (!_this.active_flg) ? _this.on(): _this.off();
     });

ここでやっていることは簡単で、

ハンバーガーメニューをクリックしたときにactive_flgがtrueだったらoffメソッドを実行してactive_flgをfalseに

active_flgがfalseだったらonメソッドを実行してactive_flgをtrueにしているだけです。

overlayについても同じなので割愛します。

※onメソッドとかoffメソッドは次の章で解説します。

なぜ、this.active_flgではなく_this.active_flgなのかというとクリックイベントの中のthisはクリックイベントが発生した要素、つまりthis.$humbergerを指してしまうからです。

じゃあどうしたらいいのかというと、先ほど一旦スルーしてくださいといった以下の部分で、thisを_thisに格納しておくことでactive_flgに正しくアクセスできるようになるわけです。

var _this = this;//Nav自身

また、以下の部分が見慣れないので戸惑うかもしれませんね。

(!_this.active_flg) ? _this.on(): _this.off();

これは以下と同じです。

_this.active_flg ? _this.off(): _this.on();

やりたいことは一緒で、active_flgがtrueだったらoffメソッドを実行してactive_flgをfalseに、active_flgがfalseだったらonメソッドを実行してactive_flgをtrueにします。

ちなみに、これは三項演算子です。知らない人は覚えておきましょう。

以下の部分については、Navコンストラクタに登録したlistdelayメソッドを実行しています。listdelayメソッドについては次の章で解説します。

this.listdelay();

コード解説(jQuey-3)

続いてメソッドを追加している以下の部分について解説します。

     Nav.prototype = {
     on: function () {
       this.$nav_menu.addClass(cls);
       this.$humberger.addClass(cls);
       this.$overlay.addClass(cls);
       this.active_flg = true;
     },
     off: function () {
       this.$nav_menu.removeClass(cls);
       this.$humberger.removeClass(cls);
       this.$overlay.removeClass(cls);
       this.active_flg = false;
     },
     listdelay: function () {
       var timer = 0;
       this.$nav_menu.children('li').each(function () {
         $(this).css('animation-delay', timer + 's');
         timer = timer + 0.1;
       });
     }
   };

Nav.prototype でNavコンストラクタにメソッドを登録しています。

なんでprototypeなんて使ってメソッドを登録するのかの説明は以下の記事を参考にしてみてください。簡単にいうと、prototypeで登録した方がメモリを浪費しなくていいというメリットがあるのでこうしています。

さて、onメソッドでは何をしているかというと

on: function () {
       this.$nav_menu.addClass(cls);
       this.$humberger.addClass(cls);
       this.$overlay.addClass(cls);
       this.active_flg = true;
     },

.nav-menuと.js-humbergerと.overlayにactiveクラスをつけて、active_flgをtrueにしています。

offメソッドでは何をしているかというと

off: function () {
       this.$nav_menu.removeClass(cls);
       this.$humberger.removeClass(cls);
       this.$overlay.removeClass(cls);
       this.active_flg = false;
     },

.nav-menuと.js-humbergerと.overlayのactiveクラスをはずして、active_flgをfalseにしています。

listdelayメソッドでは何をしているかというと

   listdelay: function () {
       var timer = 0;
       this.$nav_menu.children('li').each(function () {
         $(this).css('animation-delay', timer + 's');
         timer = timer + 0.1;
       });
     }

.nav-menu直下のliタグにanimation-delayをeachメソッドを使って0.1sずらしながら付与しています。

これによって、ナビゲーションメニューが表示されるときにパラパラとずれて表示されるようになります。(表現が難しい)


コード解説(jQuey-4)

さて、だいたい完成したのですが最後にこれを即時関数で囲みます。

以下の部分です。

var navObject = (function () {
    (省略)
    
 }());

即時変数で囲むことでvar cls ="active"などのvar変数を即時関数内に閉じ込めておくことができます。

これのメリットはプロジェクト内の他のvar変数を汚染しないことです。

即時関数はよく使うのでこの機会に覚えておきましょう!

即時関数の中身の最後の方に以下のような記述がありますが、これをしないとnavObjectにコンストラクタであるNavが入っていないことになるので必ず書きましょう。

return Nav;

最後に、インスタンスを生成したら完成です!

var nav = new navObject();

インスタンスと言っても中身はオブジェクトです。

試しに、console.log(nav);と入力してみましょう。

var nav = new navObject();
console.log(nav);//追加

すると、検証ツールのコンソールタブに以下のように出ていることがわかります。

スクリーンショット 2020-03-22 19.14.08

たしかにオブジェクトですね!クリックして詳細をみてみると....

スクリーンショット 2020-03-22 19.13.57

$nav_menuや$overlayなどのプロパティや、_proto_の中にonメソッドやoffメソッドなどが書かれていますね!!すごい!!

コード解説(jQuey-5)

以下の部分を解説します。

  //js-sub-btn
 (function () {
   $('.js-sub-btn').on('click', function () {
     $(this).next().slideToggle();
     $(this).toggleClass('is-open');
   });
 }());

これはそんなに難しくないですね。

.js-sub-btnをクリックしたらその次の要素をslideToggle()で表示させ、toggleClass()で.js-sub-btnに.is-openクラスをつけたり外したりしています。

ちなみに、これも先ほど出てきた即時関数で囲んでいます。

これでだいたい完成しました。

番外編

もうほぼ完成しているのですが、1つ問題があります。

それは.js-sub-btnをクリックしてslideToggle()で表示させたサブナビゲーションが、オーバーレイやハンバーガーメニューをクリックして閉じたあと再び開くと、サブナビゲーションが開きっぱなしになっていることです。

つまり、以下の状態でハンバーガーメニューやオーバーレイをクリックしてナビゲーションを閉じ

スクリーンショット 2020-03-22 19.29.07

再び開くと、

スクリーンショット_2020-03-22_19_29_07

サブナビゲーションが開きっぱなしですね。

これはちょっと微妙なので再び開いたときは閉じているようにしたいと思います。

さて、どのように改修すればよいでしょうか?

やりたいことを言語化しましょう。

ハンバーガークリックまたはオーバーレイクリックしたときに.js-sub-btnについている.js-openクラスを外しslideTogge()で.mega-menuにインラン属性でついた値を消してあげれば良さそうです。

以下の写真の部分のことをいっています。

スクリーンショット_2020-03-22_19_37_21

さて、改修していきます。

まず、Navコンストラクタにプロパティを追加します。

var Nav = function () {
     var _this = this; //Nav
     this.$nav_menu = $('.nav-menu');
     this.$overlay = $('.overlay');
     this.$humberger = $('.js-humberger');
     this.$js_sub_btn = $('.js-sub-btn');←追加
     this.$mega_menu = $('.mega-menu');←追加
     this.active_flg = false;//初期フラグをfalseに

次に、offメソッドに以下を追加します。

      off: function () {
       this.$nav_menu.removeClass(cls);
       this.$humberger.removeClass(cls);
       this.$overlay.removeClass(cls);
       this.$js_sub_btn.removeClass('is-open');←追加
       this.$mega_menu.removeAttr('style');←追加
       this.active_flg = false;
     },

これだけでOKです!とても簡単に改修できましたね!

まとめ

いかがだったでしょうか。

flgがtrueかfalseかで動きを管理できるとコードがシンプルになって改修しやすいというメリットがあるのでflg管理の良さだけでもわかってくれたらいいなと思います。笑

ここまで読んでいただきありがとうございました。


この記事が気に入ったらサポートをしてみませんか?