スマホサイトに特化したアコーディオンメニューを作ってみた

はやち


スマホサイトに特化したアコーディオンメニューを作ってみた

どうもです、はやちです٩( ᐛ )و
秋に近づいて涼しくなってきましたね、皆様夏はいかがお過ごしでしたか?
はやちは夏のコミケに思わず三日間連続で参加してしまい、腕がこんがり焼けてしまいました( ˇωˇ )<健康的

そんなことはどうでもいいですね٩( ᐛ )و
今回はアコーディオンメニューを、もうちょいスマホに特化させた作りにしてみました! ご紹介します!

アコーディオンメニューの準備をしよう

まず、HTMLとSCSSの準備をします。

HTML

headerとアコーディオンさせるナビゲーションは別々に置きます( ˇωˇ )

<header id="js-accordion" class="header">
        <div class="header__heading">
            <h1 class="logo">accordion menu</h1>
                <span class="menu-button">
                    <i></i>
                </span>
        </div>
        <nav class="nav-content">
            <ul>
                <li><a href="#">Home</a></li>
                <li><a href="#">About</a></li>
                <li><a href="#">Blog</a></li>
                <li><a href="#">Gallery</a></li>
                <li><a href="#">Company</a></li>
                <li><a href="#">Contact</a></li>
            </ul>
        </nav>
</header>

SCSS

全体のスタイルはこちらです。

全体のレイアウト

今回アコーディオンの動きは、CSSのtransitionで対応します。
メニュー部分は、あらかじめ高さ分のポジションを設定します( ˇωˇ )

//headerのレイアウト
.header {
  width: 100%;
  z-index: 3;
  h1{
    font-family: 'Codystar', cursive;
    text-align: center;
    font-size:25rem
    padding: 20px 40px 20px 0px;
    color: #fff;
  }
  .header__heading{
    background:#000;
    position: absolute;
    width: 100%;
    z-index: 3;
  }
}

//アコーディオンメニューの中身
.nav-content{
  position: absolute;
  width: 100%;
  top: -735px;
  z-index: 2;
  ul li{
    font-family: 'Codystar', cursive;
    font-size:22rem;
    a{
      text-align: center;
      display: block;
      padding: 20px 0px;
      color: #fff;
    }
  }
  transition: 1.5s top;
}

//アコーディオンメニューの背景
#js-cover{
  background:#000;
  width: 100%;
  height: 100%;
  display: block;
  position: absolute;
  top: 0px;
  opacity: 0;
  z-index: 1;
}

アニメーションの動き

jsで.is-openがつくと動き出す用に設定します。
top:0pxだとヘッダー部分などに被ってしまうので、ヘッダーの高さ分topをつけてあげましょう( ˇωˇ )

//アコーディオンメニューの動き
.is-open{
  .nav-content{
    top: 65px;
  }
}

ハンバーガーメニューの作り方

お次にハンバーガーアイコンを設定します。

ハンバーガーアイコンは、menu-buttonの中のiが真ん中の棒、iの擬似要素beforeで上の棒、iの擬似要素afterで下に棒を設定。
それぞれの位置をtranslateYで調整します( ˇωˇ )
ポジションでやってしまうとpositionの中心が取れず、ガタついた動きになってしまうのでお気をつけくださいませ( ˇωˇ )

beforeの上の棒とafterの下の棒には、あらかじめ傾きをrotate(0deg)にしてあげます。
真ん中の棒は背景色で見えないようにするため、transition: background 0.5sを指定してあげましょう( ˇωˇ )

//ハンバーガーアイコン
.menu-button{ 
  width: 30px;
  height: 30px;
  display: block;
  position: absolute;
  right: 14px;
  top: 15px;
  i{
    display: block;
    width: 20px;
    height: 2px;
    border-radius: 3px;
    background: #fff;
    transition: background 0.5s;
    position: relative;
    left: 5px;
    top: 14px;
    &:before,
    &:after{
      content: "";
      display: block;
      width: 20px;
      height: 2px;
      border-radius: 3px;
      background: #fff;
      position: absolute;
      transform: rotate(0deg);
      transition: all 0.3s !important;
    }
    &:before{
      transform:translateY(8px);
    }
    &:after{
      transform:translateY(-8px);
    }
  }
}
アニメーションの動き

jsで.is-openがつくと動き出す用に設定します。
iはheader色で見えなくさせて、beforeとafterはtranslateY(0px)でiと同じ位置に寄せ、rotateで45ずつ傾けます( ˇωˇ )

//ハンバーガーアイコンのアニメーション
.is-open{
  .menu-button{
    i{
      background: #000;
      &:after{
        transform:translateY(0px) rotate(-45deg);
      }
      &:before{
        transform:translateY(0px) rotate(45deg);
      }
    }
  }
}

JavaScriptの実装をしよう

下準備ができたところで、JavaScriptを実装していきます。

変数の設定

変数を設定していきます。

アコーディオン用の変数

それぞれセレクタと高さの変数を用意します。

//1.アコーディオン用の変数
var $window = $(window),
    $wrapper = $('#wrapper'),
    $accordion = $('#js-accordion'),
    $menuButton = $(".menu-button"),
    $jsCover = $('#js-cover'),
    $nav = $('.nav-content'),

    //ナビゲーションの高さ
    navHeight = $nav.height(),

    //アニメーションの速度
    SPEED = 300,

    //wrapperの高さを取得
    bodyHeight = $wrapper.height(),

    //ヘッダータイトルの高さ
    HEADER_HEIGHT = 65;

全体のWindowサイズとWrapperのサイズを常に取得できる状態にする

アコーディオンを実装するにあたり、アコーディオン表示以上にスクロールをさせないようにしましょう。あらかじめWindow全体の高さとDomの高さを保持できるようにします( ˇωˇ )

//2.リサイズ時でも変動可能なグローバル変数
$window.on('load resize', function() {
    //ウィンドウの高さを取得
    windowHeight = $window.height();
});

開いたときと閉じたときの動きを用意しよう

次にCSSで用意したクラスのアニメーションとスクロールの制御する関数を用意します( ˇωˇ )

開くときの動き

クラスをつけて、アコーディオンが出てきた際にbodyの高さを固定して暗い背景を表示します( ˇωˇ )

//3.開くときの動き
function openAction(){
    //クラスをつける
    $accordion.addClass('is-open');

    //wrapperの高さwindowの高さと同じにする
    $wrapper.css({overflow:'hidden'}).height(windowHeight);

    //高さをつけてオーバーレイを表示
    $jsCover.height(windowHeight).stop().animate({opacity:1},SPEED);
}

閉じるときの動き

クラスを外したときに、はじめに保持したbodyの高さを付け直して元通りにします( ˇωˇ )

//4.閉じるときの動き
function closeAction(){
    //クラスを外す
    $accordion.removeClass('is-open');

    //wrapperの高さを元にもどしてスクロールできるようにする。
    $wrapper.css({overflow:'visivle'}).height(bodyHeight);

    //オーバーレイが消える
    $jsCover.stop().animate({opacity:0},SPEED);
}

クリックイベントを用意する

最後にクリックイベントをつけます。
クラスがあれば閉じる動きのcloseAction();が起動、クラスがなかったら開く動きのopenAction();が起動します( ˇωˇ )

//5.クリックイベント
    $menuButton.on('click',function(){
        //open用のクラスがあるかどうか判定
        if($accordion.hasClass('is-open')){
            //閉じるときの動き
            closeAction();
        } else {
            //開くときの動き
            openAction();
        }
    });

あれ? これでできたかな???
ぱっと見、完璧なようですが問題がありました。

実装の問題点と解消法とは?

次に問題点と解消法をご紹介します( ˇωˇ )

横に傾けたときの問題

iOSでスクロールをした際に、ヘッダーの検索ボックスが変わってしまうためresizeイベントが発火してしまう現象がおきました。
傾いたときのイベントを取りたいなら、resizeではなくてorientationchangeを使います( ˇωˇ )

修正前

//2.リサイズ時でも変動可能なグローバル変数
$window.on('load resize', function() {
    //ウィンドウの高さを取得
    windowHeight = $window.height();
    //wrapperの高さを取得
    bodyHeight = $wrapper.height();
});

修正後

//2.リサイズ時でも変動可能なグローバル変数
$window.on('load orientationchange', function() {
    //ウィンドウの高さを取得
    windowHeight = $window.height();
    //wrapperの高さを取得
    bodyHeight = $wrapper.height();
});

メニューを開いたまま傾けたときの、Windowの高さ処理も入れてあげましょう( ˇωˇ )

//6.開いたままスマホを傾けたとき
$window.on('orientationchange', function () {
    if($accordion.hasClass('is-open')){
        if(navHeight > windowHeight) {
            //wrapperの高さnavの高さにする
            $wrapper.height(navHeight + HEADER_HEIGHT);
            $jsCover.height(navHeight + HEADER_HEIGHT);
        } else {
            //wrapperの高さwindowの高さと同じにする
            $wrapper.height(windowHeight);
            $jsCover.height(windowHeight);
        }
    }
});

Windowの高さが狭かったときの対応

横に傾けたとき、ナビゲーションの幅よりWindowの高さが狭くなり、その分overflowでナビゲーションが見えなくなってしまうことがあります。
傾けてメニューを開いた際のWindowの高さを、if文で比較してあげて高さを設定してあげましょう( ˇωˇ )

修正前

//3.開くときの動き
function openAction(){
    //クラスをつける
    $accordion.addClass('is-open');

    //wrapperの高さwindowの高さと同じにする
    $wrapper.css({overflow:'hidden'}).height(windowHeight);

    //高さをつけてオーバーレイを表示
    $jsCover.height(windowHeight).stop().animate({opacity:1},SPEED);
}

修正後

//3.開くときの動き
function openAction(){
    //クラスをつける
    $accordion.addClass('is-open');
    //windodwの高さがナビゲーションより高さが狭いとき
    if(navHeight > windowHeight){

        //wrapperの高さnavの高さにする
        $wrapper.css({overflow:'hidden'}).height(navHeight + HEADER_HEIGHT);

        //高さをつけてオーバーレイを表示
        $jsCover.height(navHeight + HEADER_HEIGHT).stop().animate({opacity:1},SPEED);

    } else {

        //wrapperの高さwindowの高さと同じにする
        $wrapper.css({overflow:'hidden'}).height(windowHeight);

        //高さをつけてオーバーレイを表示
        $jsCover.height(windowHeight).stop().animate({opacity:1},SPEED);
    }
}

まとめ

いかがでしたでしょうか??
スマホでちょいとつまずいた部分は高さの変動とイベントの使い方でした。
今後とも問題点がすんなり解消できるように精進していきたいですね( ˇωˇ )

完成されたアコーディオンはこちらでご確認いただけます。
( ˘ω˘)☞三☞シュッシュッ Demo

JSの書き方などに何かありましたら、ご意見ただけると幸いです( ˇωˇ )

ではでは。

はやち
この記事を書いた人
はやち

フロントエンドエンジニア

関連記事