jquery position: fixed を使ってメニュー(ナビゲーション)を固定する際の注意点

2014年11月28日

position: fixed を使ってメニューを固定する際に気をつける点などの個人的なメモ。

結論から言うと、メニュー(ナビゲーション)の高さを指定し忘れるとメニュー以降に出現するリンク要素(ハイパーリンク)のリンクがクリックできなく(リンクが効かなく)なったり、ブラウザ上の文字(テキスト)を選択しようとしても選択できなくなってしまう。

関連ページ:「レスポンシブメニュー」「ページ内リンクへアニメーションで移動

また、以下のプラグインを使用すると簡単にメニューを固定表示することができる。
jQuery Waypoints を使ってみる

目次

メニューの構造

メニューは以下のように id=”navi” という div 要素内に ul 要素(id=”navi_menu”)で作成。またレスポンシブ対応にするために、幅が狭いときは「Navi」という文字(id=”pull”)を表示して画面の下部に固定。

一定幅以上では、ヘッダー(ロゴなどを表示)の下にメニューが表示され、スクロールしてメニューの位置より下になった場合は画面上部にメニューを固定するというもの。

HTML

<body>
<div id="header">
  <div id="header_inner">
    ヘッダーの記述
  <h1 id="logo">ロゴなど</h1>
  </div>
</div>
<div id="container">

<div id="navi">
  <ul id="navi_menu" class="clearfix">
    <li><a href="#location">Location</a></li>
    <li><a href="#hours">Hours</a></li>
    <li><a href="#menu">Menu</a></li>
    <li><a href="#about">About</a></li>
    <li><a href="#oo">Order Online</a></li>
    <li id="ttt"><a href="#">To the top</a></li>
  </ul>
  <a id="pull" href="#">Navi<i class="fa fa-bars"></i></a>
</div>
<div id="content">
以下省略

CSS

メニューを固定する場合(以下では@media screen and (min-width : 600px))に #navi に高さを指定する必要がある。(この記事のポイントはこれだけ。)

.clearfix:before,
.clearfix:after {
  content: " ";
  display: table;
}
.clearfix:after {
  clear: both;
}
.clearfix {
  *zoom: 1;
}

#navi {
  position: fixed; /*600px 以下では画面下に固定*/
  bottom: -3px;
  left: 0;
  z-index: 10;
  width: 100%;
  
}
#navi ul {
  display: none;
  height: auto;
  margin: 0;
  padding: 0;
}
#navi li {
  float: left;
  width: 50%;
  border-bottom: none;
  text-align: center;
}
#navi li a {
  padding: 12px 0;
  border-top: 1px solid #fff;
  background: #888;
  color: #fff;
  display: block;
  margin: auto;
  opacity: .9;
}
#navi li a:hover {
  background: #666;
  color: #F4A1A2;
}
#navi li:nth-of-type(odd) a {
  border-right: 1px solid #fff;
}
#navi a#pull {
  display: block;
  background-color: #666;
  border-top: 1px solid #fff;
  border-bottom: 3px solid #fff;
  width: 100%;
  position: relative;
  text-align: center;
  font-size: 1.3em;
  color: #fff;
  text-decoration: none;
  padding: 0.2em 0;
  margin-bottom: 0;
  line-height: 40px;
  opacity: .9;
}
#navi a#pull:hover {
  color: #F4A1A2;
}
#navi a#pull i {
  float: right;
  line-height: 40px;
  padding-right: 20px;
  font-size: 150%;
}

@media screen and (min-width : 600px){
  /*600px 以上ではメニューの位置までスクロールすると固定(jQueryで操作)*/
  #navi {
    position: relative;   
    margin: 1em 0;
    height: 20px;    /*この高さの指定が必要*/
  }
  #navi ul {
    display: block;
    height: auto;
    margin: 0;
    padding: 0;
  }
  #navi li {
    float: left;
    width: 20%;
    text-align: center;
  }
  #navi li.totop {
    display: none;
  }
  #navi li a {
    padding: 0 20px 10px;
    border-top: none;
    border-right: none;
    background: none;
    color: #555555;
    display: inline-block;
    margin: auto;
    text-shadow: -1px 1px rgba(255, 255, 255, 0.8);
  }
  #navi li:nth-of-type(odd) a {
    border-right: none;
  }
  #navi li a:hover {
    background: none;
    color: #F33538;
  }
  #navi li a:active {
    color: #4670e0;
  }
  #navi a#pull {
    display: none;
  }
  /*「To the top」というメニュー項目は600px以下の場合のみ表示*/
  #ttt {
    display: none; 
  }
  /*以下は jQuery で付与または削除されるクラス*/
  .menuTop {
    background: #fff;
    border-bottom: 1px solid #ccc;
    -webkit-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
    -moz-box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
    max-width: 840px;
    opacity: .95;
  }
}

jQuery

今回のメニューの場合、ページ内スクロールを使用しているので、jQuery ではいろいろな記述があるが本題とはそれほど関係ないが取りあえず記載。

jQuery(function($){  
  //スクロールする要素の判定(スムーズスクロール等で使用)
  var isHtmlScrollable = (function(){
    var html = $('html'), top = html.scrollTop();
    var elm = $('<div/>').height(10000).prependTo('body');
    html.scrollTop(10000);
    var rs = !!html.scrollTop();
    html.scrollTop(top);
    elm.remove();
    return rs;
  })();

  //先頭へのボタン
  $('#footer').after('<div class="tothetop"><a href="#"><i class="fa fa-arrow-up"></i><span> To the top</a></div>');
  var topBtn = $('.tothetop');
  topBtn.hide();

  var navi$ = $('#navi');
  //navの位置
  var navTop = navi$.offset().top;

  //スクロールイベント(スクロールした際にナビゲーションを固定する等)
  $(window).scroll(function () {
    var ww = $(window).width();
    var winTop = $(this).scrollTop();
    if (ww > 600 && $(this).scrollTop() > 400) {
      topBtn.fadeIn();
    } else {
      topBtn.fadeOut();
    }
    //ナビゲーションの固定
    if(ww > 600) {
      var ul_margin_left = 0;
      if(ww > 840) {
        //中央寄せ
        ul_margin_left = (ww -840) / 2;
      }
      if (winTop >= navTop) {
        navi$.css({position:'fixed', top: 0, marginTop: 0, marginLeft: ul_margin_left}).find('ul').addClass('menuTop').find('li a').css('padding-top', '20px');
      } else if (winTop <= navTop) {
        navi$.css({position:'relative', top: 0, marginTop: '2em', marginLeft: 0}).find('ul').removeClass('menuTop').find('li a').css('padding-top', 0);
      }
    }
  });
  //スムーズスクロール
  $('a[href^=#]').not('#pull, #smap, a[href^=#mc_]').click(function(){
        var hrefval= $(this).attr('href');
    var positiontop;
    var speed;
    if(hrefval == "#") {
      positiontop = 0;
      speed = 300 + $(this).offset().top /30;
    }else{
      var targetelement = $(hrefval);
      //メニューの高さとパディング等のオフセットを調整
      positiontop = targetelement.offset().top -50;
      speed = 300 + (positiontop + 100) / 10;
    }
        $(isHtmlScrollable ? 'html' : 'body').animate({
            scrollTop: positiontop
        },  speed);
        return false;
    });
  
    //幅が600px以下の場合のメニュー(ナビゲーション)
    var pull = $('#pull');
    var menu = $('#navi ul');
    var menuOpen = false;
    pull.on('click', function(e) {
        e.preventDefault();
    if(menuOpen){
      menu.slideUp();
      menuOpen = false;
      pull.html('Menu <i class="fa fa-bars"></i>');
    }else{
      menu.slideDown();
      menuOpen = true;
      pull.html('Close <i class="fa fa-bars"></i>');
    }
    });
  //リサイズ時の調整
  var timer = false;
  $(window).resize(function(){
    var w = $(window).width();
    if (timer !== false) {
      clearTimeout(timer);
    }
    timer = setTimeout(function() {
      if(w > 600) {
        navi$.find('ul').css('display', 'block');
      }else {
        if(!menuOpen) {
          navi$.find('ul').css('display', 'none');
        }
      }
    }, 200);
  });  
});

上記記述で作成したメニューのスクリーンショット

fixed_menu_01

一定幅以下の場合

fixed_menu_02