高さの異なる要素をフロートさせて並べる時に、jQuery を使ってそれぞれの高さを揃えて並べる方法に関するメモ。
以下のサンプルは div 要素(class=”foo”)をフロートで並べて表示する例。それぞれの高さが異なるので、同じ高さを指定しなければきれいに横には並ばない。
サンプルの「高さを揃える」をクリックすると、同じ行(横の列)の要素の最大の高さを検出して横に並べる。
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>
横幅が可変の場合に対応できるようにする。
関連情報:「リサイズ $(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」では、ブラウザがリサイズされると、それらの高さを算出する際に「リサイズされる前の高さ」を元に計算しているので、リフレッシュボタンを押すと高さが元の高さと異なっていることがわかる。これを修正。
<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”)の中に、クリックすると表示されるような要素がある場合の例。
このサンプルでは 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; }
やりたいこと:
概要:
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列で表示される幅の時の高さを元にしているため、その行の要素の高さが本来の高さより小さくなってしまう。
一番手っ取り早いのは、リサイズされたらリストを全て非表示に戻してしまう。(多分他にも方法はあるのだろうけど、条件分けがかなり複雑になると思う)
リサイズイベントに以下を追加
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); });