htmlcss レスポンシブメニュー

2013年12月7日

基本的(単純)なレスポンシブメニュー(Menu 部分をクリックするとメニューが展開されて表示されるようなメニュー)の作成に関するメモ。

PC などでの表示(メニューは横並び)

responsive_menu03

ヘッダ部分の「960×240」は特に意味はなし。ダミー画像。

<img src="http://dummyimage.com/960x240/666/fff" alt="">
<!-- 上記のように記述すると表示できる(参考) -->

スマートフォンなどでの表示

responsive_menu01

Menu 部分をクリック(タッチ)すると展開される

responsive_menu02

HTML の記述

head 要素内に Viewport の指定をする。
(関連ページ:Viewport と Media Queries に関するメモ

<meta name="viewport" content="width=device-width">

body 要素内にメニューの記述をする。

<div id="navi" class="clearfix">
    <ul class="clearfix">
      <li><a class="disabled" href="#">Top</a></li>
      <li><a href="en/schedule/schedule.html">Schedule</a></li>
      <li><a href="en/profile/profile.html">Profile</a></li>
      <li><a href="en/discography/discography.html">Discography</a></li>
      <li><a href="en/video/video.html">Video</a></li>
      <li><a href="en/contact/contact1.php">Contact</a></li>
      <li><a href="indexjp.html">Japanese</a></li>
    </ul>
    <a href="#" id="pull">Menu</a> 
</div><!-- end of #navi -->

メニュー項目の他に「id=”pull”」のリンクを追加している。これはスモールデバイス(スマートフォン等)での表示の際に「Menu」や「Close」と表示されるメニューの開閉用のリンク。

CSS(スタイル)

メディアクエリーはスマートフォンなどの小さなスクリーン用から記述。

  • デスクトップを含めた共通で利用する、フォントや背景色などのベースとなるスタイルの指定
  • スマートフォン(スモールデバイス)に最適化したレイアウトの指定

メディアクエリーを使った部分では、メディアクエリーを指定していないデフォルトのスタイルシートとの差分を記述する。

以下は共通する部分とスモールデバイス(480px 未満の幅)用の記述。

#navi {
  margin-bottom : 1.5em;
  background-color: #000;
  border-top: 1px solid #827D7D;
  width: 100%;   /* 幅を100%に */
  z-index: 100;  /* 必要に応じて */
  margin-top: 0;
}

#navi ul {
  overflow : hidden;     /* 必要に応じて */
  margin: 0;
  padding: 0;
}
#navi ul li {
  width : 100%;    /* 幅を100%に */
  border-bottom: 1px solid #666;
}
#navi ul li a {
  display : block;
  text-align: center;
  color : #FFF;
  padding : 0.8em 0;
}

#navi ul li a:hover {
  color:#8FEDF1;
  background-color : #333;
}

#navi ul {  
  display: none;      /* スモールデバイスでは非表示に */
  height: auto;  
} 

#navi a#pull {  
  display: block;        /* #pull のみ表示 */
  background-color: #000;  
  width: 100%;  
  position: relative;  
  text-align: center;
  font-size: 1.3em;
  color: #FFF;
  text-decoration: none;
} 
#navi a:hover#pull {
  color: #99DDF5;
}
/* :after 擬似要素で #pull に背景画像を指定 */
#navi a#pull:after {  
  content:"";  
  background: url(../images/navi-icon.png) no-repeat; 
  width: 30px;  
  height: 30px;  
  display: inline-block;  
  position: relative; 
  left: 35%;
  top: 5px;  
} 
/* :after 擬似要素でホバー時の背景画像を指定 */ 
/* スマートフォンなどでは「ホバー」はないので不要かも */ 
#navi a:hover#pull:after {  
  content:"";  
  background: url(../images/navi-icon-hover.png) no-repeat; 
  width: 30px;  
  height: 30px;  
  display: inline-block;  
  position: relative; 
  left: 35%;
  top: 5px;  
} 

/*clearfix*/
.clearfix:after {
  content: "";
  display: block;
  clear: both;
}
.clearfix {
  zoom: 1;
}

「:before, :after 擬似要素」を使用する際、content プロパティは必要条件で、記述が無いと動作しないので、空の content プロパティを指定。

480px~767px

li 要素の横幅は「小数点以下のピクセルの計算」も参照

また、スクロールした際にメニューをjQuery などを使って position:fixed で固定する場合は高さを指定する必要がある。(関連ページ:position: fixed を使ってメニューを固定する際の注意点

@media screen and (min-width : 480px){
  #navi{  
      /* 必要に応じて高さを指定 */
  }  
  #navi a#pull {  
      display: none;      /* #pullリンクを非表示に */
  }  
  #navi ul {  
    display: block;   /* メニューを表示 */
  } 
  #navi ul li { 
    width : 14.129464%;     /* 幅を%で指定(項目数、マージン等を考慮) */
    float : left;   /* 水平方向に並べて表示 */
  }  
  #navi ul li a {
    padding : 0.9em 0;
  }
}

768px~959px

@media screen and (min-width : 768px){
  #navi ul li a {
    padding : 0.7em 0;
  }
}

960px 以上

@media screen and (min-width : 960px) {
  #navi {
    width: 960px;   /* メニュー(ナビ)の幅を固定 */
  }  
  #navi ul li a {
    padding : 0.6em 0;
  }
}

jQuery の記述(メニューの開閉)

「Menu」という表示(#pull)をクリックした際の動作を記述。

  • 「menuOpen」というフラグを設定し、フラグにより表示する文字を「Menu」か「Close」にする。
  • ウィンドウがリサイズされた場合に、「閉じる」がクリックされた後だと「slideUp()」によって「display: none」が付加されているため、メニューが表示されないので、スタイルを削除
  • 「:hidden」非表示になっている要素を抽出するセレクタ(is(‘:hidden’) の代わりに css(‘display’) で判定することも可能)
jQuery(function($){
    var pull        = $('#pull');  
    var menu        = $('#navi ul');   
    var menuOpen = false;
    $(pull).on('click', function(e) {  
        e.preventDefault();    
        if(menuOpen){
            menu.slideUp();
            menuOpen = false;
            pull.text('Menu');
        }else{
            menu.slideDown();
            menuOpen = true;
            pull.text('Close');
        }    
    }); 
    
    $(window).resize(function(){  
        var w = $(window).width();  
        if(w > 320 && menu.is(':hidden')) {  
            menu.removeAttr('style');  
        }  
    });
});

#pull の文字の表示を変更(Menu → Close)しない場合は、「slideToggle()」を使って簡単にできる。

jQuery(function($){
    var pull        = $('#pull');  
    var menu        = $('#navi ul');   
    $(pull).on('click', function(e) {  
        e.preventDefault();  
        menu.slideToggle();      
    }); 
    
    $(window).resize(function(){  
        var w = $(window).width();  
        // :hidden の代わりに css('display') で判定
        if(w > 320 && menu.css('display') == 'none') {  
      menu.removeAttr('style');  
    }  
    });
});

アイコンフォントを使う場合

:after 擬似要素で #pull に背景画像を指定する代わりに、アイコンフォントを使う場合の例。

この例では背景画像の代わりに、「Font Awesome」の「fa-bars」を使用。

HTML(変更箇所のみ)

<a href="#" id="pull">Menu <i class="fa fa-bars"></i></a>

アイコンフォントの i 要素を float で右寄せして位置やサイズを調整。

CSS(変更箇所のみ)

a#pull i  {
    float: right;
    line-height: 40px;
    padding-right: 20px;
    font-size: 150%;
}

/* 以下は不要なので削除 */
/* a#pull:after {
  content:"";
  background: url(../images/navi-icon-hover.png) no-repeat;
  width: 30px;
  height: 30px;
  display: inline-block;
  position: relative;
  left: 35%;
  top: 5px;
}
a:hover#pull:after {
  content:"";
  background: url(../images/navi-icon-hover.png) no-repeat;
  width: 30px;
  height: 30px;
  display: inline-block;
  position: relative;
  left: 35%;
  top: 5px;
} */

jQuery では text() の代わりに html() を使用。

jQuery

jQuery(function($){
    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>');
        }    
    }); 
    
    $(window).resize(function(){  
        var w = $(window).width();  
        if(w > 320 && menu.is(':hidden')) {  
            menu.removeAttr('style');  
        }  
    });
});

別の方法の例

PC などでの表示
responsive_menu04

スマートフォンなどでの表示

responsive_menu05

前述の例の「#pull」に該当する部分(メニュー開閉ボタン)が、以下の例では「#navControl」になっていて、「Menu」という文字は表示せず、アイコンのみ(画像を用意)。

HTML の記述

<div id="navControl">
        <a href="#" class="close">Navigation</a>
</div>
      <div id="navi" class="clearfix">
        <ul id="globalNav" class="clearfix">
          <li><a href="index.html">HOME</a></li>
          <li><a href="news.html">NEWS</a></li>
          <li><a href="garelly.html">GARELLY</a></li>
          <li><a href="access.html">ACCESS</a></li>
          <li><a href="contact.html">CONTACT</a></li>
        </ul>
      </div><!-- end of #navi -->

CSS(スタイル)

メニュー開閉ボタンは、画像で表示するためテキストが表示されないようにする。

  • text-indent: 100% でボックスの幅だけインデントする
  • white-space: nowrap でテキストが改行してボックスの中に残らないようにする
  • overflow: hidden で外に出た部分を非表示に
  • 背景色と背景画像を指定

またJavaScript でナビゲーションを非表示にするので、480px 以上では「ul#globalNav」に「display: block !important」で CSS の指定を優先させている。(前述の例では、 $(window).resize() を使って対処)

#navControl a {  /* メニュー開閉ボタンのスタイル */
  display: block;
  overflow: hidden;
  width: 44px;
  height: 44px;
  /* 画像の位置指定は使用する画像により異なる */
  background: #000 url(img/icon.png) no-repeat 0 0;
  text-indent: 100%;
  white-space: nowrap;
}

ul#globalNav {
  margin: 0;
  padding: 0;
  list-style-type: none;
}
ul#globalNav li a {
  display: block;
  padding: 10px;
  background: #333;
  color: #fff;
  text-decoration: none;
}
ul#globalNav li a:hover{
  background-color: #003159;
}

/* 480px 以上 */
@media only screen and (min-width: 480px) {

  #navControl {
    display: none;  /* メニュー開閉ボタンは非表示に */
  }
  ul#globalNav {
    display: block !important;  /* メニュー(ナビゲーション)は必ず表示 */
  }
  ul#globalNav li {
    float: left;  /* 横並びに */
    width: 18.6324%;  /* 幅の指定 */
    margin-right: 1.7094%;  /* 右マージンの指定 */
    text-align: center;
  }
  ul#globalNav li:last-child {
    margin-right: 0;  /* 最後の要素は右マージン無し */
  }
}
各メニューの横幅とマージンの算出

このサンプルの場合、以下のように想定。

  • 最大の画面サイズ(コンテンツの幅)を「1170px」
  • この時、メニューの横幅を「218px」、各メニュー間の間隔を「20px」
  • (218 x 5 + 20 x 4 = 1170)
  • 横幅:218 ÷ 1170 x 100 = 18.6324%
  • メニュー間の間隔:20 ÷ 1170 x 100 = 1.7094%

jQuery の記述(メニューの開閉)

メニュー開閉ボタン(#navControl a)のクラス属性が「close」の場合(初期値)は、メニューを非表示にする。CSS で非表示にしてしまうと JavaScript がオフの場合メニューが表示されなくなるため JavaScript で非表示にする。

メニュー開閉ボタンをクリックした時、クラス属性が「close」の場合、メニューをスライドダウンして表示し、クラス属性を一度削除後、「active」に変更。

メニュー開閉ボタンのクラス属性が「close」の以外の場合、メニューをスライドアップして非表示(display:none)にし、クラス属性を「close」に変更。

但し、480px 以上の場合は CSS に「display: block !important」で指定してあるので表示される。

$(function () {
  if($('#navControl a').attr('class') == 'close'){
    $('#globalNav').css('display', 'none');
  }
  $('#navControl a').click(function() {
    if($(this).attr('class') == 'close'){
      $('#globalNav').slideDown();
      $(this).removeClass();
      $(this).addClass('active');
    }else{
      $('#globalNav').slideUp();
      $(this).removeClass();
      $(this).addClass('close');
    }
  });  
});

メニュー開閉ボタン(#navControl a)のクラス属性「active」を使用して、表示を変更したりしなければ、「slideToggle()」と「toggleClass()」を使って以下のようにも記述可能。

$(function () {
  if($('#navControl a').attr('class') == 'close'){
    $('#globalNav').css('display', 'none');
  }
    $('#navControl a').click(function() {
    $('#globalNav').slideToggle();
    $(this).toggleClass('close');
  });  
});

メニューの開閉ボタンを HTML に記述しない場合

HTML の記述

HTML から前述のメニューの開閉ボタンを構成する要素を削除して、jQuery で追加する。

<div id="navi" class="clearfix">
        <ul id="globalNav" class="clearfix">
          <li><a href="index.html">HOME</a></li>
          <li><a href="news.html">NEWS</a></li>
          <li><a href="garelly.html">GARELLY</a></li>
          <li><a href="access.html">ACCESS</a></li>
          <li><a href="contact.html">CONTACT</a></li>
        </ul>
</div><!-- end of #navi -->

jQuery の記述

prepend() を使用してメニューの開閉ボタンを構成する要素を追加するだけ。(後は同じ。 CSS も同じ)

$(function () {
  $('#navi').prepend('<div id="navControl"><a href="#" class="close">Navigation</a></div>');
    
  if($('#navControl a').attr('class') == 'close'){
    $('#globalNav').css('display', 'none');
  }
  $('#navControl a').click(function() {
    if($(this).attr('class') == 'close'){
      $('#globalNav').slideDown();
      $(this).removeClass();
      $(this).addClass('active');
    }else{
      $('#globalNav').slideUp();
      $(this).removeClass();
      $(this).addClass('close');
    }
  });  
});

小数点以下のピクセルの計算

ブラウザによって小数点以下のピクセルを持った場合の計算の仕方が異なる。

  • Webkit, Firefox, IE8 以上では全体に収まるように自動的に調整される。
  • IE7 以下では「0.5以上は切り上げ」、「0.5未満は切り下げ」られる。

このため、場合によってはメニューの項目が全体に収まりきらず、落ちてしまうことがある。

IE7 以下でも対応させる場合には、メニューの項目の幅の合計が 100% にするのではなく、少し余裕を持たせると良い。

幅の算出

IE7 は小数点以下のピクセルを計算する際に、0.5px 以上は切り上げするので 0.5px より小さい幅となるように「0.5px ÷ 親要素の幅 X 100%」を width の値から引くようにする。親要素の幅は最小のときの幅を使う。

この例の場合、7項目あるので単純に7で割ると「14.285714%」

上記の算出方法を適用すると

14.285714 – (0.5 ÷ 320 X 100) = 14.129464%

また、逆にピクセルが足りない場合も発生するので、親要素の ul 要素などに背景色を設定しておくとよい。