Ruby
CSS
JavaScript
Rails
jQuery

ハンバーガーメニューを表示する時、背景を固定する方法

やること

あらゆるOSにおいて、スクロールがあるハンバーガーメニューを表示している間、背景のスクロールは動かないようにする。

まえおき

基本的に、スクロールさせるには overflow : scroll;、スクロールをしないようにするにはoverflow: hidden;を使うことが一般的。
アプリケーションのhtmlタグには初期値でoverflow : scroll;がついている。
そのため、わざわざoverflow : scroll;を当てなくてもスクロールできるようになっている。

ちなみに

htmlタグにoverflow: hiddenをつけてみてください。ほとんどの場合、スクロールがを防げます。
このほとんどというのがどういうことか後ほどわかります。

実装前のCSSとJSのコード

ハンバーガーメニューのCSS

.humburger-menu {
  /*スクロールするようにする*/
  overflow: scroll;
  /*ハンバーガーをその場に固定*/
  position: fixed;
  /*上端との距離*/
  top: 0;
  /*高さ画面いっぱい*/
  height: 100%;
  /*滑らかスクロール*/
  -webkit-overflow-scrolling: touch;
}

ハンバーガーメニューを開け閉じするJavaScript

function slideIn(){
  // 見やすくするためにするために変数を作成
  var menu = $('.humburger-menu'), // 開け閉じする要素
    menuBtn = $('.hamburger_button'), // メニューボタン
    body = $(document.body),
    menuWidth = menu.outerWidth();

  // メニューボタンをクリックした時の動き
  menuBtn.on('click', function(){
    // body に open クラスをつけたりはずしたりする( open クラスは空)
    body.toggleClass('open');
    if(body.hasClass('open')){
      // open クラスが body についていたらメニューをスライドインする
      body.animate({'right' : menuWidth }, 200);
      menu.animate({'right' : 0 }, 200);
    } else {
      // open クラスが body についていなかったらスライドアウトする
      menu.animate({'right' : -menuWidth }, 200);
      body.animate({'right' : 0 }, 200);
    }
  });
};

試したこと

1. htmlタグに overflow: hidden; を直接つける

function slideIn(){
  var menu = $('.humburger-menu'),
    menuBtn = $('.hamburger_button'),
    body = $(document.body),
    menuWidth = menu.outerWidth();

  menuBtn.on('click', function(){
    body.toggleClass('open');
    if(body.hasClass('open')){
      body.animate({'right' : menuWidth }, 200);
      menu.animate({'right' : 0 }, 200);
      $('html').css('overflow':'hidden') // 追記
    } else {
      menu.animate({'right' : -menuWidth }, 200);
      body.animate({'right' : 0 }, 200);
      $('html').css('overflow':'scroll') // 追記
    }
  });
};

これでほとんど動きます。
しかし、、iOSで正常に動作しません!!

Android, PC : ハンバーガーメニューをスクロールしても背景は動かない
iOS : ハンバーガーメニューをスクロールすると背景もスクロールされる

というわけで不採用!!

2. htmlタグに overflow: hidden; を含むclassつける

新しいCSS class

.scroll-prevent {
  overflow: hidden;
}
function slideIn(){
  var menu = $('.humburger-menu'),
    menuBtn = $('.hamburger_button'),
    body = $(document.body),
    menuWidth = menu.outerWidth();

  menuBtn.on('click', function(){
    body.toggleClass('open');
    if(body.hasClass('open')){
      body.animate({'right' : menuWidth }, 200);
      menu.animate({'right' : 0 }, 200);
      $('html').addClass('scroll-prevent') // 追記
    } else {
      menu.animate({'right' : -menuWidth }, 200);
      body.animate({'right' : 0 }, 200);
      $('html').removeClass('scroll-prevent') // 追記
    }
  });
};

これは完全にダメ元でやりましたね。
1. の結果と同じです...

3. htmlタグに position: fixed; を含むclassをつける

新しいCSS class

.scroll-prevent {
  /*動き固定*/
  position: fixed;
  /*奥行きを管理*/
  z-index: -1;
  /*下2つで背景を元のサイズのまま表示することができる*/
  width: 100%;
  height: 100%;
}
function slideIn(){
  var menu = $('.humburger-menu'),
    menuBtn = $('.hamburger_button'),
    body = $(document.body),
    menuWidth = menu.outerWidth();

  menuBtn.on('click', function(){
    body.toggleClass('open');
    if(body.hasClass('open')){
      body.animate({'right' : menuWidth }, 200);
      menu.animate({'right' : 0 }, 200);
      $('html').addClass('scroll-prevent') // 追記
    } else {
      menu.animate({'right' : -menuWidth }, 200);
      body.animate({'right' : 0 }, 200);
      $('html').removeClass('scroll-prevent') // 追記
    }
  });
};

できました!!
iOSでも求めていた動作が確認できました!!
ちなみにaddClassremoveClasstoggleClassに省略することができます。
こんな風に

function slideIn(){
  var menu = $('.humburger-menu'),
    menuBtn = $('.hamburger_button'),
    body = $(document.body),
    menuWidth = menu.outerWidth();

  menuBtn.on('click', function(){
    body.toggleClass('open');
    $('html').toggleClass('scroll-prevent') // 追記
    if(body.hasClass('open')){
      body.animate({'right' : menuWidth }, 200);
      menu.animate({'right' : 0 }, 200);
    } else {
      menu.animate({'right' : -menuWidth }, 200);
      body.animate({'right' : 0 }, 200);
    }
  });
};

追記

このままだと、ハンバーガー内のリンク先に飛んだ時に、scroll-preventが居残ってしまい、遷移先の画面のスクロールできないことがわかりました。。。

function slideIn(){
  var menu = $('.humburger-menu'),
    menuBtn = $('.hamburger_button'),
    body = $(document.body),
    menuWidth = menu.outerWidth();

  $('html').removeclass('scroll-prevent') // 追記

  menuBtn.on('click', function(){
    body.toggleClass('open');
    $('html').toggleClass('scroll-prevent')
    if(body.hasClass('open')){
      body.animate({'right' : menuWidth }, 200);
      menu.animate({'right' : 0 }, 200);
    } else {
      menu.animate({'right' : -menuWidth }, 200);
      body.animate({'right' : 0 }, 200);
    }
  });
};

この追記でつけたした

$('html').removeclass('scroll-prevent')

scroll-preventclassをページ遷移時にremoveすることで、初期値に戻し異常なく動くようになりました!

余談

これはハンバーガーだけでなく、モーダルなどでも応用することができそうです。(試してないけど)
デザインなどに応じてCSSの中身を変更することで、柔軟に対応することができると思います。
例えばこんな感じ

.scroll-prevent {
  position: fixed;
  z-index: -1;
  width: 100%;
  height: 100%;
  top: 0; /*追記*/
  right: 0; /*追記*/
}

それでもめんどくさいので、全OSがoverflow: hiddenだけで解決できるようになることを期待して終わります。