jquery jQuery を使って要素の高さを揃える

2014年1月24日

高さの異なる要素をフロートさせて並べる時に、jQuery を使ってそれぞれの高さを揃えて並べる方法に関するメモ。

横に並んだ要素の高さを揃える

以下のサンプルは div 要素(class=”foo”)をフロートで並べて表示する例。それぞれの高さが異なるので、同じ高さを指定しなければきれいに横には並ばない。

サンプル1

サンプルの「高さを揃える」をクリックすると、同じ行(横の列)の要素の最大の高さを検出して横に並べる。

HTML

<div id="content">
<div class="foo">
<h2>Index : 0 </h2>
<ul class="bar">
<li>bar 1</li>
<li>bar 1</li>
<li>bar 1</li>
</ul>

・・・省略・・・

<div class="foo">
<h2>Index : 8 </h2>
<ul class="bar">
<li>bar 9</li>
<li>bar 9</li>
</ul>
</div><!-- end of .foo -->
</div><!-- end of #content -->

CSS

#content {
  clear: both;
}
.foo {
  width: 300px;
  margin: 8px;
  border: 1px solid #B4A2A2;
  background: #EFEFEF;
  float: left;
}
ul.bar {
  list-style-type: none;
  padding: 20px;
}

このサンプルの場合、div 要素(class=”foo”)を3つずつ横に並べて表示している。

横に一列に並べるには隣り合う要素の「最大の高さ」を取得する必要がある。

横の列(行)の数の取得:Math.ceil(foo_length / 3)

div 要素(class=”foo”)の総数を並べる個数(3)で割ったものを切り上げした値

横の列(行)の中のそれぞれの要素の高さの最大値を取得して、それらの要素の高さとして設定する。

jQuery

<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
jQuery(function($){
  var foo$ = $('.foo');
  //div 要素(class="foo")の総数
  var foo_length = foo$.length;
  
  //横の列(行)それぞれについて実行
  for(var i = 0 ; i < Math.ceil(foo_length / 3) ; i++) {
    var maxHeight = 0;
    //同じ横の列(行)のそれぞれの要素について実行
    for(var j = 0; j < 3; j++){
      if (foo$.eq(i * 3 + j).height() > maxHeight) { 
        maxHeight = foo$.eq(i * 3 + j).height(); 
      }
    }
    //要素の高さの最大値をそれぞれの要素の高さとして設定
    for(var k = 0; k < 3; k++){
      foo$.eq(i * 3 + k).height(maxHeight); 
    }
  }      
});
</script>

可変幅に対応にする

サンプル2

横幅が可変の場合に対応できるようにする。

  • サンプル1で行った処理を関数「set_height()」にして、横に並べる要素の個数(n)を引数に取るようにする
  • ブラウザの横幅を検出して、横に並べる要素の個数を指定して関数「set_height()」を実行
  • リサイズされた場合の処理も追加

関連情報:「リサイズ $(window).resize が終了した時点で実行

jQuery

<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
jQuery(function($){
  //処理を関数にまとめる
  function set_height(n) {
    var foo$ = $('.foo');
    var foo_length = foo$.length;
    for(var i = 0 ; i < Math.ceil(foo_length / n) ; i++) {
      var maxHeight = 0;
      for(var j = 0; j < n; j++){
        if (foo$.eq(i * n + j).height() > maxHeight) { 
          maxHeight = foo$.eq(i * n + j).height(); 
        }
      }
      for(var k = 0; k < n; k++){
        foo$.eq(i * n + k).height(maxHeight); 
      }
    }    
  }
  
  //ブラウザの横幅
  var ww = $(window).width();
  //横幅により、並べる要素の個数を指定して関数を実行
  if(ww >= 954) {
    set_height(3);
  }else if(ww >= 640) {
    set_height(2);
  }
  
  //リサイズされた場合の処理
  var timer = false;
  $(window).resize(function(){  

    var window_width = $(window).width();
    
    if (timer !== false) {
      clearTimeout(timer);
    }
    timer = setTimeout(function() {
      if(window_width >= 954) {
        set_height(3);
      }else if(window_width >= 640) {
        set_height(2);
      }
    }, 200);  
  });   
  
});
</script>

修正(変更)

「サンプル2」では、ブラウザがリサイズされると、それらの高さを算出する際に「リサイズされる前の高さ」を元に計算しているので、リフレッシュボタンを押すと高さが元の高さと異なっていることがわかる。これを修正。

サンプル2-2

  • 最初にそれぞれの要素の元の高さを取得しておき、「data()」を使ってその値を格納
  • 高さを比較算出する際はその「data()」の値を元に算出する
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
jQuery(function($){
  
  //要素の元の高さを取得(追加)
  $('.foo').each(function(index, element) {
        $(this).data('height', $(this).height());
    });
  
  function set_height(n) {
    var foo$ = $('.foo');
    var foo_length = foo$.length;
    for(var i = 0 ; i < Math.ceil(foo_length / n) ; i++) {
      var maxHeight = 0;
      for(var j = 0; j < n; j++){
        //if (foo$.eq(i * n + j).height() > maxHeight) から変更
        if (foo$.eq(i * n + j).data('height') > maxHeight) { 
          //maxHeight = foo$.eq(i * n + j).height(); から変更
          maxHeight = foo$.eq(i * n + j).data('height'); 
        }
      }
      for(var k = 0; k < n; k++){
        foo$.eq(i * n + k).height(maxHeight); 
      }
    }    
  }
  //以下は同じ
  var ww = $(window).width();
  
  if(ww >= 954) {
    set_height(3);
  }else if(ww >= 640) {
    set_height(2);
  }
  
  var timer = false;
  $(window).resize(function(){  
    $('#ww span').text(' ' + $(window).width() + ' px');
    var window_width = $(window).width();
    
    if (timer !== false) {
      clearTimeout(timer);
    }
    timer = setTimeout(function() {
      if(window_width >= 954) {
        set_height(3);
      }else if(window_width >= 640) {
        set_height(2);
      }
    }, 200);  
  });   
  
});
</script>

高さを揃える要素の中に、可変の高さの要素がある場合

div 要素(class=”foo”)の中に、クリックすると表示されるような要素がある場合の例。

サンプル3

このサンプルでは div 要素(class=”foo”)の中の p 要素(class=”show_list”)をクリックすると、非表示にしていたリストをスライドダウンで表示するようになっている。

HTML

<div class="foo">
<h2>Index : 0 </h2>
<ul class="bar">
    <li>bar 1</li>
    <li>bar 1</li>
    <li>bar 1</li>
    <li>bar 1</li>
</ul>
<p class="show_list">List 1</p>
<ul class="hidden">
    <li>item 1</li>
    <li>item 2</li>
</ul>
</div><!-- end of .foo -->

CSS

ul.hidden {
  list-style-type: none;
  padding: 20px;
  background: #FFF;
  border: 1px solid #999;
  display: none;
  width: 200px;
  margin: auto;
}
ul.hidden li {
  height: 20px;
}

p.show_list {
  margin: 0 0 20px 20px;
  border: 1px solid #666;
  background: #999999;
  color: #FFF;
  width: 60px;
  padding: 5px;
  text-align: center;
  cursor: pointer;
}

やりたいこと:

  • p 要素(class=”show_list”)をクリックしてリストが表示される際に、横に並んだ要素の高さを変更(リストの分増加)する
  • その際、並んだ要素のリストを調査して一番長い(高さが大きい)リストの高さ分増加させる
  • リストを非表示にするときは、横に並んだ要素の中にリストが表示されている場合はそのままにしておき、そのリストが最後の場合に親要素の高さを元に戻す

概要:

  • 高さを増加させる関数「expand_height()」と元に戻す関数「reset_height() 」を作成
  • クリックされた要素が属する行(横の列)は、その要素のインデックスを元に Math.floor(index / n)で取得(n は横一列に表示する要素の数)
  • クリックされた要素が属する行(横の列)にある要素を比較
  • 比較する際は for 文で div 要素(クラス.foo)が属する行のインデックス(row)を使って foo$.eq(row * n + i) としてその行に含まれる要素を調査(n は横一列に表示する要素の個数)
  • 非表示になっている ul 要素含まれる li 要素の最大値を取得
  • div 要素(クラス.foo)の高さの最大値を取得
  • その行の中のリストが表示されているかどうかを判定するために、div 要素(クラス.foo)の data() に’expanded’という値を設定
  • ‘expanded’の値(’true’または’false’という文字列)を元に高さの増減を実行
  • また横に並んだ行の高さを揃える関数「set_height()」を実行する際にも data(‘expanded’) の値を利用して、横に並んだ要素が元の高さの場合は、あらかじめ取得しておいた元の高さで高さを揃える

jQuery

<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script>
jQuery(function($){
  var foo$ = $('.foo');
  var foo_length = foo$.length;
  var hidden_li_height = 20;
  //非表示になっている ul 要素の上下のパディング
  var hidden_ul_padding = parseInt($('.hidden').css('padding-top'), 10) + parseInt($('.hidden').css('padding-bottom'), 10);
  
  //n は横一列に表示する要素の個数、that はクリックされた p 要素自身($(this))
  function expand_height(n, that) {
    //クリックされた p 要素の次の(非表示になっている)ul 要素
    var hidden_list$ = that.next('.hidden');
    //非表示になっている ul 要素の上位(親)の div 要素(クラス.foo)
    var parent_div$ = hidden_list$.closest('.foo');
    //この div 要素(クラス.foo)のインデックス
    var index = foo$.index(parent_div$);
    //この div 要素(クラス.foo)が属する行(横の列)のインデックス
    var row = Math.floor(index / n);  
    //非表示になっている ul 要素に含まれる li 要素の数(初期化)
    var max_hidden_li_count = 0;
    //この div 要素(クラス.foo)が属する行(横の列)の中の div 要素の高さの最大値(初期化)
    var max_div_height = 0;
    //高さを変更(増加)したかどうかのフラグ
    var is_expanded = false;
    //クリックされた要素が属する行(横の列)にある要素を比較
    for(var i = 0; i < n; i++) {
      //非表示になっている ul 要素含まれる li 要素の最大値を取得
      if(foo$.eq(row * n + i).find('.hidden li').length > max_hidden_li_count) {
        max_hidden_li_count = foo$.eq(row * n + i).find('.hidden li').length;
      }
      //div 要素(クラス.foo)の高さの最大値を取得
      if(foo$.eq(row * n + i).height() > max_div_height) {
        max_div_height = foo$.eq(row * n +i).outerHeight(true);
      }
      //div 要素(クラス.foo)の data() の'expanded'が'true'ならフラグ「is_expanded」をtrue に
      if(foo$.eq(row * n + i).data('expanded') == 'true') is_expanded = true;
    }  
    
    //フラグ「is_expanded」 が true でなければ高さを li 要素の最大値分増加(+パディング)    
    if(!is_expanded) {    
      for(var i = 0; i < n; i++) {
        foo$.eq(row * n + i).stop().animate({
          height: (max_div_height + max_hidden_li_count * hidden_li_height + hidden_ul_padding) + 'px'
        }, 400);          
      }
    }
    //クリックされた p 要素の上位の div 要素(クラス.foo)の data() の'expanded'を'true'に
    foo$.eq(index).data('expanded', 'true');
  }
  
  function reset_height(n, that) {
    var hidden_list$ = that.next('.hidden');
    var parent_div$ = hidden_list$.closest('.foo');
    var index = foo$.index(parent_div$);
    var row = Math.floor(index / n);      
    var max_hidden_li_count = 0;
    var max_div_height = 0;
    
    //クリックされた要素が属する行(横の列)にある要素を比較
    for(var i = 0; i < n; i++) {
      //非表示になっている ul 要素含まれる li 要素の最大値を取得
      if(foo$.eq(row * n + i).find('.hidden li').length > max_hidden_li_count) {
        max_hidden_li_count = foo$.eq(row * n + i).find('.hidden li').length;
      }
      //div 要素(クラス.foo)の高さの最大値を取得
      if(foo$.eq(row * n + i).height() > max_div_height) {
        max_div_height = foo$.eq(row * n +i).outerHeight(true);
      }          
    }
    
    //クリックされた要素が属する行(横の列)のそれぞれのdiv 要素(クラス.foo)のdata() を調査
    var expanded_count = 0;
    for(var i = 0; i < n; i++) {
      //'expanded' が 'true' のものがあればカウント
      if(foo$.eq(row * n + i).data('expanded') == 'true') expanded_count++;
    }
    
    //'expanded' が 'true' のものが1つだけの場合のみ、高さを li 要素の最大値分(+パディング)減じる
    if(expanded_count == 1) {        
      for(var i = 0; i < n; i++) {
        foo$.eq(row * n + i).stop().animate({
          height: (max_div_height - max_hidden_li_count * hidden_li_height - hidden_ul_padding * 2 + 4) + 'px'
        }, 400);
      }
    }  
    //クリックされた p 要素の上位の div 要素(クラス.foo)の data() の'expanded'を'false'に
    foo$.eq(index).data('expanded', 'false');
  }
  
  //p 要素(.show_list)のクリックイベント
  $('p.show_list').click(function(){
    //ブラウザの幅を検出し、その幅により何列で表示するかを判定
    var window_w = $(window).width();
    //クリックされた p 要素の次の ul 要素が非表示の場合は「expand_height()」を実行
    if($(this).next('.hidden').css('display') == 'none') {
      if(window_w > 960) {
        expand_height(3, $(this));
      }else if(window_w > 640) {
        expand_height(2, $(this));
      }  
    // ul 要素が表示されている場合は「reset_height()」を実行      
    }else{
      if(window_w > 960) {
        reset_height(3, $(this));
      }else if(window_w > 640) {
        reset_height(2, $(this));
      }        
    }
    // ul 要素の表示・非表示の切り替え
    $(this).next('ul.hidden').slideToggle();    
  });
  
  //div 要素(クラス.foo)の元の高さを取得して data('height') に格納
  $('.foo').each(function(index, element) {
        $(this).data('height', $(this).height());
    });
  
  function set_height(n) {
    var foo$ = $('.foo');
    var foo_length = foo$.length;
    for(var i = 0 ; i < Math.ceil(foo_length / n) ; i++) {
      var maxHeight = 0;
      for(var j = 0; j < n; j++){
        //もし'expanded' が 'true' なら現在の高さ(表示された ul 要素の高さを含む)を比較
        if (foo$.eq(i * n + j).data('expanded') == 'true') {
          if (foo$.eq(i * n + j).height() > maxHeight) { 
            maxHeight = foo$.eq(i * n + j).height(); 
          }
        //そうでなければ初期状態の高さを比較
        }else{
          if (foo$.eq(i * n + j).data('height') > maxHeight) { 
            maxHeight = foo$.eq(i * n + j).data('height'); 
          }
        }      
      }
      for(var k = 0; k < n; k++){
        foo$.eq(i * n + k).height(maxHeight); 
      }
    }    
  }
  
  var ww = $(window).width();
  
  if(ww >= 954) {
    set_height(3);
  }else if(ww >= 640) {
    set_height(2);
  }
  
  var timer = false;
  $(window).resize(function(){  
    $('#ww span').text(' ' + $(window).width() + ' px');
    var window_width = $(window).width();
    
    if (timer !== false) {
      clearTimeout(timer);
    }
    timer = setTimeout(function() {
      if(window_width >= 954) {
        set_height(3);
      }else if(window_width >= 640) {
        set_height(2);
      }
    }, 200);  
  }); 
  
});
</script>

修正(変更)

固定幅の場合は問題ないがリサイズされた場合を考慮すると「サンプル2」同様、ブラウザがリサイズされる際に、それらの高さを算出するときは「リサイズされる前の高さ」を元に計算しているので問題が発生する。

「サンプル3」の場合、2列で表示される幅(953px以下)で「Index : 8 」のリスト「List 9」をクリックしてから幅を 954px 以上に変更して、「List 9」をクリックすると、2列で表示される幅の時の高さを元にしているため、その行の要素の高さが本来の高さより小さくなってしまう。

一番手っ取り早いのは、リサイズされたらリストを全て非表示に戻してしまう。(多分他にも方法はあるのだろうけど、条件分けがかなり複雑になると思う)

サンプル3-2

リサイズイベントに以下を追加

  • リストの要素全てに対し「each()」を使って、表示されているものは「slideToggle()」で非表示にする
  • その際に data(‘expanded’) の値を’false’に設定
  • また div 要素(クラス.foo)の高さはあらかじめ取得しておいた元の高さに戻す
var timer = false;
  $(window).resize(function(){  
    $('#ww span').text(' ' + $(window).width() + ' px');
    var window_width = $(window).width();
    
    if (timer !== false) {
      clearTimeout(timer);
    }
    timer = setTimeout(function() {
      //追加
      $('.hidden').each(function(index, element) {
                if($(this).css('display') != 'none') {
          $(this).slideToggle(500);
          $(this).closest('.foo').data('expanded', 'false');
        }
            });
      
      //追加
      $('.foo').each(function(index, element) {
        $(this).height($(this).data('height'));
        
      });
      
      if(window_width >= 954) {
        set_height(3);
      }else if(window_width >= 640) {
        set_height(2);
      }
    }, 200);  
  });